nss: upgrade to release 3.73
[LibreOffice.git] / vcl / unx / gtk3 / gtk3gtkinst.cxx
bloba5d233e2f95e5fdf10bbb996da38b5024b7d62a6
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <sal/config.h>
12 #include <deque>
13 #include <stack>
14 #include <string.h>
15 #include <osl/process.h>
16 #include <unx/gtk/gtkdata.hxx>
17 #include <unx/gtk/gtkinst.hxx>
18 #include <unx/salobj.h>
19 #include <unx/gtk/gtkgdi.hxx>
20 #include <unx/gtk/gtkframe.hxx>
21 #include <unx/gtk/gtkobject.hxx>
22 #include <unx/gtk/atkbridge.hxx>
23 #include <unx/gtk/gtkprn.hxx>
24 #include <unx/gtk/gtksalmenu.hxx>
25 #include <headless/svpvd.hxx>
26 #include <headless/svpbmp.hxx>
27 #include <vcl/inputtypes.hxx>
28 #include <vcl/transfer.hxx>
29 #include <unx/genpspgraphics.h>
30 #include <rtl/strbuf.hxx>
31 #include <sal/log.hxx>
32 #include <rtl/uri.hxx>
34 #include <vcl/settings.hxx>
36 #include <dlfcn.h>
37 #include <fcntl.h>
38 #include <unistd.h>
40 #include <unx/gtk/gtkprintwrapper.hxx>
42 #include "a11y/atkwrapper.hxx"
43 #include <com/sun/star/lang/IllegalArgumentException.hpp>
44 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
45 #include <com/sun/star/lang/XServiceInfo.hpp>
46 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
47 #include <com/sun/star/lang/XInitialization.hpp>
48 #include <com/sun/star/datatransfer/XTransferable.hpp>
49 #include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
50 #include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
51 #include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
52 #include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
53 #include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
54 #include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
55 #include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
56 #include <comphelper/lok.hxx>
57 #include <comphelper/processfactory.hxx>
58 #include <comphelper/sequence.hxx>
59 #include <cppuhelper/compbase.hxx>
60 #include <comphelper/string.hxx>
61 #include <cppuhelper/implbase.hxx>
62 #include <cppuhelper/supportsservice.hxx>
63 #include <officecfg/Office/Common.hxx>
64 #include <rtl/bootstrap.hxx>
65 #include <o3tl/unreachable.hxx>
66 #include <svl/zforlist.hxx>
67 #include <svl/zformat.hxx>
68 #include <tools/helpers.hxx>
69 #include <tools/fract.hxx>
70 #include <tools/stream.hxx>
71 #include <unotools/resmgr.hxx>
72 #include <unx/gstsink.hxx>
73 #include <vcl/ImageTree.hxx>
74 #include <vcl/abstdlg.hxx>
75 #include <vcl/event.hxx>
76 #include <vcl/i18nhelp.hxx>
77 #include <vcl/quickselectionengine.hxx>
78 #include <vcl/mnemonic.hxx>
79 #include <vcl/pngwrite.hxx>
80 #include <vcl/stdtext.hxx>
81 #include <vcl/syswin.hxx>
82 #include <vcl/virdev.hxx>
83 #include <vcl/weld.hxx>
84 #include <vcl/wrkwin.hxx>
85 #include <strings.hrc>
86 #include <window.h>
87 #include <numeric>
89 #include <boost/property_tree/ptree.hpp>
91 using namespace com::sun::star;
92 using namespace com::sun::star::uno;
93 using namespace com::sun::star::lang;
95 extern "C"
97 #define GET_YIELD_MUTEX() static_cast<GtkYieldMutex*>(GetSalData()->m_pInstance->GetYieldMutex())
98 static void GdkThreadsEnter()
100 GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
101 pYieldMutex->ThreadsEnter();
103 static void GdkThreadsLeave()
105 GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
106 pYieldMutex->ThreadsLeave();
109 VCLPLUG_GTK_PUBLIC SalInstance* create_SalInstance()
111 SAL_INFO(
112 "vcl.gtk",
113 "create vcl plugin instance with gtk version " << gtk_major_version
114 << " " << gtk_minor_version << " " << gtk_micro_version);
116 if (gtk_major_version == 3 && gtk_minor_version < 18)
118 g_warning("require gtk >= 3.18 for theme expectations");
119 return nullptr;
122 // for gtk2 it is always built with X support, so this is always called
123 // for gtk3 it is normally built with X and Wayland support, if
124 // X is supported GDK_WINDOWING_X11 is defined and this is always
125 // called, regardless of if we're running under X or Wayland.
126 // We can't use (DLSYM_GDK_IS_X11_DISPLAY(pDisplay)) to only do it under
127 // X, because we need to do it earlier than we have a display
128 #if defined(GDK_WINDOWING_X11)
129 /* #i92121# workaround deadlocks in the X11 implementation
131 static const char* pNoXInitThreads = getenv( "SAL_NO_XINITTHREADS" );
132 /* #i90094#
133 from now on we know that an X connection will be
134 established, so protect X against itself
136 if( ! ( pNoXInitThreads && *pNoXInitThreads ) )
137 XInitThreads();
138 #endif
140 // init gdk thread protection
141 bool const sup = g_thread_supported();
142 // extracted from the 'if' to avoid Clang -Wunreachable-code
143 if ( !sup )
144 g_thread_init( nullptr );
146 gdk_threads_set_lock_functions (GdkThreadsEnter, GdkThreadsLeave);
147 SAL_INFO("vcl.gtk", "Hooked gdk threads locks");
149 auto pYieldMutex = std::make_unique<GtkYieldMutex>();
151 gdk_threads_init();
153 GtkInstance* pInstance = new GtkInstance( std::move(pYieldMutex) );
154 SAL_INFO("vcl.gtk", "creating GtkInstance " << pInstance);
156 // Create SalData, this does not leak
157 new GtkSalData( pInstance );
159 return pInstance;
163 static VclInputFlags categorizeEvent(const GdkEvent *pEvent)
165 VclInputFlags nType = VclInputFlags::NONE;
166 switch( pEvent->type )
168 case GDK_MOTION_NOTIFY:
169 case GDK_BUTTON_PRESS:
170 case GDK_2BUTTON_PRESS:
171 case GDK_3BUTTON_PRESS:
172 case GDK_BUTTON_RELEASE:
173 case GDK_ENTER_NOTIFY:
174 case GDK_LEAVE_NOTIFY:
175 case GDK_SCROLL:
176 nType = VclInputFlags::MOUSE;
177 break;
178 case GDK_KEY_PRESS:
179 // case GDK_KEY_RELEASE: //similar to the X11SalInstance one
180 nType = VclInputFlags::KEYBOARD;
181 break;
182 case GDK_EXPOSE:
183 nType = VclInputFlags::PAINT;
184 break;
185 default:
186 nType = VclInputFlags::OTHER;
187 break;
189 return nType;
192 GtkInstance::GtkInstance( std::unique_ptr<SalYieldMutex> pMutex )
193 : SvpSalInstance( std::move(pMutex) )
194 , m_pTimer(nullptr)
195 , bNeedsInit(true)
196 , m_pLastCairoFontOptions(nullptr)
200 //We want to defer initializing gtk until we are after uno has been
201 //bootstrapped so we can ask the config what the UI language is so that we can
202 //force that in as $LANGUAGE to get gtk to render widgets RTL if we have a RTL
203 //UI in a LTR locale
204 void GtkInstance::AfterAppInit()
206 EnsureInit();
209 void GtkInstance::EnsureInit()
211 if (!bNeedsInit)
212 return;
213 // initialize SalData
214 GtkSalData *pSalData = GetGtkSalData();
215 pSalData->Init();
216 GtkSalData::initNWF();
218 InitAtkBridge();
220 ImplSVData* pSVData = ImplGetSVData();
221 #ifdef GTK_TOOLKIT_NAME
222 pSVData->maAppData.mxToolkitName = OUString(GTK_TOOLKIT_NAME);
223 #else
224 pSVData->maAppData.mxToolkitName = OUString("gtk3");
225 #endif
227 bNeedsInit = false;
230 GtkInstance::~GtkInstance()
232 assert( nullptr == m_pTimer );
233 DeInitAtkBridge();
234 ResetLastSeenCairoFontOptions(nullptr);
237 SalFrame* GtkInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
239 EnsureInit();
240 return new GtkSalFrame( pParent, nStyle );
243 SalFrame* GtkInstance::CreateChildFrame( SystemParentData* pParentData, SalFrameStyleFlags )
245 EnsureInit();
246 return new GtkSalFrame( pParentData );
249 SalObject* GtkInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow )
251 EnsureInit();
252 //FIXME: Missing CreateObject functionality ...
253 if (pWindowData && pWindowData->bClipUsingNativeWidget)
254 return new GtkSalObjectWidgetClip(static_cast<GtkSalFrame*>(pParent), bShow);
255 return new GtkSalObject(static_cast<GtkSalFrame*>(pParent), bShow);
258 extern "C"
260 typedef void*(* getDefaultFnc)();
261 typedef void(* addItemFnc)(void *, const char *);
264 void GtkInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString&, const OUString&)
266 EnsureInit();
267 OString sGtkURL;
268 rtl_TextEncoding aSystemEnc = osl_getThreadTextEncoding();
269 if ((aSystemEnc == RTL_TEXTENCODING_UTF8) || !rFileUrl.startsWith( "file://" ))
270 sGtkURL = OUStringToOString(rFileUrl, RTL_TEXTENCODING_UTF8);
271 else
273 //Non-utf8 locales are a bad idea if trying to work with non-ascii filenames
274 //Decode %XX components
275 OUString sDecodedUri = rtl::Uri::decode(rFileUrl.copy(7), rtl_UriDecodeToIuri, RTL_TEXTENCODING_UTF8);
276 //Convert back to system locale encoding
277 OString sSystemUrl = OUStringToOString(sDecodedUri, aSystemEnc);
278 //Encode to an escaped ASCII-encoded URI
279 gchar *g_uri = g_filename_to_uri(sSystemUrl.getStr(), nullptr, nullptr);
280 sGtkURL = OString(g_uri);
281 g_free(g_uri);
283 GtkRecentManager *manager = gtk_recent_manager_get_default ();
284 gtk_recent_manager_add_item (manager, sGtkURL.getStr());
287 SalInfoPrinter* GtkInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
288 ImplJobSetup* pSetupData )
290 EnsureInit();
291 mbPrinterInit = true;
292 // create and initialize SalInfoPrinter
293 PspSalInfoPrinter* pPrinter = new GtkSalInfoPrinter;
294 configurePspInfoPrinter(pPrinter, pQueueInfo, pSetupData);
295 return pPrinter;
298 std::unique_ptr<SalPrinter> GtkInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
300 EnsureInit();
301 mbPrinterInit = true;
302 return std::unique_ptr<SalPrinter>(new GtkSalPrinter( pInfoPrinter ));
306 * These methods always occur in pairs
307 * A ThreadsEnter is followed by a ThreadsLeave
308 * We need to queue up the recursive lock count
309 * for each pair, so we can accurately restore
310 * it later.
312 thread_local std::stack<sal_uInt32> GtkYieldMutex::yieldCounts;
314 void GtkYieldMutex::ThreadsEnter()
316 acquire();
317 if (!yieldCounts.empty()) {
318 auto n = yieldCounts.top();
319 yieldCounts.pop();
320 assert(n > 0);
321 n--;
322 if (n > 0)
323 acquire(n);
327 void GtkYieldMutex::ThreadsLeave()
329 assert(m_nCount != 0);
330 yieldCounts.push(m_nCount);
331 release(true);
334 std::unique_ptr<SalVirtualDevice> GtkInstance::CreateVirtualDevice( SalGraphics *pG,
335 tools::Long &nDX, tools::Long &nDY,
336 DeviceFormat eFormat,
337 const SystemGraphicsData* pGd )
339 EnsureInit();
340 SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(pG);
341 assert(pSvpSalGraphics);
342 // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget
343 cairo_surface_t* pPreExistingTarget = pGd ? static_cast<cairo_surface_t*>(pGd->pSurface) : nullptr;
344 std::unique_ptr<SalVirtualDevice> pNew(new SvpSalVirtualDevice(eFormat, pSvpSalGraphics->getSurface(), pPreExistingTarget));
345 pNew->SetSize( nDX, nDY );
346 return pNew;
349 std::shared_ptr<SalBitmap> GtkInstance::CreateSalBitmap()
351 EnsureInit();
352 return SvpSalInstance::CreateSalBitmap();
355 std::unique_ptr<SalMenu> GtkInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
357 EnsureInit();
358 GtkSalMenu* pSalMenu = new GtkSalMenu( bMenuBar );
359 pSalMenu->SetMenu( pVCLMenu );
360 return std::unique_ptr<SalMenu>(pSalMenu);
363 std::unique_ptr<SalMenuItem> GtkInstance::CreateMenuItem( const SalItemParams & rItemData )
365 EnsureInit();
366 return std::unique_ptr<SalMenuItem>(new GtkSalMenuItem( &rItemData ));
369 SalTimer* GtkInstance::CreateSalTimer()
371 EnsureInit();
372 assert( nullptr == m_pTimer );
373 if ( nullptr == m_pTimer )
374 m_pTimer = new GtkSalTimer();
375 return m_pTimer;
378 void GtkInstance::RemoveTimer ()
380 EnsureInit();
381 m_pTimer = nullptr;
384 bool GtkInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
386 EnsureInit();
387 return GetGtkSalData()->Yield( bWait, bHandleAllCurrentEvents );
390 bool GtkInstance::IsTimerExpired()
392 EnsureInit();
393 return (m_pTimer && m_pTimer->Expired());
396 bool GtkInstance::AnyInput( VclInputFlags nType )
398 EnsureInit();
399 if( (nType & VclInputFlags::TIMER) && IsTimerExpired() )
400 return true;
401 if (!gdk_events_pending())
402 return false;
404 if (nType == VCL_INPUT_ANY)
405 return true;
407 bool bRet = false;
408 std::deque<GdkEvent*> aEvents;
409 GdkEvent *pEvent = nullptr;
410 while ((pEvent = gdk_event_get()))
412 aEvents.push_back(pEvent);
413 VclInputFlags nEventType = categorizeEvent(pEvent);
414 if ( (nEventType & nType) || ( nEventType == VclInputFlags::NONE && (nType & VclInputFlags::OTHER) ) )
416 bRet = true;
420 while (!aEvents.empty())
422 pEvent = aEvents.front();
423 gdk_event_put(pEvent);
424 gdk_event_free(pEvent);
425 aEvents.pop_front();
427 return bRet;
430 std::unique_ptr<GenPspGraphics> GtkInstance::CreatePrintGraphics()
432 EnsureInit();
433 return std::make_unique<GenPspGraphics>();
436 std::shared_ptr<vcl::unx::GtkPrintWrapper> const &
437 GtkInstance::getPrintWrapper() const
439 if (!m_xPrintWrapper)
440 m_xPrintWrapper = std::make_shared<vcl::unx::GtkPrintWrapper>();
441 return m_xPrintWrapper;
444 const cairo_font_options_t* GtkInstance::GetCairoFontOptions()
446 const cairo_font_options_t* pCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
447 if (!m_pLastCairoFontOptions && pCairoFontOptions)
448 m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
449 return pCairoFontOptions;
452 const cairo_font_options_t* GtkInstance::GetLastSeenCairoFontOptions() const
454 return m_pLastCairoFontOptions;
457 void GtkInstance::ResetLastSeenCairoFontOptions(const cairo_font_options_t* pCairoFontOptions)
459 if (m_pLastCairoFontOptions)
460 cairo_font_options_destroy(m_pLastCairoFontOptions);
461 if (pCairoFontOptions)
462 m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
463 else
464 m_pLastCairoFontOptions = nullptr;
468 namespace
470 struct TypeEntry
472 const char* pNativeType; // string corresponding to nAtom for the case of nAtom being uninitialized
473 const char* pType; // Mime encoding on our side
476 const TypeEntry aConversionTab[] =
478 { "ISO10646-1", "text/plain;charset=utf-16" },
479 { "UTF8_STRING", "text/plain;charset=utf-8" },
480 { "UTF-8", "text/plain;charset=utf-8" },
481 { "text/plain;charset=UTF-8", "text/plain;charset=utf-8" },
482 // ISO encodings
483 { "ISO8859-2", "text/plain;charset=iso8859-2" },
484 { "ISO8859-3", "text/plain;charset=iso8859-3" },
485 { "ISO8859-4", "text/plain;charset=iso8859-4" },
486 { "ISO8859-5", "text/plain;charset=iso8859-5" },
487 { "ISO8859-6", "text/plain;charset=iso8859-6" },
488 { "ISO8859-7", "text/plain;charset=iso8859-7" },
489 { "ISO8859-8", "text/plain;charset=iso8859-8" },
490 { "ISO8859-9", "text/plain;charset=iso8859-9" },
491 { "ISO8859-10", "text/plain;charset=iso8859-10" },
492 { "ISO8859-13", "text/plain;charset=iso8859-13" },
493 { "ISO8859-14", "text/plain;charset=iso8859-14" },
494 { "ISO8859-15", "text/plain;charset=iso8859-15" },
495 // asian encodings
496 { "JISX0201.1976-0", "text/plain;charset=jisx0201.1976-0" },
497 { "JISX0208.1983-0", "text/plain;charset=jisx0208.1983-0" },
498 { "JISX0208.1990-0", "text/plain;charset=jisx0208.1990-0" },
499 { "JISX0212.1990-0", "text/plain;charset=jisx0212.1990-0" },
500 { "GB2312.1980-0", "text/plain;charset=gb2312.1980-0" },
501 { "KSC5601.1992-0", "text/plain;charset=ksc5601.1992-0" },
502 // eastern european encodings
503 { "KOI8-R", "text/plain;charset=koi8-r" },
504 { "KOI8-U", "text/plain;charset=koi8-u" },
505 // String (== iso8859-1)
506 { "STRING", "text/plain;charset=iso8859-1" },
507 // special for compound text
508 { "COMPOUND_TEXT", "text/plain;charset=compound_text" },
510 // PIXMAP
511 { "PIXMAP", "image/bmp" }
514 class DataFlavorEq
516 private:
517 const css::datatransfer::DataFlavor& m_rData;
518 public:
519 explicit DataFlavorEq(const css::datatransfer::DataFlavor& rData) : m_rData(rData) {}
520 bool operator() (const css::datatransfer::DataFlavor& rData) const
522 return rData.MimeType == m_rData.MimeType &&
523 rData.DataType == m_rData.DataType;
528 std::vector<css::datatransfer::DataFlavor> GtkTransferable::getTransferDataFlavorsAsVector(GdkAtom *targets, gint n_targets)
530 std::vector<css::datatransfer::DataFlavor> aVector;
532 bool bHaveText = false, bHaveUTF16 = false;
534 for (gint i = 0; i < n_targets; ++i)
536 gchar* pName = gdk_atom_name(targets[i]);
537 const char* pFinalName = pName;
538 css::datatransfer::DataFlavor aFlavor;
540 // omit text/plain;charset=unicode since it is not well defined
541 if (rtl_str_compare(pName, "text/plain;charset=unicode") == 0)
543 g_free(pName);
544 continue;
547 for (size_t j = 0; j < SAL_N_ELEMENTS(aConversionTab); ++j)
549 if (rtl_str_compare(pName, aConversionTab[j].pNativeType) == 0)
551 pFinalName = aConversionTab[j].pType;
552 break;
556 // There are more non-MIME-types reported that are not translated by
557 // aConversionTab, like "SAVE_TARGETS", "INTEGER", "ATOM"; just filter
558 // them out for now before they confuse this code's clients:
559 if (rtl_str_indexOfChar(pFinalName, '/') == -1)
561 g_free(pName);
562 continue;
565 aFlavor.MimeType = OUString(pFinalName,
566 strlen(pFinalName),
567 RTL_TEXTENCODING_UTF8);
569 m_aMimeTypeToAtom[aFlavor.MimeType] = targets[i];
571 aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
573 sal_Int32 nIndex(0);
574 if (aFlavor.MimeType.getToken(0, ';', nIndex) == "text/plain")
576 bHaveText = true;
577 OUString aToken(aFlavor.MimeType.getToken(0, ';', nIndex));
578 if (aToken == "charset=utf-16")
580 bHaveUTF16 = true;
581 aFlavor.DataType = cppu::UnoType<OUString>::get();
584 aVector.push_back(aFlavor);
585 g_free(pName);
588 //If we have text, but no UTF-16 format which is basically the only
589 //text-format LibreOffice supports for cnp then claim we do and we
590 //will convert on demand
591 if (bHaveText && !bHaveUTF16)
593 css::datatransfer::DataFlavor aFlavor;
594 aFlavor.MimeType = "text/plain;charset=utf-16";
595 aFlavor.DataType = cppu::UnoType<OUString>::get();
596 aVector.push_back(aFlavor);
599 return aVector;
603 css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL GtkTransferable::getTransferDataFlavors()
605 return comphelper::containerToSequence(getTransferDataFlavorsAsVector());
608 sal_Bool SAL_CALL GtkTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
610 const std::vector<css::datatransfer::DataFlavor> aAll =
611 getTransferDataFlavorsAsVector();
613 return std::any_of(aAll.begin(), aAll.end(), DataFlavorEq(rFlavor));
616 namespace {
618 class GtkClipboardTransferable : public GtkTransferable
620 private:
621 GdkAtom m_nSelection;
622 public:
624 explicit GtkClipboardTransferable(GdkAtom nSelection)
625 : m_nSelection(nSelection)
630 * XTransferable
633 virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override
635 GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
636 if (rFlavor.MimeType == "text/plain;charset=utf-16")
638 OUString aStr;
639 gchar *pText = gtk_clipboard_wait_for_text(clipboard);
640 if (pText)
641 aStr = OUString(pText, strlen(pText), RTL_TEXTENCODING_UTF8);
642 g_free(pText);
643 css::uno::Any aRet;
644 aRet <<= aStr.replaceAll("\r\n", "\n");
645 return aRet;
648 auto it = m_aMimeTypeToAtom.find(rFlavor.MimeType);
649 if (it == m_aMimeTypeToAtom.end())
650 return css::uno::Any();
652 css::uno::Any aRet;
653 GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard,
654 it->second);
655 if (!data)
657 return css::uno::Any();
659 gint length;
660 const guchar *rawdata = gtk_selection_data_get_data_with_length(data,
661 &length);
662 Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
663 gtk_selection_data_free(data);
664 aRet <<= aSeq;
665 return aRet;
668 std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector()
669 override
671 std::vector<css::datatransfer::DataFlavor> aVector;
673 GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
675 GdkAtom *targets;
676 gint n_targets;
677 if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
679 aVector = GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
680 g_free(targets);
683 return aVector;
687 class VclGtkClipboard :
688 public cppu::WeakComponentImplHelper<
689 datatransfer::clipboard::XSystemClipboard,
690 datatransfer::clipboard::XFlushableClipboard,
691 XServiceInfo>
693 GdkAtom m_nSelection;
694 osl::Mutex m_aMutex;
695 gulong m_nOwnerChangedSignalId;
696 ImplSVEvent* m_pSetClipboardEvent;
697 Reference<css::datatransfer::XTransferable> m_aContents;
698 Reference<css::datatransfer::clipboard::XClipboardOwner> m_aOwner;
699 std::vector< Reference<css::datatransfer::clipboard::XClipboardListener> > m_aListeners;
700 std::vector<GtkTargetEntry> m_aGtkTargets;
701 VclToGtkHelper m_aConversionHelper;
703 DECL_LINK(AsyncSetGtkClipboard, void*, void);
704 public:
706 explicit VclGtkClipboard(GdkAtom nSelection);
707 virtual ~VclGtkClipboard() override;
710 * XServiceInfo
713 virtual OUString SAL_CALL getImplementationName() override;
714 virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
715 virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
718 * XClipboard
721 virtual Reference< css::datatransfer::XTransferable > SAL_CALL getContents() override;
723 virtual void SAL_CALL setContents(
724 const Reference< css::datatransfer::XTransferable >& xTrans,
725 const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner ) override;
727 virtual OUString SAL_CALL getName() override;
730 * XClipboardEx
733 virtual sal_Int8 SAL_CALL getRenderingCapabilities() override;
736 * XFlushableClipboard
738 virtual void SAL_CALL flushClipboard() override;
741 * XClipboardNotifier
743 virtual void SAL_CALL addClipboardListener(
744 const Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
746 virtual void SAL_CALL removeClipboardListener(
747 const Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
749 void ClipboardGet(GtkSelectionData *selection_data, guint info);
750 void ClipboardClear();
751 void OwnerPossiblyChanged(GtkClipboard *clipboard);
752 void SetGtkClipboard();
753 void SyncGtkClipboard();
758 OUString VclGtkClipboard::getImplementationName()
760 return "com.sun.star.datatransfer.VclGtkClipboard";
763 Sequence< OUString > VclGtkClipboard::getSupportedServiceNames()
765 Sequence<OUString> aRet { "com.sun.star.datatransfer.clipboard.SystemClipboard" };
766 return aRet;
769 sal_Bool VclGtkClipboard::supportsService( const OUString& ServiceName )
771 return cppu::supportsService(this, ServiceName);
774 Reference< css::datatransfer::XTransferable > VclGtkClipboard::getContents()
776 if (!m_aContents.is())
778 //tdf#93887 This is the system clipboard/selection. We fetch it when we are not
779 //the owner of the clipboard and have not already fetched it.
780 m_aContents = new GtkClipboardTransferable(m_nSelection);
783 return m_aContents;
786 void VclGtkClipboard::ClipboardGet(GtkSelectionData *selection_data, guint info)
788 if (!m_aContents.is())
789 return;
790 // tdf#129809 take a reference in case m_aContents is replaced during this
791 // call
792 Reference<datatransfer::XTransferable> xCurrentContents(m_aContents);
793 m_aConversionHelper.setSelectionData(xCurrentContents, selection_data, info);
796 namespace
798 const OString& getPID()
800 static OString sPID;
801 if (!sPID.getLength())
803 oslProcessIdentifier aProcessId = 0;
804 oslProcessInfo info;
805 info.Size = sizeof (oslProcessInfo);
806 if (osl_getProcessInfo(nullptr, osl_Process_IDENTIFIER, &info) == osl_Process_E_None)
807 aProcessId = info.Ident;
808 sPID = OString::number(aProcessId);
810 return sPID;
814 namespace
816 void ClipboardGetFunc(GtkClipboard* /*clipboard*/, GtkSelectionData *selection_data,
817 guint info,
818 gpointer user_data_or_owner)
820 VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
821 pThis->ClipboardGet(selection_data, info);
824 void ClipboardClearFunc(GtkClipboard* /*clipboard*/, gpointer user_data_or_owner)
826 VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
827 pThis->ClipboardClear();
830 void handle_owner_change(GtkClipboard *clipboard, GdkEvent* /*event*/, gpointer user_data)
832 VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data);
833 pThis->OwnerPossiblyChanged(clipboard);
837 void VclGtkClipboard::OwnerPossiblyChanged(GtkClipboard* clipboard)
839 SyncGtkClipboard(); // tdf#138183 do any pending SetGtkClipboard calls
840 if (!m_aContents.is())
841 return;
843 //if gdk_display_supports_selection_notification is not supported, e.g. like
844 //right now under wayland, then you only get owner-changed notifications at
845 //opportune times when the selection might have changed. So here
846 //we see if the selection supports a dummy selection type identifying
847 //our pid, in which case it's us.
848 bool bSelf = false;
850 //disconnect and reconnect after gtk_clipboard_wait_for_targets to
851 //avoid possible recursion
852 g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
854 OString sTunnel = "application/x-libreoffice-internal-id-" + getPID();
855 GdkAtom *targets;
856 gint n_targets;
857 if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
859 for (gint i = 0; i < n_targets && !bSelf; ++i)
861 gchar* pName = gdk_atom_name(targets[i]);
862 if (strcmp(pName, sTunnel.getStr()) == 0)
864 bSelf = true;
866 g_free(pName);
869 g_free(targets);
872 m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
873 G_CALLBACK(handle_owner_change), this);
875 if (!bSelf)
877 //null out m_aContents to return control to the system-one which
878 //will be retrieved if getContents is called again
879 setContents(Reference<css::datatransfer::XTransferable>(),
880 Reference<css::datatransfer::clipboard::XClipboardOwner>());
884 void VclGtkClipboard::ClipboardClear()
886 if (m_pSetClipboardEvent)
888 Application::RemoveUserEvent(m_pSetClipboardEvent);
889 m_pSetClipboardEvent = nullptr;
891 for (auto &a : m_aGtkTargets)
892 g_free(a.target);
893 m_aGtkTargets.clear();
896 GtkTargetEntry VclToGtkHelper::makeGtkTargetEntry(const css::datatransfer::DataFlavor& rFlavor)
898 GtkTargetEntry aEntry;
899 aEntry.target =
900 g_strdup(OUStringToOString(rFlavor.MimeType, RTL_TEXTENCODING_UTF8).getStr());
901 aEntry.flags = 0;
902 auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
903 DataFlavorEq(rFlavor));
904 if (it != aInfoToFlavor.end())
905 aEntry.info = std::distance(aInfoToFlavor.begin(), it);
906 else
908 aEntry.info = aInfoToFlavor.size();
909 aInfoToFlavor.push_back(rFlavor);
911 return aEntry;
914 void VclToGtkHelper::setSelectionData(const Reference<css::datatransfer::XTransferable> &rTrans,
915 GtkSelectionData *selection_data, guint info)
917 GdkAtom type(gdk_atom_intern(OUStringToOString(aInfoToFlavor[info].MimeType,
918 RTL_TEXTENCODING_UTF8).getStr(),
919 false));
921 css::datatransfer::DataFlavor aFlavor(aInfoToFlavor[info]);
922 if (aFlavor.MimeType == "UTF8_STRING" || aFlavor.MimeType == "STRING")
923 aFlavor.MimeType = "text/plain;charset=utf-8";
925 Sequence<sal_Int8> aData;
926 Any aValue;
930 aValue = rTrans->getTransferData(aFlavor);
932 catch (...)
936 if (aValue.getValueTypeClass() == TypeClass_STRING)
938 OUString aString;
939 aValue >>= aString;
940 aData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aString.getStr()), aString.getLength() * sizeof( sal_Unicode ) );
942 else if (aValue.getValueType() == cppu::UnoType<Sequence< sal_Int8 >>::get())
944 aValue >>= aData;
946 else if (aFlavor.MimeType == "text/plain;charset=utf-8")
948 //didn't have utf-8, try utf-16 and convert
949 aFlavor.MimeType = "text/plain;charset=utf-16";
950 aFlavor.DataType = cppu::UnoType<OUString>::get();
953 aValue = rTrans->getTransferData(aFlavor);
955 catch (...)
958 OUString aString;
959 aValue >>= aString;
960 OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
961 gtk_selection_data_set(selection_data, type, 8,
962 reinterpret_cast<const guchar *>(aUTF8String.getStr()),
963 aUTF8String.getLength());
964 return;
967 gtk_selection_data_set(selection_data, type, 8,
968 reinterpret_cast<const guchar *>(aData.getArray()),
969 aData.getLength());
972 VclGtkClipboard::VclGtkClipboard(GdkAtom nSelection)
973 : cppu::WeakComponentImplHelper<datatransfer::clipboard::XSystemClipboard,
974 datatransfer::clipboard::XFlushableClipboard, XServiceInfo>
975 (m_aMutex)
976 , m_nSelection(nSelection)
977 , m_pSetClipboardEvent(nullptr)
979 GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
980 m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
981 G_CALLBACK(handle_owner_change), this);
984 void VclGtkClipboard::flushClipboard()
986 SolarMutexGuard aGuard;
988 if (GDK_SELECTION_CLIPBOARD != m_nSelection)
989 return;
991 GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
992 gtk_clipboard_store(clipboard);
995 VclGtkClipboard::~VclGtkClipboard()
997 GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
998 g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
999 if (!m_aGtkTargets.empty())
1001 gtk_clipboard_clear(clipboard);
1002 ClipboardClear();
1004 assert(!m_pSetClipboardEvent);
1005 assert(m_aGtkTargets.empty());
1008 std::vector<GtkTargetEntry> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
1010 std::vector<GtkTargetEntry> aGtkTargets;
1012 bool bHaveText(false), bHaveUTF8(false);
1013 for (const css::datatransfer::DataFlavor& rFlavor : rFormats)
1015 sal_Int32 nIndex(0);
1016 if (rFlavor.MimeType.getToken(0, ';', nIndex) == "text/plain")
1018 bHaveText = true;
1019 OUString aToken(rFlavor.MimeType.getToken(0, ';', nIndex));
1020 if (aToken == "charset=utf-8")
1022 bHaveUTF8 = true;
1025 GtkTargetEntry aEntry(makeGtkTargetEntry(rFlavor));
1026 aGtkTargets.push_back(aEntry);
1029 if (bHaveText)
1031 css::datatransfer::DataFlavor aFlavor;
1032 aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
1033 if (!bHaveUTF8)
1035 aFlavor.MimeType = "text/plain;charset=utf-8";
1036 aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
1038 aFlavor.MimeType = "UTF8_STRING";
1039 aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
1040 aFlavor.MimeType = "STRING";
1041 aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
1044 return aGtkTargets;
1047 IMPL_LINK_NOARG(VclGtkClipboard, AsyncSetGtkClipboard, void*, void)
1049 osl::ClearableMutexGuard aGuard( m_aMutex );
1050 m_pSetClipboardEvent = nullptr;
1051 SetGtkClipboard();
1054 void VclGtkClipboard::SyncGtkClipboard()
1056 osl::ClearableMutexGuard aGuard(m_aMutex);
1057 if (m_pSetClipboardEvent)
1059 Application::RemoveUserEvent(m_pSetClipboardEvent);
1060 m_pSetClipboardEvent = nullptr;
1061 SetGtkClipboard();
1065 void VclGtkClipboard::SetGtkClipboard()
1067 GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
1068 gtk_clipboard_set_with_data(clipboard, m_aGtkTargets.data(), m_aGtkTargets.size(),
1069 ClipboardGetFunc, ClipboardClearFunc, this);
1070 gtk_clipboard_set_can_store(clipboard, m_aGtkTargets.data(), m_aGtkTargets.size());
1073 void VclGtkClipboard::setContents(
1074 const Reference< css::datatransfer::XTransferable >& xTrans,
1075 const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner )
1077 css::uno::Sequence<css::datatransfer::DataFlavor> aFormats;
1078 if (xTrans.is())
1080 aFormats = xTrans->getTransferDataFlavors();
1083 osl::ClearableMutexGuard aGuard( m_aMutex );
1084 Reference< datatransfer::clipboard::XClipboardOwner > xOldOwner( m_aOwner );
1085 Reference< datatransfer::XTransferable > xOldContents( m_aContents );
1086 m_aContents = xTrans;
1087 m_aOwner = xClipboardOwner;
1089 std::vector< Reference< datatransfer::clipboard::XClipboardListener > > aListeners( m_aListeners );
1090 datatransfer::clipboard::ClipboardEvent aEv;
1092 GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
1093 if (!m_aGtkTargets.empty())
1095 gtk_clipboard_clear(clipboard);
1096 ClipboardClear();
1098 assert(m_aGtkTargets.empty());
1099 if (m_aContents.is())
1101 std::vector<GtkTargetEntry> aGtkTargets(m_aConversionHelper.FormatsToGtk(aFormats));
1102 if (!aGtkTargets.empty())
1104 GtkTargetEntry aEntry;
1105 OString sTunnel = "application/x-libreoffice-internal-id-" + getPID();
1106 aEntry.target = g_strdup(sTunnel.getStr());
1107 aEntry.flags = 0;
1108 aEntry.info = 0;
1109 aGtkTargets.push_back(aEntry);
1111 m_aGtkTargets = aGtkTargets;
1113 if (!m_pSetClipboardEvent)
1114 m_pSetClipboardEvent = Application::PostUserEvent(LINK(this, VclGtkClipboard, AsyncSetGtkClipboard));
1118 aEv.Contents = getContents();
1120 aGuard.clear();
1122 if (xOldOwner.is() && xOldOwner != xClipboardOwner)
1123 xOldOwner->lostOwnership( this, xOldContents );
1124 for (auto const& listener : aListeners)
1126 listener->changedContents( aEv );
1130 OUString VclGtkClipboard::getName()
1132 return (m_nSelection == GDK_SELECTION_CLIPBOARD) ? OUString("CLIPBOARD") : OUString("PRIMARY");
1135 sal_Int8 VclGtkClipboard::getRenderingCapabilities()
1137 return 0;
1140 void VclGtkClipboard::addClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
1142 osl::ClearableMutexGuard aGuard( m_aMutex );
1144 m_aListeners.push_back( listener );
1147 void VclGtkClipboard::removeClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
1149 osl::ClearableMutexGuard aGuard( m_aMutex );
1151 m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), listener), m_aListeners.end());
1154 Reference< XInterface > GtkInstance::CreateClipboard(const Sequence< Any >& arguments)
1156 OUString sel;
1157 if (!arguments.hasElements()) {
1158 sel = "CLIPBOARD";
1159 } else if (arguments.getLength() != 1 || !(arguments[0] >>= sel)) {
1160 throw css::lang::IllegalArgumentException(
1161 "bad GtkInstance::CreateClipboard arguments",
1162 css::uno::Reference<css::uno::XInterface>(), -1);
1165 GdkAtom nSelection = (sel == "CLIPBOARD") ? GDK_SELECTION_CLIPBOARD : GDK_SELECTION_PRIMARY;
1167 auto it = m_aClipboards.find(nSelection);
1168 if (it != m_aClipboards.end())
1169 return it->second;
1171 Reference<XInterface> xClipboard(static_cast<cppu::OWeakObject *>(new VclGtkClipboard(nSelection)));
1172 m_aClipboards[nSelection] = xClipboard;
1174 return xClipboard;
1177 GtkDropTarget::GtkDropTarget()
1178 : WeakComponentImplHelper(m_aMutex)
1179 , m_pFrame(nullptr)
1180 , m_pFormatConversionRequest(nullptr)
1181 , m_bActive(false)
1182 , m_bInDrag(false)
1183 , m_nDefaultActions(0)
1187 OUString SAL_CALL GtkDropTarget::getImplementationName()
1189 return "com.sun.star.datatransfer.dnd.VclGtkDropTarget";
1192 sal_Bool SAL_CALL GtkDropTarget::supportsService(OUString const & ServiceName)
1194 return cppu::supportsService(this, ServiceName);
1197 css::uno::Sequence<OUString> SAL_CALL GtkDropTarget::getSupportedServiceNames()
1199 Sequence<OUString> aRet { "com.sun.star.datatransfer.dnd.GtkDropTarget" };
1200 return aRet;
1203 GtkDropTarget::~GtkDropTarget()
1205 if (m_pFrame)
1206 m_pFrame->deregisterDropTarget(this);
1209 void GtkDropTarget::deinitialize()
1211 m_pFrame = nullptr;
1212 m_bActive = false;
1215 void GtkDropTarget::initialize(const Sequence<Any>& rArguments)
1217 if (rArguments.getLength() < 2)
1219 throw RuntimeException("DropTarget::initialize: Cannot install window event handler",
1220 static_cast<OWeakObject*>(this));
1223 sal_IntPtr nFrame = 0;
1224 rArguments.getConstArray()[1] >>= nFrame;
1226 if (!nFrame)
1228 throw RuntimeException("DropTarget::initialize: missing SalFrame",
1229 static_cast<OWeakObject*>(this));
1232 m_pFrame = reinterpret_cast<GtkSalFrame*>(nFrame);
1233 m_pFrame->registerDropTarget(this);
1234 m_bActive = true;
1237 void GtkDropTarget::addDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
1239 ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
1241 m_aListeners.push_back( xListener );
1244 void GtkDropTarget::removeDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
1246 ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
1248 m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), xListener), m_aListeners.end());
1251 void GtkDropTarget::fire_drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde)
1253 osl::ClearableGuard<osl::Mutex> aGuard( m_aMutex );
1254 std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1255 aGuard.clear();
1257 for (auto const& listener : aListeners)
1259 listener->drop( dtde );
1263 void GtkDropTarget::fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde)
1265 osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
1266 std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1267 aGuard.clear();
1269 for (auto const& listener : aListeners)
1271 listener->dragEnter( dtde );
1275 void GtkDropTarget::fire_dragOver(const css::datatransfer::dnd::DropTargetDragEvent& dtde)
1277 osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
1278 std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1279 aGuard.clear();
1281 for (auto const& listener : aListeners)
1283 listener->dragOver( dtde );
1287 void GtkDropTarget::fire_dragExit(const css::datatransfer::dnd::DropTargetEvent& dte)
1289 osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
1290 std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
1291 aGuard.clear();
1293 for (auto const& listener : aListeners)
1295 listener->dragExit( dte );
1299 sal_Bool GtkDropTarget::isActive()
1301 return m_bActive;
1304 void GtkDropTarget::setActive(sal_Bool bActive)
1306 m_bActive = bActive;
1309 sal_Int8 GtkDropTarget::getDefaultActions()
1311 return m_nDefaultActions;
1314 void GtkDropTarget::setDefaultActions(sal_Int8 nDefaultActions)
1316 m_nDefaultActions = nDefaultActions;
1319 Reference< XInterface > GtkInstance::CreateDropTarget()
1321 return Reference<XInterface>(static_cast<cppu::OWeakObject*>(new GtkDropTarget));
1324 GtkDragSource::~GtkDragSource()
1326 if (m_pFrame)
1327 m_pFrame->deregisterDragSource(this);
1329 if (GtkDragSource::g_ActiveDragSource == this)
1331 SAL_WARN( "vcl.gtk", "dragEnd should have been called on GtkDragSource before dtor");
1332 GtkDragSource::g_ActiveDragSource = nullptr;
1336 void GtkDragSource::deinitialize()
1338 m_pFrame = nullptr;
1341 sal_Bool GtkDragSource::isDragImageSupported()
1343 return true;
1346 sal_Int32 GtkDragSource::getDefaultCursor( sal_Int8 )
1348 return 0;
1351 void GtkDragSource::initialize(const css::uno::Sequence<css::uno::Any >& rArguments)
1353 if (rArguments.getLength() < 2)
1355 throw RuntimeException("DragSource::initialize: Cannot install window event handler",
1356 static_cast<OWeakObject*>(this));
1359 sal_IntPtr nFrame = 0;
1360 rArguments.getConstArray()[1] >>= nFrame;
1362 if (!nFrame)
1364 throw RuntimeException("DragSource::initialize: missing SalFrame",
1365 static_cast<OWeakObject*>(this));
1368 m_pFrame = reinterpret_cast<GtkSalFrame*>(nFrame);
1369 m_pFrame->registerDragSource(this);
1372 OUString SAL_CALL GtkDragSource::getImplementationName()
1374 return "com.sun.star.datatransfer.dnd.VclGtkDragSource";
1377 sal_Bool SAL_CALL GtkDragSource::supportsService(OUString const & ServiceName)
1379 return cppu::supportsService(this, ServiceName);
1382 css::uno::Sequence<OUString> SAL_CALL GtkDragSource::getSupportedServiceNames()
1384 Sequence<OUString> aRet { "com.sun.star.datatransfer.dnd.GtkDragSource" };
1385 return aRet;
1388 Reference< XInterface > GtkInstance::CreateDragSource()
1390 return Reference< XInterface >( static_cast<cppu::OWeakObject *>(new GtkDragSource()) );
1393 namespace {
1395 class GtkOpenGLContext : public OpenGLContext
1397 GLWindow m_aGLWin;
1398 GtkWidget *m_pGLArea;
1399 GdkGLContext *m_pContext;
1400 guint m_nAreaFrameBuffer;
1401 guint m_nFrameBuffer;
1402 guint m_nRenderBuffer;
1403 guint m_nDepthBuffer;
1404 guint m_nFrameScratchBuffer;
1405 guint m_nRenderScratchBuffer;
1406 guint m_nDepthScratchBuffer;
1408 public:
1409 GtkOpenGLContext()
1410 : OpenGLContext()
1411 , m_pGLArea(nullptr)
1412 , m_pContext(nullptr)
1413 , m_nAreaFrameBuffer(0)
1414 , m_nFrameBuffer(0)
1415 , m_nRenderBuffer(0)
1416 , m_nDepthBuffer(0)
1417 , m_nFrameScratchBuffer(0)
1418 , m_nRenderScratchBuffer(0)
1419 , m_nDepthScratchBuffer(0)
1423 virtual void initWindow() override
1425 if( !m_pChildWindow )
1427 SystemWindowData winData = generateWinData(mpWindow, mbRequestLegacyContext);
1428 m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
1431 if (m_pChildWindow)
1433 InitChildWindow(m_pChildWindow.get());
1437 private:
1438 virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
1439 virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }
1441 static void signalDestroy(GtkWidget*, gpointer context)
1443 GtkOpenGLContext* pThis = static_cast<GtkOpenGLContext*>(context);
1444 pThis->m_pGLArea = nullptr;
1447 static gboolean signalRender(GtkGLArea*, GdkGLContext*, gpointer window)
1449 GtkOpenGLContext* pThis = static_cast<GtkOpenGLContext*>(window);
1451 int scale = gtk_widget_get_scale_factor(pThis->m_pGLArea);
1452 int width = pThis->m_aGLWin.Width * scale;
1453 int height = pThis->m_aGLWin.Height * scale;
1455 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
1457 glBindFramebuffer(GL_READ_FRAMEBUFFER, pThis->m_nAreaFrameBuffer);
1458 glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
1460 glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
1461 GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
1463 gdk_gl_context_make_current(pThis->m_pContext);
1464 return true;
1467 virtual void adjustToNewSize() override
1469 if (!m_pGLArea)
1470 return;
1472 int scale = gtk_widget_get_scale_factor(m_pGLArea);
1473 int width = m_aGLWin.Width * scale;
1474 int height = m_aGLWin.Height * scale;
1476 // seen in tdf#124729 width/height of 0 leading to GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
1477 int allocwidth = std::max(width, 1);
1478 int allocheight = std::max(height, 1);
1480 gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea));
1481 if (GError *pError = gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea)))
1483 SAL_WARN("vcl.gtk", "gtk gl area error: " << pError->message);
1484 return;
1487 glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderBuffer);
1488 glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, allocwidth, allocheight);
1489 glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBuffer);
1490 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, allocwidth, allocheight);
1491 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nAreaFrameBuffer);
1493 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1494 GL_RENDERBUFFER_EXT, m_nRenderBuffer);
1495 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1496 GL_RENDERBUFFER_EXT, m_nDepthBuffer);
1498 gdk_gl_context_make_current(m_pContext);
1499 glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderBuffer);
1500 glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBuffer);
1501 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameBuffer);
1503 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1504 GL_RENDERBUFFER_EXT, m_nRenderBuffer);
1505 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1506 GL_RENDERBUFFER_EXT, m_nDepthBuffer);
1507 glViewport(0, 0, width, height);
1509 glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderScratchBuffer);
1510 glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, allocwidth, allocheight);
1511 glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthScratchBuffer);
1512 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, allocwidth, allocheight);
1513 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
1515 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1516 GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
1517 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1518 GL_RENDERBUFFER_EXT, m_nDepthScratchBuffer);
1520 glViewport(0, 0, width, height);
1523 virtual bool ImplInit() override
1525 const SystemEnvData* pEnvData = m_pChildWindow->GetSystemData();
1526 GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
1527 m_pGLArea = gtk_gl_area_new();
1528 g_signal_connect(G_OBJECT(m_pGLArea), "destroy", G_CALLBACK(signalDestroy), this);
1529 g_signal_connect(G_OBJECT(m_pGLArea), "render", G_CALLBACK(signalRender), this);
1530 gtk_gl_area_set_has_depth_buffer(GTK_GL_AREA(m_pGLArea), true);
1531 gtk_gl_area_set_auto_render(GTK_GL_AREA(m_pGLArea), false);
1532 gtk_widget_set_hexpand(m_pGLArea, true);
1533 gtk_widget_set_vexpand(m_pGLArea, true);
1534 gtk_container_add(GTK_CONTAINER(pParent), m_pGLArea);
1535 gtk_widget_show_all(pParent);
1537 gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea));
1538 if (GError *pError = gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea)))
1540 SAL_WARN("vcl.gtk", "gtk gl area error: " << pError->message);
1541 return false;
1544 gtk_gl_area_attach_buffers(GTK_GL_AREA(m_pGLArea));
1545 glGenFramebuffersEXT(1, &m_nAreaFrameBuffer);
1547 GdkWindow *pWindow = gtk_widget_get_window(pParent);
1548 m_pContext = gdk_window_create_gl_context(pWindow, nullptr);
1549 if (!m_pContext)
1550 return false;
1552 if (!gdk_gl_context_realize(m_pContext, nullptr))
1553 return false;
1555 gdk_gl_context_make_current(m_pContext);
1556 glGenFramebuffersEXT(1, &m_nFrameBuffer);
1557 glGenRenderbuffersEXT(1, &m_nRenderBuffer);
1558 glGenRenderbuffersEXT(1, &m_nDepthBuffer);
1559 glGenFramebuffersEXT(1, &m_nFrameScratchBuffer);
1560 glGenRenderbuffersEXT(1, &m_nRenderScratchBuffer);
1561 glGenRenderbuffersEXT(1, &m_nDepthScratchBuffer);
1563 bool bRet = InitGL();
1564 InitGLDebugging();
1565 return bRet;
1568 virtual void restoreDefaultFramebuffer() override
1570 OpenGLContext::restoreDefaultFramebuffer();
1571 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
1572 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1573 GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
1576 virtual void makeCurrent() override
1578 if (isCurrent())
1579 return;
1581 clearCurrent();
1583 if (m_pGLArea)
1585 int scale = gtk_widget_get_scale_factor(m_pGLArea);
1586 int width = m_aGLWin.Width * scale;
1587 int height = m_aGLWin.Height * scale;
1589 gdk_gl_context_make_current(m_pContext);
1591 glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderScratchBuffer);
1592 glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthScratchBuffer);
1593 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
1594 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
1595 GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
1596 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
1597 GL_RENDERBUFFER_EXT, m_nDepthScratchBuffer);
1598 glViewport(0, 0, width, height);
1601 registerAsCurrent();
1604 virtual void destroyCurrentContext() override
1606 gdk_gl_context_clear_current();
1609 virtual bool isCurrent() override
1611 return m_pGLArea && gdk_gl_context_get_current() == m_pContext;
1614 virtual void sync() override
1618 virtual void resetCurrent() override
1620 clearCurrent();
1621 gdk_gl_context_clear_current();
1624 virtual void swapBuffers() override
1626 int scale = gtk_widget_get_scale_factor(m_pGLArea);
1627 int width = m_aGLWin.Width * scale;
1628 int height = m_aGLWin.Height * scale;
1630 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_nFrameBuffer);
1631 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
1633 glBindFramebuffer(GL_READ_FRAMEBUFFER, m_nFrameScratchBuffer);
1634 glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
1636 glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
1637 GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
1639 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_nFrameScratchBuffer);
1640 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
1642 gtk_gl_area_queue_render(GTK_GL_AREA(m_pGLArea));
1643 BuffersSwapped();
1646 virtual ~GtkOpenGLContext() override
1648 if (m_pContext)
1650 g_clear_object(&m_pContext);
1657 OpenGLContext* GtkInstance::CreateOpenGLContext()
1659 return new GtkOpenGLContext;
1662 // tdf#123800 avoid requiring wayland at runtime just because it existed at buildtime
1663 bool DLSYM_GDK_IS_WAYLAND_DISPLAY(GdkDisplay* pDisplay)
1665 static auto get_type = reinterpret_cast<GType (*) (void)>(dlsym(nullptr, "gdk_wayland_display_get_type"));
1666 if (!get_type)
1667 return false;
1668 return G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
1671 bool DLSYM_GDK_IS_X11_DISPLAY(GdkDisplay* pDisplay)
1673 static auto get_type = reinterpret_cast<GType (*) (void)>(dlsym(nullptr, "gdk_x11_display_get_type"));
1674 if (!get_type)
1675 return false;
1676 return G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
1679 namespace
1682 class GtkInstanceBuilder;
1684 void set_help_id(const GtkWidget *pWidget, const OString& rHelpId)
1686 gchar *helpid = g_strdup(rHelpId.getStr());
1687 g_object_set_data_full(G_OBJECT(pWidget), "g-lo-helpid", helpid, g_free);
1690 OString get_help_id(const GtkWidget *pWidget)
1692 void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-helpid");
1693 const gchar* pStr = static_cast<const gchar*>(pData);
1694 return OString(pStr, pStr ? strlen(pStr) : 0);
1697 KeyEvent GtkToVcl(const GdkEventKey& rEvent)
1699 sal_uInt16 nKeyCode = GtkSalFrame::GetKeyCode(rEvent.keyval);
1700 if (nKeyCode == 0)
1702 guint updated_keyval = GtkSalFrame::GetKeyValFor(gdk_keymap_get_default(), rEvent.hardware_keycode, rEvent.group);
1703 nKeyCode = GtkSalFrame::GetKeyCode(updated_keyval);
1705 nKeyCode |= GtkSalFrame::GetKeyModCode(rEvent.state);
1706 return KeyEvent(gdk_keyval_to_unicode(rEvent.keyval), nKeyCode, 0);
1710 static MouseEventModifiers ImplGetMouseButtonMode(sal_uInt16 nButton, sal_uInt16 nCode)
1712 MouseEventModifiers nMode = MouseEventModifiers::NONE;
1713 if ( nButton == MOUSE_LEFT )
1714 nMode |= MouseEventModifiers::SIMPLECLICK;
1715 if ( (nButton == MOUSE_LEFT) && !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT)) )
1716 nMode |= MouseEventModifiers::SELECT;
1717 if ( (nButton == MOUSE_LEFT) && (nCode & KEY_MOD1) &&
1718 !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_SHIFT)) )
1719 nMode |= MouseEventModifiers::MULTISELECT;
1720 if ( (nButton == MOUSE_LEFT) && (nCode & KEY_SHIFT) &&
1721 !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_MOD1)) )
1722 nMode |= MouseEventModifiers::RANGESELECT;
1723 return nMode;
1726 static MouseEventModifiers ImplGetMouseMoveMode(sal_uInt16 nCode)
1728 MouseEventModifiers nMode = MouseEventModifiers::NONE;
1729 if ( !nCode )
1730 nMode |= MouseEventModifiers::SIMPLEMOVE;
1731 if ( (nCode & MOUSE_LEFT) && !(nCode & KEY_MOD1) )
1732 nMode |= MouseEventModifiers::DRAGMOVE;
1733 if ( (nCode & MOUSE_LEFT) && (nCode & KEY_MOD1) )
1734 nMode |= MouseEventModifiers::DRAGCOPY;
1735 return nMode;
1738 namespace
1740 bool SwapForRTL(GtkWidget* pWidget)
1742 GtkTextDirection eDir = gtk_widget_get_direction(pWidget);
1743 if (eDir == GTK_TEXT_DIR_RTL)
1744 return true;
1745 if (eDir == GTK_TEXT_DIR_LTR)
1746 return false;
1747 return AllSettings::GetLayoutRTL();
1750 void replaceWidget(GtkWidget* pWidget, GtkWidget* pReplacement)
1752 // remove the widget and replace it with pReplacement
1753 GtkWidget* pParent = gtk_widget_get_parent(pWidget);
1755 // if pWidget was un-parented then don't bother
1756 if (!pParent)
1757 return;
1759 g_object_ref(pWidget);
1761 gint nTopAttach(0), nLeftAttach(0), nHeight(1), nWidth(1);
1762 if (GTK_IS_GRID(pParent))
1764 gtk_container_child_get(GTK_CONTAINER(pParent), pWidget,
1765 "left-attach", &nLeftAttach,
1766 "top-attach", &nTopAttach,
1767 "width", &nWidth,
1768 "height", &nHeight,
1769 nullptr);
1772 gboolean bExpand(false), bFill(false);
1773 GtkPackType ePackType(GTK_PACK_START);
1774 guint nPadding(0);
1775 gint nPosition(0);
1776 if (GTK_IS_BOX(pParent))
1778 gtk_container_child_get(GTK_CONTAINER(pParent), pWidget,
1779 "expand", &bExpand,
1780 "fill", &bFill,
1781 "pack-type", &ePackType,
1782 "padding", &nPadding,
1783 "position", &nPosition,
1784 nullptr);
1787 gtk_container_remove(GTK_CONTAINER(pParent), pWidget);
1789 gtk_widget_set_visible(pReplacement, gtk_widget_get_visible(pWidget));
1790 gtk_widget_set_no_show_all(pReplacement, gtk_widget_get_no_show_all(pWidget));
1792 int nReqWidth, nReqHeight;
1793 gtk_widget_get_size_request(pWidget, &nReqWidth, &nReqHeight);
1794 gtk_widget_set_size_request(pReplacement, nReqWidth, nReqHeight);
1796 static GQuark quark_size_groups = g_quark_from_static_string("gtk-widget-size-groups");
1797 GSList* pSizeGroups = static_cast<GSList*>(g_object_get_qdata(G_OBJECT(pWidget), quark_size_groups));
1798 while (pSizeGroups)
1800 GtkSizeGroup *pSizeGroup = static_cast<GtkSizeGroup*>(pSizeGroups->data);
1801 pSizeGroups = pSizeGroups->next;
1802 gtk_size_group_remove_widget(pSizeGroup, pWidget);
1803 gtk_size_group_add_widget(pSizeGroup, pReplacement);
1806 // tdf#135368 change the mnemonic to point to our replacement
1807 GList* pLabels = gtk_widget_list_mnemonic_labels(pWidget);
1808 for (GList* pLabel = g_list_first(pLabels); pLabel; pLabel = g_list_next(pLabel))
1810 GtkWidget* pLabelWidget = static_cast<GtkWidget*>(pLabel->data);
1811 if (!GTK_IS_LABEL(pLabelWidget))
1812 continue;
1813 gtk_label_set_mnemonic_widget(GTK_LABEL(pLabelWidget), pReplacement);
1815 g_list_free(pLabels);
1817 gtk_container_add(GTK_CONTAINER(pParent), pReplacement);
1819 if (GTK_IS_GRID(pParent))
1821 gtk_container_child_set(GTK_CONTAINER(pParent), pReplacement,
1822 "left-attach", nLeftAttach,
1823 "top-attach", nTopAttach,
1824 "width", nWidth,
1825 "height", nHeight,
1826 nullptr);
1829 if (GTK_IS_BOX(pParent))
1831 gtk_container_child_set(GTK_CONTAINER(pParent), pReplacement,
1832 "expand", bExpand,
1833 "fill", bFill,
1834 "pack-type", ePackType,
1835 "padding", nPadding,
1836 "position", nPosition,
1837 nullptr);
1840 if (gtk_widget_get_hexpand_set(pWidget))
1841 gtk_widget_set_hexpand(pReplacement, gtk_widget_get_hexpand(pWidget));
1843 if (gtk_widget_get_vexpand_set(pWidget))
1844 gtk_widget_set_vexpand(pReplacement, gtk_widget_get_vexpand(pWidget));
1846 gtk_widget_set_halign(pReplacement, gtk_widget_get_halign(pWidget));
1847 gtk_widget_set_valign(pReplacement, gtk_widget_get_valign(pWidget));
1849 g_object_unref(pWidget);
1852 void insertAsParent(GtkWidget* pWidget, GtkWidget* pReplacement)
1854 g_object_ref(pWidget);
1856 replaceWidget(pWidget, pReplacement);
1858 gtk_container_add(GTK_CONTAINER(pReplacement), pWidget);
1860 g_object_unref(pWidget);
1863 GtkWidget* ensureEventWidget(GtkWidget* pWidget)
1865 if (!pWidget)
1866 return nullptr;
1868 GtkWidget* pMouseEventBox;
1869 // not every widget has a GdkWindow and can get any event, so if we
1870 // want an event it doesn't have, insert a GtkEventBox so we can get
1871 // those
1872 if (gtk_widget_get_has_window(pWidget))
1873 pMouseEventBox = pWidget;
1874 else
1876 // remove the widget and replace it with an eventbox and put the old
1877 // widget into it
1878 pMouseEventBox = gtk_event_box_new();
1879 gtk_event_box_set_above_child(GTK_EVENT_BOX(pMouseEventBox), false);
1880 gtk_event_box_set_visible_window(GTK_EVENT_BOX(pMouseEventBox), false);
1881 insertAsParent(pWidget, pMouseEventBox);
1884 return pMouseEventBox;
1888 namespace {
1890 GdkDragAction VclToGdk(sal_Int8 dragOperation)
1892 GdkDragAction eRet(static_cast<GdkDragAction>(0));
1893 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
1894 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_COPY);
1895 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
1896 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_MOVE);
1897 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
1898 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_LINK);
1899 return eRet;
1902 class GtkInstanceWidget : public virtual weld::Widget
1904 protected:
1905 GtkWidget* m_pWidget;
1906 GtkWidget* m_pMouseEventBox;
1907 GtkInstanceBuilder* m_pBuilder;
1909 DECL_LINK(async_drag_cancel, void*, void);
1911 static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
1913 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1914 pThis->signal_focus_in();
1915 return false;
1918 void signal_focus_in()
1920 m_aFocusInHdl.Call(*this);
1923 static gboolean signalMnemonicActivate(GtkWidget*, gboolean, gpointer widget)
1925 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1926 SolarMutexGuard aGuard;
1927 return pThis->signal_mnemonic_activate();
1930 bool signal_mnemonic_activate()
1932 return m_aMnemonicActivateHdl.Call(*this);
1935 static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
1937 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1938 SolarMutexGuard aGuard;
1939 pThis->signal_focus_out();
1940 return false;
1943 void launch_drag_cancel(GdkDragContext* context)
1945 // post our drag cancel to happen at the next available event cycle
1946 if (m_pDragCancelEvent)
1947 return;
1948 g_object_ref(context);
1949 m_pDragCancelEvent = Application::PostUserEvent(LINK(this, GtkInstanceWidget, async_drag_cancel), context);
1952 void signal_focus_out()
1954 m_aFocusOutHdl.Call(*this);
1957 virtual void ensureMouseEventWidget()
1959 if (!m_pMouseEventBox)
1960 m_pMouseEventBox = ::ensureEventWidget(m_pWidget);
1963 void ensureButtonPressSignal()
1965 if (!m_nButtonPressSignalId)
1967 ensureMouseEventWidget();
1968 m_nButtonPressSignalId = g_signal_connect(m_pMouseEventBox, "button-press-event", G_CALLBACK(signalButton), this);
1972 static gboolean signalPopupMenu(GtkWidget* pWidget, gpointer widget)
1974 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
1975 SolarMutexGuard aGuard;
1976 //center it when we don't know where else to use
1977 Point aPos(gtk_widget_get_allocated_width(pWidget) / 2,
1978 gtk_widget_get_allocated_height(pWidget) / 2);
1979 CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, false);
1980 return pThis->signal_popup_menu(aCEvt);
1983 bool SwapForRTL() const
1985 return ::SwapForRTL(m_pWidget);
1988 void do_enable_drag_source(const rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants)
1990 css::uno::Reference<css::datatransfer::XTransferable> xTrans(rHelper.get());
1991 css::uno::Reference<css::datatransfer::dnd::XDragSourceListener> xListener(rHelper.get());
1993 ensure_drag_source();
1995 auto aFormats = xTrans->getTransferDataFlavors();
1996 std::vector<GtkTargetEntry> aGtkTargets(m_xDragSource->FormatsToGtk(aFormats));
1998 m_eDragAction = VclToGdk(eDNDConstants);
1999 drag_source_set(aGtkTargets, m_eDragAction);
2001 for (auto &a : aGtkTargets)
2002 g_free(a.target);
2004 m_xDragSource->set_datatransfer(xTrans, xListener);
2007 void localizeDecimalSeparator()
2009 // tdf#128867 if localize decimal separator is active we will always
2010 // need to be able to change the output of the decimal key press
2011 if (!m_nKeyPressSignalId && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
2012 m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
2015 void ensure_drag_begin_end()
2017 if (!m_nDragBeginSignalId)
2019 // using "after" due to https://gitlab.gnome.org/GNOME/pygobject/issues/251
2020 m_nDragBeginSignalId = g_signal_connect_after(m_pWidget, "drag-begin", G_CALLBACK(signalDragBegin), this);
2022 if (!m_nDragEndSignalId)
2023 m_nDragEndSignalId = g_signal_connect(m_pWidget, "drag-end", G_CALLBACK(signalDragEnd), this);
2026 private:
2027 bool m_bTakeOwnership;
2028 bool m_bDraggedOver;
2029 sal_uInt16 m_nLastMouseButton;
2030 sal_uInt16 m_nLastMouseClicks;
2031 int m_nPressedButton;
2032 int m_nPressStartX;
2033 int m_nPressStartY;
2034 ImplSVEvent* m_pDragCancelEvent;
2035 GtkCssProvider* m_pBgCssProvider;
2036 GdkDragAction m_eDragAction;
2037 gulong m_nFocusInSignalId;
2038 gulong m_nMnemonicActivateSignalId;
2039 gulong m_nFocusOutSignalId;
2040 gulong m_nKeyPressSignalId;
2041 gulong m_nKeyReleaseSignalId;
2042 gulong m_nSizeAllocateSignalId;
2043 gulong m_nButtonPressSignalId;
2044 gulong m_nMotionSignalId;
2045 gulong m_nLeaveSignalId;
2046 gulong m_nEnterSignalId;
2047 gulong m_nButtonReleaseSignalId;
2048 gulong m_nDragMotionSignalId;
2049 gulong m_nDragDropSignalId;
2050 gulong m_nDragDropReceivedSignalId;
2051 gulong m_nDragLeaveSignalId;
2052 gulong m_nDragBeginSignalId;
2053 gulong m_nDragEndSignalId;
2054 gulong m_nDragFailedSignalId;
2055 gulong m_nDragDataDeleteignalId;
2056 gulong m_nDragGetSignalId;
2058 rtl::Reference<GtkDropTarget> m_xDropTarget;
2059 rtl::Reference<GtkDragSource> m_xDragSource;
2061 static void signalSizeAllocate(GtkWidget*, GdkRectangle* allocation, gpointer widget)
2063 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2064 SolarMutexGuard aGuard;
2065 pThis->signal_size_allocate(allocation->width, allocation->height);
2068 static gboolean signalKey(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
2070 // #i1820# use locale specific decimal separator
2071 if (pEvent->keyval == GDK_KEY_KP_Decimal && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
2073 OUString aSep(Application::GetSettings().GetLocaleDataWrapper().getNumDecimalSep());
2074 pEvent->keyval = aSep[0];
2077 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2078 return pThis->signal_key(pEvent);
2081 virtual bool signal_popup_menu(const CommandEvent&)
2083 return false;
2086 static gboolean signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
2088 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2089 SolarMutexGuard aGuard;
2090 return pThis->signal_button(pEvent);
2093 bool signal_button(GdkEventButton* pEvent)
2095 m_nPressedButton = -1;
2097 Point aPos(pEvent->x, pEvent->y);
2098 if (SwapForRTL())
2099 aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
2101 if (gdk_event_triggers_context_menu(reinterpret_cast<GdkEvent*>(pEvent)) && pEvent->type == GDK_BUTTON_PRESS)
2103 //if handled for context menu, stop processing
2104 CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, true);
2105 if (signal_popup_menu(aCEvt))
2106 return true;
2109 if (!m_aMousePressHdl.IsSet() && !m_aMouseReleaseHdl.IsSet())
2110 return false;
2112 SalEvent nEventType = SalEvent::NONE;
2113 switch (pEvent->type)
2115 case GDK_BUTTON_PRESS:
2116 if (GdkEvent* pPeekEvent = gdk_event_peek())
2118 bool bSkip = pPeekEvent->type == GDK_2BUTTON_PRESS ||
2119 pPeekEvent->type == GDK_3BUTTON_PRESS;
2120 gdk_event_free(pPeekEvent);
2121 if (bSkip)
2123 return false;
2126 nEventType = SalEvent::MouseButtonDown;
2127 m_nLastMouseClicks = 1;
2128 break;
2129 case GDK_2BUTTON_PRESS:
2130 m_nLastMouseClicks = 2;
2131 nEventType = SalEvent::MouseButtonDown;
2132 break;
2133 case GDK_3BUTTON_PRESS:
2134 m_nLastMouseClicks = 3;
2135 nEventType = SalEvent::MouseButtonDown;
2136 break;
2137 case GDK_BUTTON_RELEASE:
2138 nEventType = SalEvent::MouseButtonUp;
2139 break;
2140 default:
2141 return false;
2144 switch (pEvent->button)
2146 case 1:
2147 m_nLastMouseButton = MOUSE_LEFT;
2148 break;
2149 case 2:
2150 m_nLastMouseButton = MOUSE_MIDDLE;
2151 break;
2152 case 3:
2153 m_nLastMouseButton = MOUSE_RIGHT;
2154 break;
2155 default:
2156 return false;
2159 /* Save press to possibly begin a drag */
2160 if (pEvent->type != GDK_BUTTON_RELEASE)
2162 m_nPressedButton = pEvent->button;
2163 m_nPressStartX = pEvent->x;
2164 m_nPressStartY = pEvent->y;
2167 sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
2168 // strip out which buttons are involved from the nModCode and replace with m_nLastMouseButton
2169 sal_uInt16 nCode = m_nLastMouseButton | (nModCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2));
2170 MouseEvent aMEvt(aPos, m_nLastMouseClicks, ImplGetMouseButtonMode(m_nLastMouseButton, nModCode), nCode, nCode);
2172 if (nEventType == SalEvent::MouseButtonDown)
2174 if (!m_aMousePressHdl.IsSet())
2175 return false;
2176 return m_aMousePressHdl.Call(aMEvt);
2179 if (!m_aMouseReleaseHdl.IsSet())
2180 return false;
2181 return m_aMouseReleaseHdl.Call(aMEvt);
2184 static gboolean signalMotion(GtkWidget*, GdkEventMotion* pEvent, gpointer widget)
2186 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2187 SolarMutexGuard aGuard;
2188 return pThis->signal_motion(pEvent);
2191 bool signal_motion(const GdkEventMotion* pEvent)
2193 GtkTargetList* pDragData = (m_eDragAction != 0 && m_nPressedButton != -1 && m_xDragSource.is()) ? gtk_drag_source_get_target_list(m_pWidget) : nullptr;
2194 bool bUnsetDragIcon(false);
2195 if (pDragData && gtk_drag_check_threshold(m_pWidget, m_nPressStartX, m_nPressStartY, pEvent->x, pEvent->y) && !do_signal_drag_begin(bUnsetDragIcon))
2197 GdkDragContext* pContext = gtk_drag_begin_with_coordinates(m_pWidget,
2198 pDragData,
2199 m_eDragAction,
2200 m_nPressedButton,
2201 const_cast<GdkEvent*>(reinterpret_cast<const GdkEvent*>(pEvent)),
2202 m_nPressStartX, m_nPressStartY);
2204 if (pContext && bUnsetDragIcon)
2206 cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
2207 gtk_drag_set_icon_surface(pContext, surface);
2210 m_nPressedButton = -1;
2211 return false;
2214 if (!m_aMouseMotionHdl.IsSet())
2215 return false;
2217 Point aPos(pEvent->x, pEvent->y);
2218 if (SwapForRTL())
2219 aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
2220 sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
2221 MouseEvent aMEvt(aPos, 0, ImplGetMouseMoveMode(nModCode), nModCode, nModCode);
2223 m_aMouseMotionHdl.Call(aMEvt);
2224 return true;
2227 static gboolean signalCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
2229 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2230 SolarMutexGuard aGuard;
2231 return pThis->signal_crossing(pEvent);
2234 bool signal_crossing(const GdkEventCrossing* pEvent)
2236 if (!m_aMouseMotionHdl.IsSet())
2237 return false;
2239 Point aPos(pEvent->x, pEvent->y);
2240 if (SwapForRTL())
2241 aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
2242 sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
2243 MouseEventModifiers eModifiers = ImplGetMouseMoveMode(nModCode);
2244 eModifiers = eModifiers | (pEvent->type == GDK_ENTER_NOTIFY ? MouseEventModifiers::ENTERWINDOW : MouseEventModifiers::LEAVEWINDOW);
2245 MouseEvent aMEvt(aPos, 0, eModifiers, nModCode, nModCode);
2247 m_aMouseMotionHdl.Call(aMEvt);
2248 return true;
2251 virtual void drag_started()
2255 static gboolean signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time, gpointer widget)
2257 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2258 if (!pThis->m_bDraggedOver)
2260 pThis->m_bDraggedOver = true;
2261 pThis->drag_started();
2263 return pThis->m_xDropTarget->signalDragMotion(pWidget, context, x, y, time);
2266 static gboolean signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time, gpointer widget)
2268 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2269 return pThis->m_xDropTarget->signalDragDrop(pWidget, context, x, y, time);
2272 static void signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time, gpointer widget)
2274 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2275 pThis->m_xDropTarget->signalDragDropReceived(pWidget, context, x, y, data, ttype, time);
2278 virtual void drag_ended()
2282 static void signalDragLeave(GtkWidget *pWidget, GdkDragContext *context, guint time, gpointer widget)
2284 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2285 pThis->m_xDropTarget->signalDragLeave(pWidget, context, time);
2286 if (pThis->m_bDraggedOver)
2288 pThis->m_bDraggedOver = false;
2289 pThis->drag_ended();
2293 static void signalDragBegin(GtkWidget*, GdkDragContext* context, gpointer widget)
2295 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2296 pThis->signal_drag_begin(context);
2299 void ensure_drag_source()
2301 if (!m_xDragSource)
2303 m_xDragSource.set(new GtkDragSource);
2305 m_nDragFailedSignalId = g_signal_connect(m_pWidget, "drag-failed", G_CALLBACK(signalDragFailed), this);
2306 m_nDragDataDeleteignalId = g_signal_connect(m_pWidget, "drag-data-delete", G_CALLBACK(signalDragDelete), this);
2307 m_nDragGetSignalId = g_signal_connect(m_pWidget, "drag-data-get", G_CALLBACK(signalDragDataGet), this);
2309 ensure_drag_begin_end();
2313 virtual bool do_signal_drag_begin(bool& rUnsetDragIcon)
2315 rUnsetDragIcon = false;
2316 return false;
2319 void signal_drag_begin(GdkDragContext* context)
2321 bool bUnsetDragIcon(false);
2322 if (do_signal_drag_begin(bUnsetDragIcon))
2324 launch_drag_cancel(context);
2325 return;
2327 if (bUnsetDragIcon)
2329 cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
2330 gtk_drag_set_icon_surface(context, surface);
2332 if (!m_xDragSource)
2333 return;
2334 m_xDragSource->setActiveDragSource();
2337 virtual void do_signal_drag_end()
2341 static void signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer widget)
2343 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2344 pThis->do_signal_drag_end();
2345 if (pThis->m_xDragSource.is())
2346 pThis->m_xDragSource->dragEnd(context);
2349 static gboolean signalDragFailed(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkDragResult /*result*/, gpointer widget)
2351 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2352 pThis->m_xDragSource->dragFailed();
2353 return false;
2356 static void signalDragDelete(GtkWidget* /*widget*/, GdkDragContext* /*context*/, gpointer widget)
2358 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2359 pThis->m_xDragSource->dragDelete();
2362 static void signalDragDataGet(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkSelectionData *data, guint info,
2363 guint /*time*/, gpointer widget)
2365 GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
2366 pThis->m_xDragSource->dragDataGet(data, info);
2369 virtual void drag_source_set(const std::vector<GtkTargetEntry>& rGtkTargets, GdkDragAction eDragAction)
2371 if (rGtkTargets.empty() && !eDragAction)
2372 gtk_drag_source_unset(m_pWidget);
2373 else
2374 gtk_drag_source_set(m_pWidget, GDK_BUTTON1_MASK, rGtkTargets.data(), rGtkTargets.size(), eDragAction);
2377 void do_set_background(const Color& rColor)
2379 const bool bRemoveColor = rColor == COL_AUTO;
2380 if (bRemoveColor && !m_pBgCssProvider)
2381 return;
2382 GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(GTK_WIDGET(m_pWidget));
2383 if (m_pBgCssProvider)
2385 gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider));
2386 m_pBgCssProvider = nullptr;
2388 if (bRemoveColor)
2389 return;
2390 OUString sColor = rColor.AsRGBHexString();
2391 m_pBgCssProvider = gtk_css_provider_new();
2392 OUString aBuffer = "* { background-color: #" + sColor + "; }";
2393 OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
2394 gtk_css_provider_load_from_data(m_pBgCssProvider, aResult.getStr(), aResult.getLength(), nullptr);
2395 gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider),
2396 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2399 public:
2400 GtkInstanceWidget(GtkWidget* pWidget, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
2401 : m_pWidget(pWidget)
2402 , m_pMouseEventBox(nullptr)
2403 , m_pBuilder(pBuilder)
2404 , m_bTakeOwnership(bTakeOwnership)
2405 , m_bDraggedOver(false)
2406 , m_nLastMouseButton(0)
2407 , m_nLastMouseClicks(0)
2408 , m_nPressedButton(-1)
2409 , m_nPressStartX(-1)
2410 , m_nPressStartY(-1)
2411 , m_pDragCancelEvent(nullptr)
2412 , m_pBgCssProvider(nullptr)
2413 , m_eDragAction(GdkDragAction(0))
2414 , m_nFocusInSignalId(0)
2415 , m_nMnemonicActivateSignalId(0)
2416 , m_nFocusOutSignalId(0)
2417 , m_nKeyPressSignalId(0)
2418 , m_nKeyReleaseSignalId(0)
2419 , m_nSizeAllocateSignalId(0)
2420 , m_nButtonPressSignalId(0)
2421 , m_nMotionSignalId(0)
2422 , m_nLeaveSignalId(0)
2423 , m_nEnterSignalId(0)
2424 , m_nButtonReleaseSignalId(0)
2425 , m_nDragMotionSignalId(0)
2426 , m_nDragDropSignalId(0)
2427 , m_nDragDropReceivedSignalId(0)
2428 , m_nDragLeaveSignalId(0)
2429 , m_nDragBeginSignalId(0)
2430 , m_nDragEndSignalId(0)
2431 , m_nDragFailedSignalId(0)
2432 , m_nDragDataDeleteignalId(0)
2433 , m_nDragGetSignalId(0)
2435 if (!bTakeOwnership)
2436 g_object_ref(m_pWidget);
2438 localizeDecimalSeparator();
2441 virtual void connect_key_press(const Link<const KeyEvent&, bool>& rLink) override
2443 if (!m_nKeyPressSignalId)
2444 m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
2445 weld::Widget::connect_key_press(rLink);
2448 virtual void connect_key_release(const Link<const KeyEvent&, bool>& rLink) override
2450 if (!m_nKeyReleaseSignalId)
2451 m_nKeyReleaseSignalId = g_signal_connect(m_pWidget, "key-release-event", G_CALLBACK(signalKey), this);
2452 weld::Widget::connect_key_release(rLink);
2455 virtual void connect_mouse_press(const Link<const MouseEvent&, bool>& rLink) override
2457 ensureButtonPressSignal();
2458 weld::Widget::connect_mouse_press(rLink);
2461 virtual void connect_mouse_move(const Link<const MouseEvent&, bool>& rLink) override
2463 ensureMouseEventWidget();
2464 if (!m_nMotionSignalId)
2465 m_nMotionSignalId = g_signal_connect(m_pMouseEventBox, "motion-notify-event", G_CALLBACK(signalMotion), this);
2466 if (!m_nLeaveSignalId)
2467 m_nLeaveSignalId = g_signal_connect(m_pMouseEventBox, "leave-notify-event", G_CALLBACK(signalCrossing), this);
2468 if (!m_nEnterSignalId)
2469 m_nEnterSignalId = g_signal_connect(m_pMouseEventBox, "enter-notify-event", G_CALLBACK(signalCrossing), this);
2470 weld::Widget::connect_mouse_move(rLink);
2473 virtual void connect_mouse_release(const Link<const MouseEvent&, bool>& rLink) override
2475 ensureMouseEventWidget();
2476 if (!m_nButtonReleaseSignalId)
2477 m_nButtonReleaseSignalId = g_signal_connect(m_pMouseEventBox, "button-release-event", G_CALLBACK(signalButton), this);
2478 weld::Widget::connect_mouse_release(rLink);
2481 virtual void set_sensitive(bool sensitive) override
2483 gtk_widget_set_sensitive(m_pWidget, sensitive);
2486 virtual bool get_sensitive() const override
2488 return gtk_widget_get_sensitive(m_pWidget);
2491 virtual bool get_visible() const override
2493 return gtk_widget_get_visible(m_pWidget);
2496 virtual bool is_visible() const override
2498 return gtk_widget_is_visible(m_pWidget);
2501 virtual void set_can_focus(bool bCanFocus) override
2503 gtk_widget_set_can_focus(m_pWidget, bCanFocus);
2506 virtual void grab_focus() override
2508 if (has_focus())
2509 return;
2510 gtk_widget_grab_focus(m_pWidget);
2513 virtual bool has_focus() const override
2515 return gtk_widget_has_focus(m_pWidget);
2518 virtual bool is_active() const override
2520 GtkWindow* pTopLevel = GTK_WINDOW(gtk_widget_get_toplevel(m_pWidget));
2521 return pTopLevel && gtk_window_is_active(pTopLevel) && has_focus();
2524 // is the focus in a child of this widget, where a transient popup attached
2525 // to a widget is considered a child of that widget
2526 virtual bool has_child_focus() const override
2528 bool bRet = false;
2530 GList* pList = gtk_window_list_toplevels();
2532 for (GList* pEntry = pList; pEntry; pEntry = pEntry->next)
2534 if (!gtk_window_has_toplevel_focus(GTK_WINDOW(pEntry->data)))
2535 continue;
2536 GtkWidget* pFocus = gtk_window_get_focus(GTK_WINDOW(pEntry->data));
2537 if (pFocus && gtk_widget_is_ancestor(pFocus, m_pWidget))
2539 bRet = true;
2540 break;
2542 GtkWidget* pAttachedTo = gtk_window_get_attached_to(GTK_WINDOW(pEntry->data));
2543 if (!pAttachedTo)
2544 continue;
2545 if (pAttachedTo == m_pWidget || gtk_widget_is_ancestor(pAttachedTo, m_pWidget))
2547 bRet = true;
2548 break;
2552 g_list_free(pList);
2554 return bRet;
2557 virtual void set_has_default(bool has_default) override
2559 g_object_set(G_OBJECT(m_pWidget), "has-default", has_default, nullptr);
2562 virtual bool get_has_default() const override
2564 gboolean has_default(false);
2565 g_object_get(G_OBJECT(m_pWidget), "has-default", &has_default, nullptr);
2566 return has_default;
2569 virtual void show() override
2571 gtk_widget_show(m_pWidget);
2574 virtual void hide() override
2576 gtk_widget_hide(m_pWidget);
2579 virtual void set_size_request(int nWidth, int nHeight) override
2581 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
2582 if (GTK_IS_VIEWPORT(pParent))
2583 pParent = gtk_widget_get_parent(pParent);
2584 if (GTK_IS_SCROLLED_WINDOW(pParent))
2586 gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(pParent), nWidth);
2587 gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(pParent), nHeight);
2589 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
2592 virtual Size get_size_request() const override
2594 int nWidth, nHeight;
2595 gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
2596 return Size(nWidth, nHeight);
2599 virtual Size get_preferred_size() const override
2601 GtkRequisition size;
2602 gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
2603 return Size(size.width, size.height);
2606 virtual float get_approximate_digit_width() const override
2608 PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
2609 PangoFontMetrics* pMetrics = pango_context_get_metrics(pContext,
2610 pango_context_get_font_description(pContext),
2611 pango_context_get_language(pContext));
2612 float nDigitWidth = pango_font_metrics_get_approximate_digit_width(pMetrics);
2613 pango_font_metrics_unref(pMetrics);
2615 return nDigitWidth / PANGO_SCALE;
2618 virtual int get_text_height() const override
2620 PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
2621 PangoFontMetrics* pMetrics = pango_context_get_metrics(pContext,
2622 pango_context_get_font_description(pContext),
2623 pango_context_get_language(pContext));
2624 int nLineHeight = pango_font_metrics_get_ascent(pMetrics) + pango_font_metrics_get_descent(pMetrics);
2625 pango_font_metrics_unref(pMetrics);
2626 return nLineHeight / PANGO_SCALE;
2629 virtual Size get_pixel_size(const OUString& rText) const override
2631 OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
2632 PangoLayout* pLayout = gtk_widget_create_pango_layout(m_pWidget, aStr.getStr());
2633 gint nWidth, nHeight;
2634 pango_layout_get_pixel_size(pLayout, &nWidth, &nHeight);
2635 g_object_unref(pLayout);
2636 return Size(nWidth, nHeight);
2639 virtual vcl::Font get_font() override
2641 PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
2642 return pango_to_vcl(pango_context_get_font_description(pContext),
2643 Application::GetSettings().GetUILanguageTag().getLocale());
2646 virtual void set_grid_left_attach(int nAttach) override
2648 GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
2649 gtk_container_child_set(pParent, m_pWidget, "left-attach", nAttach, nullptr);
2652 virtual int get_grid_left_attach() const override
2654 GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
2655 gint nAttach(0);
2656 gtk_container_child_get(pParent, m_pWidget, "left-attach", &nAttach, nullptr);
2657 return nAttach;
2660 virtual void set_grid_width(int nCols) override
2662 GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
2663 gtk_container_child_set(pParent, m_pWidget, "width", nCols, nullptr);
2666 virtual void set_grid_top_attach(int nAttach) override
2668 GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
2669 gtk_container_child_set(pParent, m_pWidget, "top-attach", nAttach, nullptr);
2672 virtual int get_grid_top_attach() const override
2674 GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
2675 gint nAttach(0);
2676 gtk_container_child_get(pParent, m_pWidget, "top-attach", &nAttach, nullptr);
2677 return nAttach;
2680 virtual void set_hexpand(bool bExpand) override
2682 gtk_widget_set_hexpand(m_pWidget, bExpand);
2685 virtual bool get_hexpand() const override
2687 return gtk_widget_get_hexpand(m_pWidget);
2690 virtual void set_vexpand(bool bExpand) override
2692 gtk_widget_set_vexpand(m_pWidget, bExpand);
2695 virtual bool get_vexpand() const override
2697 return gtk_widget_get_vexpand(m_pWidget);
2700 virtual void set_secondary(bool bSecondary) override
2702 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
2703 if (pParent && GTK_IS_BUTTON_BOX(pParent))
2704 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(pParent), m_pWidget, bSecondary);
2707 virtual void set_margin_top(int nMargin) override
2709 gtk_widget_set_margin_top(m_pWidget, nMargin);
2712 virtual void set_margin_bottom(int nMargin) override
2714 gtk_widget_set_margin_bottom(m_pWidget, nMargin);
2717 virtual void set_margin_left(int nMargin) override
2719 gtk_widget_set_margin_left(m_pWidget, nMargin);
2722 virtual void set_margin_right(int nMargin) override
2724 gtk_widget_set_margin_right(m_pWidget, nMargin);
2727 virtual int get_margin_top() const override
2729 return gtk_widget_get_margin_top(m_pWidget);
2732 virtual int get_margin_bottom() const override
2734 return gtk_widget_get_margin_bottom(m_pWidget);
2737 virtual int get_margin_left() const override
2739 return gtk_widget_get_margin_left(m_pWidget);
2742 virtual int get_margin_right() const override
2744 return gtk_widget_get_margin_right(m_pWidget);
2747 virtual void set_accessible_name(const OUString& rName) override
2749 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2750 if (!pAtkObject)
2751 return;
2752 atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
2755 virtual void set_accessible_description(const OUString& rDescription) override
2757 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2758 if (!pAtkObject)
2759 return;
2760 atk_object_set_description(pAtkObject, OUStringToOString(rDescription, RTL_TEXTENCODING_UTF8).getStr());
2763 virtual OUString get_accessible_name() const override
2765 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2766 const char* pStr = pAtkObject ? atk_object_get_name(pAtkObject) : nullptr;
2767 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2770 virtual OUString get_accessible_description() const override
2772 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2773 const char* pStr = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
2774 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2777 virtual void set_accessible_relation_labeled_by(weld::Widget* pLabel) override
2779 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2780 if (!pAtkObject)
2781 return;
2782 AtkObject *pAtkLabel = pLabel ? gtk_widget_get_accessible(dynamic_cast<GtkInstanceWidget&>(*pLabel).getWidget()) : nullptr;
2783 AtkRelationSet *pRelationSet = atk_object_ref_relation_set(pAtkObject);
2784 AtkRelation *pRelation = atk_relation_set_get_relation_by_type(pRelationSet, ATK_RELATION_LABELLED_BY);
2785 if (pRelation)
2786 atk_relation_set_remove(pRelationSet, pRelation);
2787 if (pAtkLabel)
2789 AtkObject *obj_array[1];
2790 obj_array[0] = pAtkLabel;
2791 pRelation = atk_relation_new(obj_array, 1, ATK_RELATION_LABELLED_BY);
2792 atk_relation_set_add(pRelationSet, pRelation);
2794 g_object_unref(pRelationSet);
2797 virtual void set_accessible_relation_label_for(weld::Widget* pLabeled) override
2799 AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
2800 if (!pAtkObject)
2801 return;
2802 AtkObject *pAtkLabeled = pLabeled ? gtk_widget_get_accessible(dynamic_cast<GtkInstanceWidget&>(*pLabeled).getWidget()) : nullptr;
2803 AtkRelationSet *pRelationSet = atk_object_ref_relation_set(pAtkObject);
2804 AtkRelation *pRelation = atk_relation_set_get_relation_by_type(pRelationSet, ATK_RELATION_LABEL_FOR);
2805 if (pRelation)
2806 atk_relation_set_remove(pRelationSet, pRelation);
2807 if (pAtkLabeled)
2809 AtkObject *obj_array[1];
2810 obj_array[0] = pAtkLabeled;
2811 pRelation = atk_relation_new(obj_array, 1, ATK_RELATION_LABEL_FOR);
2812 atk_relation_set_add(pRelationSet, pRelation);
2814 g_object_unref(pRelationSet);
2817 virtual bool get_extents_relative_to(const weld::Widget& rRelative, int& x, int &y, int& width, int &height) const override
2819 //for toplevel windows this is sadly futile under wayland, so we can't tell where a dialog is in order to allow
2820 //the document underneath to auto-scroll to place content in a visible location
2821 bool ret = gtk_widget_translate_coordinates(m_pWidget,
2822 dynamic_cast<const GtkInstanceWidget&>(rRelative).getWidget(),
2823 0, 0, &x, &y);
2824 width = gtk_widget_get_allocated_width(m_pWidget);
2825 height = gtk_widget_get_allocated_height(m_pWidget);
2826 return ret;
2829 virtual void set_tooltip_text(const OUString& rTip) override
2831 gtk_widget_set_tooltip_text(m_pWidget, OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
2834 virtual OUString get_tooltip_text() const override
2836 const gchar* pStr = gtk_widget_get_tooltip_text(m_pWidget);
2837 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
2840 virtual std::unique_ptr<weld::Container> weld_parent() const override;
2842 virtual OString get_buildable_name() const override
2844 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(m_pWidget));
2845 return OString(pStr, pStr ? strlen(pStr) : 0);
2848 virtual void set_help_id(const OString& rHelpId) override
2850 ::set_help_id(m_pWidget, rHelpId);
2853 virtual OString get_help_id() const override
2855 OString sRet = ::get_help_id(m_pWidget);
2856 if (sRet.isEmpty())
2857 sRet = OString("null");
2858 return sRet;
2861 GtkWidget* getWidget() const
2863 return m_pWidget;
2866 GtkWindow* getWindow()
2868 return GTK_WINDOW(gtk_widget_get_toplevel(m_pWidget));
2871 virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
2873 if (!m_nFocusInSignalId)
2874 m_nFocusInSignalId = g_signal_connect(m_pWidget, "focus-in-event", G_CALLBACK(signalFocusIn), this);
2875 weld::Widget::connect_focus_in(rLink);
2878 virtual void connect_mnemonic_activate(const Link<Widget&, bool>& rLink) override
2880 if (!m_nMnemonicActivateSignalId)
2881 m_nMnemonicActivateSignalId = g_signal_connect(m_pWidget, "mnemonic-activate", G_CALLBACK(signalMnemonicActivate), this);
2882 weld::Widget::connect_mnemonic_activate(rLink);
2885 virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
2887 if (!m_nFocusOutSignalId)
2888 m_nFocusOutSignalId = g_signal_connect(m_pWidget, "focus-out-event", G_CALLBACK(signalFocusOut), this);
2889 weld::Widget::connect_focus_out(rLink);
2892 virtual void connect_size_allocate(const Link<const Size&, void>& rLink) override
2894 m_nSizeAllocateSignalId = g_signal_connect(m_pWidget, "size_allocate", G_CALLBACK(signalSizeAllocate), this);
2895 weld::Widget::connect_size_allocate(rLink);
2898 virtual void signal_size_allocate(guint nWidth, guint nHeight)
2900 m_aSizeAllocateHdl.Call(Size(nWidth, nHeight));
2903 bool signal_key(const GdkEventKey* pEvent)
2905 if (pEvent->type == GDK_KEY_PRESS && m_aKeyPressHdl.IsSet())
2907 SolarMutexGuard aGuard;
2908 return m_aKeyPressHdl.Call(GtkToVcl(*pEvent));
2910 if (pEvent->type == GDK_KEY_RELEASE && m_aKeyReleaseHdl.IsSet())
2912 SolarMutexGuard aGuard;
2913 return m_aKeyReleaseHdl.Call(GtkToVcl(*pEvent));
2915 return false;
2918 virtual void grab_add() override
2920 gtk_grab_add(m_pWidget);
2923 virtual bool has_grab() const override
2925 return gtk_widget_has_grab(m_pWidget);
2928 virtual void grab_remove() override
2930 gtk_grab_remove(m_pWidget);
2933 virtual bool get_direction() const override
2935 return gtk_widget_get_direction(m_pWidget) == GTK_TEXT_DIR_RTL;
2938 virtual void set_direction(bool bRTL) override
2940 gtk_widget_set_direction(m_pWidget, bRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
2943 virtual void freeze() override
2945 gtk_widget_freeze_child_notify(m_pWidget);
2946 g_object_freeze_notify(G_OBJECT(m_pWidget));
2949 virtual void thaw() override
2951 g_object_thaw_notify(G_OBJECT(m_pWidget));
2952 gtk_widget_thaw_child_notify(m_pWidget);
2955 virtual css::uno::Reference<css::datatransfer::dnd::XDropTarget> get_drop_target() override
2957 if (!m_xDropTarget)
2959 m_xDropTarget.set(new GtkDropTarget);
2960 if (!gtk_drag_dest_get_track_motion(m_pWidget))
2962 gtk_drag_dest_set(m_pWidget, GtkDestDefaults(0), nullptr, 0, GdkDragAction(0));
2963 gtk_drag_dest_set_track_motion(m_pWidget, true);
2965 m_nDragMotionSignalId = g_signal_connect(m_pWidget, "drag-motion", G_CALLBACK(signalDragMotion), this);
2966 m_nDragDropSignalId = g_signal_connect(m_pWidget, "drag-drop", G_CALLBACK(signalDragDrop), this);
2967 m_nDragDropReceivedSignalId = g_signal_connect(m_pWidget, "drag-data-received", G_CALLBACK(signalDragDropReceived), this);
2968 m_nDragLeaveSignalId = g_signal_connect(m_pWidget, "drag-leave", G_CALLBACK(signalDragLeave), this);
2970 return m_xDropTarget.get();
2973 virtual void connect_get_property_tree(const Link<tools::JsonWriter&, void>& /*rLink*/) override
2975 //not implemented for the gtk variant
2978 virtual void set_stack_background() override
2980 do_set_background(Application::GetSettings().GetStyleSettings().GetWindowColor());
2983 virtual void set_title_background() override
2985 do_set_background(Application::GetSettings().GetStyleSettings().GetShadowColor());
2988 virtual void set_highlight_background() override
2990 do_set_background(Application::GetSettings().GetStyleSettings().GetHighlightColor());
2993 virtual void set_background(const Color& rColor) override
2995 do_set_background(rColor);
2998 virtual void set_toolbar_background() override
3000 // no-op
3003 virtual ~GtkInstanceWidget() override
3005 if (m_pDragCancelEvent)
3006 Application::RemoveUserEvent(m_pDragCancelEvent);
3007 if (m_nDragMotionSignalId)
3008 g_signal_handler_disconnect(m_pWidget, m_nDragMotionSignalId);
3009 if (m_nDragDropSignalId)
3010 g_signal_handler_disconnect(m_pWidget, m_nDragDropSignalId);
3011 if (m_nDragDropReceivedSignalId)
3012 g_signal_handler_disconnect(m_pWidget, m_nDragDropReceivedSignalId);
3013 if (m_nDragLeaveSignalId)
3014 g_signal_handler_disconnect(m_pWidget, m_nDragLeaveSignalId);
3015 if (m_nDragEndSignalId)
3016 g_signal_handler_disconnect(m_pWidget, m_nDragEndSignalId);
3017 if (m_nDragBeginSignalId)
3018 g_signal_handler_disconnect(m_pWidget, m_nDragBeginSignalId);
3019 if (m_nDragFailedSignalId)
3020 g_signal_handler_disconnect(m_pWidget, m_nDragFailedSignalId);
3021 if (m_nDragDataDeleteignalId)
3022 g_signal_handler_disconnect(m_pWidget, m_nDragDataDeleteignalId);
3023 if (m_nDragGetSignalId)
3024 g_signal_handler_disconnect(m_pWidget, m_nDragGetSignalId);
3025 if (m_nKeyPressSignalId)
3026 g_signal_handler_disconnect(m_pWidget, m_nKeyPressSignalId);
3027 if (m_nKeyReleaseSignalId)
3028 g_signal_handler_disconnect(m_pWidget, m_nKeyReleaseSignalId);
3029 if (m_nButtonPressSignalId)
3030 g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonPressSignalId);
3031 if (m_nMotionSignalId)
3032 g_signal_handler_disconnect(m_pMouseEventBox, m_nMotionSignalId);
3033 if (m_nLeaveSignalId)
3034 g_signal_handler_disconnect(m_pMouseEventBox, m_nLeaveSignalId);
3035 if (m_nEnterSignalId)
3036 g_signal_handler_disconnect(m_pMouseEventBox, m_nEnterSignalId);
3037 if (m_nButtonReleaseSignalId)
3038 g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonReleaseSignalId);
3039 if (m_nFocusInSignalId)
3040 g_signal_handler_disconnect(m_pWidget, m_nFocusInSignalId);
3041 if (m_nMnemonicActivateSignalId)
3042 g_signal_handler_disconnect(m_pWidget, m_nMnemonicActivateSignalId);
3043 if (m_nFocusOutSignalId)
3044 g_signal_handler_disconnect(m_pWidget, m_nFocusOutSignalId);
3045 if (m_nSizeAllocateSignalId)
3046 g_signal_handler_disconnect(m_pWidget, m_nSizeAllocateSignalId);
3048 do_set_background(COL_AUTO);
3050 if (m_pMouseEventBox && m_pMouseEventBox != m_pWidget)
3052 // put things back they way we found them
3053 GtkWidget* pParent = gtk_widget_get_parent(m_pMouseEventBox);
3055 g_object_ref(m_pWidget);
3056 gtk_container_remove(GTK_CONTAINER(m_pMouseEventBox), m_pWidget);
3058 gtk_widget_destroy(m_pMouseEventBox);
3060 gtk_container_add(GTK_CONTAINER(pParent), m_pWidget);
3061 g_object_unref(m_pWidget);
3064 if (m_bTakeOwnership)
3065 gtk_widget_destroy(m_pWidget);
3066 else
3067 g_object_unref(m_pWidget);
3070 virtual void disable_notify_events()
3072 if (m_nFocusInSignalId)
3073 g_signal_handler_block(m_pWidget, m_nFocusInSignalId);
3074 if (m_nMnemonicActivateSignalId)
3075 g_signal_handler_block(m_pWidget, m_nMnemonicActivateSignalId);
3076 if (m_nFocusOutSignalId)
3077 g_signal_handler_block(m_pWidget, m_nFocusOutSignalId);
3078 if (m_nSizeAllocateSignalId)
3079 g_signal_handler_block(m_pWidget, m_nSizeAllocateSignalId);
3082 virtual void enable_notify_events()
3084 if (m_nSizeAllocateSignalId)
3085 g_signal_handler_unblock(m_pWidget, m_nSizeAllocateSignalId);
3086 if (m_nFocusOutSignalId)
3087 g_signal_handler_unblock(m_pWidget, m_nFocusOutSignalId);
3088 if (m_nMnemonicActivateSignalId)
3089 g_signal_handler_unblock(m_pWidget, m_nMnemonicActivateSignalId);
3090 if (m_nFocusInSignalId)
3091 g_signal_handler_unblock(m_pWidget, m_nFocusInSignalId);
3094 virtual void help_hierarchy_foreach(const std::function<bool(const OString&)>& func) override;
3096 virtual OUString strip_mnemonic(const OUString &rLabel) const override
3098 return rLabel.replaceFirst("_", "");
3101 virtual VclPtr<VirtualDevice> create_virtual_device() const override
3103 // create with no separate alpha layer like everything sane does
3104 auto xRet = VclPtr<VirtualDevice>::Create();
3105 xRet->SetBackground(COL_TRANSPARENT);
3106 return xRet;
3109 virtual void draw(OutputDevice& rOutput, const tools::Rectangle& rRect) override
3111 // detect if we have to manually setup its size
3112 bool bAlreadyRealized = gtk_widget_get_realized(m_pWidget);
3113 // has to be visible for draw to work
3114 bool bAlreadyVisible = gtk_widget_get_visible(m_pWidget);
3115 // has to be mapped for draw to work
3116 bool bAlreadyMapped = gtk_widget_get_mapped(m_pWidget);
3118 if (!bAlreadyRealized)
3119 gtk_widget_realize(m_pWidget);
3120 if (!bAlreadyVisible)
3121 gtk_widget_show(m_pWidget);
3122 if (!bAlreadyMapped)
3123 gtk_widget_map(m_pWidget);
3125 assert(gtk_widget_is_drawable(m_pWidget)); // all that should result in this holding
3127 // turn off animations, otherwise we get a frame of an animation sequence
3128 gboolean bAnimations;
3129 GtkSettings* pSettings = gtk_widget_get_settings(m_pWidget);
3130 g_object_get(pSettings, "gtk-enable-animations", &bAnimations, nullptr);
3131 if (bAnimations)
3132 g_object_set(pSettings, "gtk-enable-animations", false, nullptr);
3134 Size aSize(rRect.GetSize());
3136 GtkAllocation aOrigAllocation;
3137 gtk_widget_get_allocation(m_pWidget, &aOrigAllocation);
3139 GtkAllocation aNewAllocation {aOrigAllocation.x,
3140 aOrigAllocation.y,
3141 static_cast<int>(aSize.Width()),
3142 static_cast<int>(aSize.Height()) };
3143 gtk_widget_size_allocate(m_pWidget, &aNewAllocation);
3145 if (GTK_IS_CONTAINER(m_pWidget))
3146 gtk_container_resize_children(GTK_CONTAINER(m_pWidget));
3148 VclPtr<VirtualDevice> xOutput(VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT));
3149 xOutput->SetOutputSizePixel(aSize);
3150 xOutput->DrawOutDev(Point(), aSize, rRect.TopLeft(), aSize, rOutput);
3152 cairo_surface_t* pSurface = get_underlying_cairo_surface(*xOutput);
3153 cairo_t* cr = cairo_create(pSurface);
3155 gtk_widget_draw(m_pWidget, cr);
3157 cairo_destroy(cr);
3159 gtk_widget_set_allocation(m_pWidget, &aOrigAllocation);
3160 gtk_widget_size_allocate(m_pWidget, &aOrigAllocation);
3162 rOutput.DrawOutDev(rRect.TopLeft(), aSize, Point(), aSize, *xOutput);
3164 if (bAnimations)
3165 g_object_set(pSettings, "gtk-enable-animations", true, nullptr);
3167 if (!bAlreadyMapped)
3168 gtk_widget_unmap(m_pWidget);
3169 if (!bAlreadyVisible)
3170 gtk_widget_hide(m_pWidget);
3171 if (!bAlreadyRealized)
3172 gtk_widget_unrealize(m_pWidget);
3178 IMPL_LINK(GtkInstanceWidget, async_drag_cancel, void*, arg, void)
3180 m_pDragCancelEvent = nullptr;
3181 GdkDragContext* context = static_cast<GdkDragContext*>(arg);
3183 // tdf#132477 simply calling gtk_drag_cancel on the treeview dnd under X
3184 // doesn't seem to work as hoped for (though under wayland all is well).
3185 // Under X the next (allowed) drag effort doesn't work to drop anything,
3186 // but a then repeated attempt does.
3187 // emitting cancel to get gtk to cancel the drag for us does work as hoped for.
3188 g_signal_emit_by_name(context, "cancel", 0, GDK_DRAG_CANCEL_USER_CANCELLED);
3190 g_object_unref(context);
3193 namespace
3195 OString MapToGtkAccelerator(const OUString &rStr)
3197 return OUStringToOString(rStr.replaceFirst("~", "_"), RTL_TEXTENCODING_UTF8);
3200 OUString get_label(GtkLabel* pLabel)
3202 const gchar* pStr = gtk_label_get_label(pLabel);
3203 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
3206 void set_label(GtkLabel* pLabel, const OUString& rText)
3208 gtk_label_set_label(pLabel, MapToGtkAccelerator(rText).getStr());
3211 OUString get_label(GtkButton* pButton)
3213 const gchar* pStr = gtk_button_get_label(pButton);
3214 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
3217 void set_label(GtkButton* pButton, const OUString& rText)
3219 gtk_button_set_label(pButton, MapToGtkAccelerator(rText).getStr());
3222 OUString get_title(GtkWindow* pWindow)
3224 const gchar* pStr = gtk_window_get_title(pWindow);
3225 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
3228 void set_title(GtkWindow* pWindow, const OUString& rTitle)
3230 gtk_window_set_title(pWindow, OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
3233 OUString get_primary_text(GtkMessageDialog* pMessageDialog)
3235 gchar* pText = nullptr;
3236 g_object_get(G_OBJECT(pMessageDialog), "text", &pText, nullptr);
3237 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
3240 void set_primary_text(GtkMessageDialog* pMessageDialog, const OUString& rText)
3242 g_object_set(G_OBJECT(pMessageDialog), "text",
3243 OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
3244 nullptr);
3247 void set_secondary_text(GtkMessageDialog* pMessageDialog, const OUString& rText)
3249 g_object_set(G_OBJECT(pMessageDialog), "secondary-text",
3250 OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
3251 nullptr);
3254 OUString get_secondary_text(GtkMessageDialog* pMessageDialog)
3256 gchar* pText = nullptr;
3257 g_object_get(G_OBJECT(pMessageDialog), "secondary-text", &pText, nullptr);
3258 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
3262 namespace
3264 GdkPixbuf* load_icon_from_stream(SvMemoryStream& rStream)
3266 auto nLength = rStream.TellEnd();
3267 if (!nLength)
3268 return nullptr;
3269 const guchar* pData = static_cast<const guchar*>(rStream.GetData());
3270 assert((*pData == 137 || *pData == '<') && "if we want to support more than png or svg this function must change");
3271 // if we know the image type, it's a little faster to hand the type over and skip the type detection.
3272 GdkPixbufLoader *pixbuf_loader = gdk_pixbuf_loader_new_with_type(*pData == 137 ? "png" : "svg", nullptr);
3273 gdk_pixbuf_loader_write(pixbuf_loader, pData, nLength, nullptr);
3274 gdk_pixbuf_loader_close(pixbuf_loader, nullptr);
3275 GdkPixbuf* pixbuf = gdk_pixbuf_loader_get_pixbuf(pixbuf_loader);
3276 if (pixbuf)
3277 g_object_ref(pixbuf);
3278 g_object_unref(pixbuf_loader);
3279 return pixbuf;
3282 GdkPixbuf* load_icon_by_name_theme_lang(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
3284 auto xMemStm = ImageTree::get().getImageStream(rIconName, rIconTheme, rUILang);
3285 if (!xMemStm)
3286 return nullptr;
3287 return load_icon_from_stream(*xMemStm);
3291 GdkPixbuf* load_icon_by_name(const OUString& rIconName)
3293 OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
3294 OUString sUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
3295 return load_icon_by_name_theme_lang(rIconName, sIconTheme, sUILang);
3298 namespace
3300 GdkPixbuf* getPixbuf(const css::uno::Reference<css::graphic::XGraphic>& rImage)
3302 Image aImage(rImage);
3304 OUString sStock(aImage.GetStock());
3305 if (!sStock.isEmpty())
3306 return load_icon_by_name(sStock);
3308 std::unique_ptr<SvMemoryStream> xMemStm(new SvMemoryStream);
3310 css::uno::Sequence<css::beans::PropertyValue> aFilterData(1);
3311 aFilterData[0].Name = "Compression";
3312 // We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed.
3313 aFilterData[0].Value <<= sal_Int32(1);
3315 vcl::PNGWriter aWriter(aImage.GetBitmapEx(), &aFilterData);
3316 aWriter.Write(*xMemStm);
3318 return load_icon_from_stream(*xMemStm);
3321 GdkPixbuf* getPixbuf(const VirtualDevice& rDevice)
3323 Size aSize(rDevice.GetOutputSizePixel());
3324 cairo_surface_t* orig_surface = get_underlying_cairo_surface(rDevice);
3325 double m_fXScale, m_fYScale;
3326 dl_cairo_surface_get_device_scale(orig_surface, &m_fXScale, &m_fYScale);
3328 cairo_surface_t* surface;
3329 if (m_fXScale != 1.0 || m_fYScale != -1)
3331 surface = cairo_surface_create_similar_image(orig_surface,
3332 CAIRO_FORMAT_ARGB32,
3333 aSize.Width(),
3334 aSize.Height());
3335 cairo_t* cr = cairo_create(surface);
3336 cairo_set_source_surface(cr, orig_surface, 0, 0);
3337 cairo_paint(cr);
3338 cairo_destroy(cr);
3340 else
3341 surface = orig_surface;
3343 GdkPixbuf* pRet = gdk_pixbuf_get_from_surface(surface, 0, 0, aSize.Width(), aSize.Height());
3345 if (surface != orig_surface)
3346 cairo_surface_destroy(surface);
3348 return pRet;
3351 GdkPixbuf* getPixbuf(const OUString& rIconName)
3353 if (rIconName.isEmpty())
3354 return nullptr;
3356 GdkPixbuf* pixbuf = nullptr;
3358 if (rIconName.lastIndexOf('.') != rIconName.getLength() - 4)
3360 assert((rIconName== "dialog-warning" || rIconName== "dialog-error" || rIconName== "dialog-information") &&
3361 "unknown stock image");
3363 GError *error = nullptr;
3364 GtkIconTheme *icon_theme = gtk_icon_theme_get_default();
3365 pixbuf = gtk_icon_theme_load_icon(icon_theme, OUStringToOString(rIconName, RTL_TEXTENCODING_UTF8).getStr(),
3366 16, GTK_ICON_LOOKUP_USE_BUILTIN, &error);
3368 else
3370 const AllSettings& rSettings = Application::GetSettings();
3371 pixbuf = load_icon_by_name_theme_lang(rIconName,
3372 rSettings.GetStyleSettings().DetermineIconTheme(),
3373 rSettings.GetUILanguageTag().getBcp47());
3376 return pixbuf;
3379 GtkWidget* image_new_from_virtual_device(const VirtualDevice& rImageSurface)
3381 GtkWidget* pImage = nullptr;
3382 cairo_surface_t* surface = get_underlying_cairo_surface(rImageSurface);
3384 Size aSize(rImageSurface.GetOutputSizePixel());
3385 cairo_surface_t* target = cairo_surface_create_similar(surface,
3386 cairo_surface_get_content(surface),
3387 aSize.Width(),
3388 aSize.Height());
3390 cairo_t* cr = cairo_create(target);
3391 cairo_set_source_surface(cr, surface, 0, 0);
3392 cairo_paint(cr);
3393 cairo_destroy(cr);
3394 pImage = gtk_image_new_from_surface(target);
3395 cairo_surface_destroy(target);
3396 return pImage;
3399 class MenuHelper
3401 protected:
3402 GtkMenu* m_pMenu;
3403 bool m_bTakeOwnership;
3404 std::map<OString, GtkMenuItem*> m_aMap;
3405 private:
3407 static void collect(GtkWidget* pItem, gpointer widget)
3409 GtkMenuItem* pMenuItem = GTK_MENU_ITEM(pItem);
3410 if (GtkWidget* pSubMenu = gtk_menu_item_get_submenu(pMenuItem))
3411 gtk_container_foreach(GTK_CONTAINER(pSubMenu), collect, widget);
3412 MenuHelper* pThis = static_cast<MenuHelper*>(widget);
3413 pThis->add_to_map(pMenuItem);
3416 static void signalActivate(GtkMenuItem* pItem, gpointer widget)
3418 MenuHelper* pThis = static_cast<MenuHelper*>(widget);
3419 SolarMutexGuard aGuard;
3420 pThis->signal_activate(pItem);
3423 virtual void signal_activate(GtkMenuItem* pItem) = 0;
3425 public:
3426 MenuHelper(GtkMenu* pMenu, bool bTakeOwnership)
3427 : m_pMenu(pMenu)
3428 , m_bTakeOwnership(bTakeOwnership)
3430 if (!m_pMenu)
3431 return;
3432 gtk_container_foreach(GTK_CONTAINER(m_pMenu), collect, this);
3435 void add_to_map(GtkMenuItem* pMenuItem)
3437 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pMenuItem));
3438 OString id(pStr, pStr ? strlen(pStr) : 0);
3439 m_aMap[id] = pMenuItem;
3440 g_signal_connect(pMenuItem, "activate", G_CALLBACK(signalActivate), this);
3443 void remove_from_map(GtkMenuItem* pMenuItem)
3445 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pMenuItem));
3446 OString id(pStr, pStr ? strlen(pStr) : 0);
3447 auto iter = m_aMap.find(id);
3448 g_signal_handlers_disconnect_by_data(pMenuItem, this);
3449 m_aMap.erase(iter);
3452 void disable_item_notify_events()
3454 for (auto& a : m_aMap)
3455 g_signal_handlers_block_by_func(a.second, reinterpret_cast<void*>(signalActivate), this);
3458 void enable_item_notify_events()
3460 for (auto& a : m_aMap)
3461 g_signal_handlers_unblock_by_func(a.second, reinterpret_cast<void*>(signalActivate), this);
3464 void insert_item(int pos, const OUString& rId, const OUString& rStr,
3465 const OUString* pIconName, const VirtualDevice* pImageSurface,
3466 TriState eCheckRadioFalse)
3468 GtkWidget* pImage = nullptr;
3469 if (pIconName && !pIconName->isEmpty())
3471 GdkPixbuf* pixbuf = load_icon_by_name(*pIconName);
3472 if (!pixbuf)
3474 pImage = gtk_image_new_from_pixbuf(pixbuf);
3475 g_object_unref(pixbuf);
3478 else if (pImageSurface)
3479 pImage = image_new_from_virtual_device(*pImageSurface);
3481 GtkWidget *pItem;
3482 if (pImage)
3484 GtkWidget *pBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
3485 GtkWidget *pLabel = gtk_label_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
3486 pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new() : gtk_menu_item_new();
3487 gtk_container_add(GTK_CONTAINER(pBox), pImage);
3488 gtk_container_add(GTK_CONTAINER(pBox), pLabel);
3489 gtk_container_add(GTK_CONTAINER(pItem), pBox);
3490 gtk_widget_show_all(pItem);
3492 else
3494 pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr())
3495 : gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
3498 if (eCheckRadioFalse == TRISTATE_FALSE)
3499 gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(pItem), true);
3501 gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
3502 gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
3503 gtk_widget_show(pItem);
3504 add_to_map(GTK_MENU_ITEM(pItem));
3505 if (pos != -1)
3506 gtk_menu_reorder_child(m_pMenu, pItem, pos);
3509 void insert_separator(int pos, const OUString& rId)
3511 GtkWidget* pItem = gtk_separator_menu_item_new();
3512 gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
3513 gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
3514 gtk_widget_show(pItem);
3515 add_to_map(GTK_MENU_ITEM(pItem));
3516 if (pos != -1)
3517 gtk_menu_reorder_child(m_pMenu, pItem, pos);
3520 void remove_item(const OString& rIdent)
3522 GtkMenuItem* pMenuItem = m_aMap[rIdent];
3523 remove_from_map(pMenuItem);
3524 gtk_widget_destroy(GTK_WIDGET(pMenuItem));
3527 void set_item_sensitive(const OString& rIdent, bool bSensitive)
3529 gtk_widget_set_sensitive(GTK_WIDGET(m_aMap[rIdent]), bSensitive);
3532 void set_item_active(const OString& rIdent, bool bActive)
3534 disable_item_notify_events();
3535 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(m_aMap[rIdent]), bActive);
3536 enable_item_notify_events();
3539 bool get_item_active(const OString& rIdent) const
3541 return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(m_aMap.find(rIdent)->second));
3544 void set_item_label(const OString& rIdent, const OUString& rText)
3546 gtk_menu_item_set_label(m_aMap[rIdent], MapToGtkAccelerator(rText).getStr());
3549 OUString get_item_label(const OString& rIdent) const
3551 const gchar* pText = gtk_menu_item_get_label(m_aMap.find(rIdent)->second);
3552 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
3555 void set_item_help_id(const OString& rIdent, const OString& rHelpId)
3557 set_help_id(GTK_WIDGET(m_aMap[rIdent]), rHelpId);
3560 OString get_item_help_id(const OString& rIdent) const
3562 return get_help_id(GTK_WIDGET(m_aMap.find(rIdent)->second));
3565 void set_item_visible(const OString& rIdent, bool bShow)
3567 GtkWidget* pWidget = GTK_WIDGET(m_aMap[rIdent]);
3568 if (bShow)
3569 gtk_widget_show(pWidget);
3570 else
3571 gtk_widget_hide(pWidget);
3574 void clear_items()
3576 for (const auto& a : m_aMap)
3578 GtkMenuItem* pMenuItem = a.second;
3579 g_signal_handlers_disconnect_by_data(pMenuItem, this);
3580 gtk_widget_destroy(GTK_WIDGET(pMenuItem));
3582 m_aMap.clear();
3585 GtkMenu* getMenu() const
3587 return m_pMenu;
3590 virtual ~MenuHelper()
3592 for (auto& a : m_aMap)
3593 g_signal_handlers_disconnect_by_data(a.second, this);
3594 if (m_bTakeOwnership)
3595 gtk_widget_destroy(GTK_WIDGET(m_pMenu));
3599 class GtkInstanceSizeGroup : public weld::SizeGroup
3601 private:
3602 GtkSizeGroup* m_pGroup;
3603 public:
3604 GtkInstanceSizeGroup()
3605 : m_pGroup(gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL))
3608 virtual void add_widget(weld::Widget* pWidget) override
3610 GtkInstanceWidget* pVclWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
3611 assert(pVclWidget);
3612 gtk_size_group_add_widget(m_pGroup, pVclWidget->getWidget());
3614 virtual void set_mode(VclSizeGroupMode eVclMode) override
3616 GtkSizeGroupMode eGtkMode(GTK_SIZE_GROUP_NONE);
3617 switch (eVclMode)
3619 case VclSizeGroupMode::NONE:
3620 eGtkMode = GTK_SIZE_GROUP_NONE;
3621 break;
3622 case VclSizeGroupMode::Horizontal:
3623 eGtkMode = GTK_SIZE_GROUP_HORIZONTAL;
3624 break;
3625 case VclSizeGroupMode::Vertical:
3626 eGtkMode = GTK_SIZE_GROUP_VERTICAL;
3627 break;
3628 case VclSizeGroupMode::Both:
3629 eGtkMode = GTK_SIZE_GROUP_BOTH;
3630 break;
3632 gtk_size_group_set_mode(m_pGroup, eGtkMode);
3634 virtual ~GtkInstanceSizeGroup() override
3636 g_object_unref(m_pGroup);
3640 class ChildFrame : public WorkWindow
3642 private:
3643 Idle maLayoutIdle;
3645 DECL_LINK(ImplHandleLayoutTimerHdl, Timer*, void);
3646 public:
3647 ChildFrame(vcl::Window* pParent, WinBits nStyle)
3648 : WorkWindow(pParent, nStyle)
3650 maLayoutIdle.SetPriority(TaskPriority::RESIZE);
3651 maLayoutIdle.SetInvokeHandler( LINK( this, ChildFrame, ImplHandleLayoutTimerHdl ) );
3652 maLayoutIdle.SetDebugName( "ChildFrame maLayoutIdle" );
3655 virtual void dispose() override
3657 maLayoutIdle.Stop();
3658 WorkWindow::dispose();
3661 virtual void queue_resize(StateChangedType eReason = StateChangedType::Layout) override
3663 WorkWindow::queue_resize(eReason);
3664 if (maLayoutIdle.IsActive())
3665 return;
3666 maLayoutIdle.Start();
3669 virtual void Resize() override
3671 WorkWindow::Resize();
3672 queue_resize();
3676 IMPL_LINK_NOARG(ChildFrame, ImplHandleLayoutTimerHdl, Timer*, void)
3678 if (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild))
3679 pChild->SetPosSizePixel(Point(0, 0), GetSizePixel());
3682 class GtkInstanceContainer : public GtkInstanceWidget, public virtual weld::Container
3684 private:
3685 GtkContainer* m_pContainer;
3687 static void implResetDefault(GtkWidget *pWidget, gpointer user_data)
3689 if (GTK_IS_BUTTON(pWidget))
3690 g_object_set(G_OBJECT(pWidget), "has-default", false, nullptr);
3691 if (GTK_IS_CONTAINER(pWidget))
3692 gtk_container_forall(GTK_CONTAINER(pWidget), implResetDefault, user_data);
3695 public:
3696 GtkInstanceContainer(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
3697 : GtkInstanceWidget(GTK_WIDGET(pContainer), pBuilder, bTakeOwnership)
3698 , m_pContainer(pContainer)
3702 GtkContainer* getContainer() { return m_pContainer; }
3704 virtual void move(weld::Widget* pWidget, weld::Container* pNewParent) override
3706 GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
3707 assert(pGtkWidget);
3708 GtkWidget* pChild = pGtkWidget->getWidget();
3709 g_object_ref(pChild);
3710 gtk_container_remove(getContainer(), pChild);
3712 GtkInstanceContainer* pNewGtkParent = dynamic_cast<GtkInstanceContainer*>(pNewParent);
3713 assert(!pNewParent || pNewGtkParent);
3714 if (pNewGtkParent)
3715 gtk_container_add(pNewGtkParent->getContainer(), pChild);
3716 g_object_unref(pChild);
3719 virtual void recursively_unset_default_buttons() override
3721 implResetDefault(GTK_WIDGET(m_pContainer), nullptr);
3724 virtual css::uno::Reference<css::awt::XWindow> CreateChildFrame() override
3726 // This will cause a GtkSalFrame to be created. With WB_SYSTEMCHILDWINDOW set it
3727 // will create a toplevel GtkEventBox window
3728 auto xEmbedWindow = VclPtr<ChildFrame>::Create(ImplGetDefaultWindow(), WB_SYSTEMCHILDWINDOW | WB_DIALOGCONTROL | WB_CHILDDLGCTRL);
3729 SalFrame* pFrame = xEmbedWindow->ImplGetFrame();
3730 GtkSalFrame* pGtkFrame = dynamic_cast<GtkSalFrame*>(pFrame);
3731 assert(pGtkFrame);
3733 // relocate that toplevel GtkEventBox into this widget
3734 GtkWidget* pWindow = pGtkFrame->getWindow();
3736 GtkWidget* pParent = gtk_widget_get_parent(pWindow);
3738 g_object_ref(pWindow);
3739 gtk_container_remove(GTK_CONTAINER(pParent), pWindow);
3740 gtk_container_add(m_pContainer, pWindow);
3741 gtk_container_child_set(m_pContainer, pWindow, "expand", true, "fill", true, nullptr);
3742 gtk_widget_set_hexpand(pWindow, true);
3743 gtk_widget_set_vexpand(pWindow, true);
3744 gtk_widget_realize(pWindow);
3745 gtk_widget_set_can_focus(pWindow, true);
3746 g_object_unref(pWindow);
3748 // NoActivate otherwise Show grab focus to this widget
3749 xEmbedWindow->Show(true, ShowFlags::NoActivate);
3750 css::uno::Reference<css::awt::XWindow> xWindow(xEmbedWindow->GetComponentInterface(), css::uno::UNO_QUERY);
3751 return xWindow;
3757 std::unique_ptr<weld::Container> GtkInstanceWidget::weld_parent() const
3759 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
3760 if (!pParent)
3761 return nullptr;
3762 return std::make_unique<GtkInstanceContainer>(GTK_CONTAINER(pParent), m_pBuilder, false);
3765 namespace {
3767 struct ButtonOrder
3769 const char * m_aType;
3770 int m_nPriority;
3773 int getButtonPriority(const OString &rType)
3775 static const size_t N_TYPES = 7;
3776 static const ButtonOrder aDiscardCancelSave[N_TYPES] =
3778 { "/discard", 0 },
3779 { "/cancel", 1 },
3780 { "/no", 2 },
3781 { "/open", 3 },
3782 { "/save", 3 },
3783 { "/yes", 3 },
3784 { "/ok", 3 }
3787 static const ButtonOrder aSaveDiscardCancel[N_TYPES] =
3789 { "/open", 0 },
3790 { "/save", 0 },
3791 { "/yes", 0 },
3792 { "/ok", 0 },
3793 { "/discard", 1 },
3794 { "/no", 1 },
3795 { "/cancel", 2 }
3798 const ButtonOrder* pOrder = &aDiscardCancelSave[0];
3800 const OUString &rEnv = Application::GetDesktopEnvironment();
3802 if (rEnv.equalsIgnoreAsciiCase("windows") ||
3803 rEnv.equalsIgnoreAsciiCase("tde") ||
3804 rEnv.startsWithIgnoreAsciiCase("kde"))
3806 pOrder = &aSaveDiscardCancel[0];
3809 for (size_t i = 0; i < N_TYPES; ++i, ++pOrder)
3811 if (rType.endsWith(pOrder->m_aType))
3812 return pOrder->m_nPriority;
3815 return -1;
3818 bool sortButtons(const GtkWidget* pA, const GtkWidget* pB)
3820 //order within groups according to platform rules
3821 return getButtonPriority(::get_help_id(pA)) < getButtonPriority(::get_help_id(pB));
3824 void sort_native_button_order(GtkBox* pContainer)
3826 std::vector<GtkWidget*> aChildren;
3827 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pContainer));
3828 for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
3829 aChildren.push_back(static_cast<GtkWidget*>(pChild->data));
3830 g_list_free(pChildren);
3832 //sort child order within parent so that we match the platform button order
3833 std::stable_sort(aChildren.begin(), aChildren.end(), sortButtons);
3835 for (size_t pos = 0; pos < aChildren.size(); ++pos)
3836 gtk_box_reorder_child(pContainer, aChildren[pos], pos);
3839 class GtkInstanceBox : public GtkInstanceContainer, public virtual weld::Box
3841 private:
3842 GtkBox* m_pBox;
3844 public:
3845 GtkInstanceBox(GtkBox* pBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
3846 : GtkInstanceContainer(GTK_CONTAINER(pBox), pBuilder, bTakeOwnership)
3847 , m_pBox(pBox)
3851 virtual void reorder_child(weld::Widget* pWidget, int nNewPosition) override
3853 GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
3854 assert(pGtkWidget);
3855 GtkWidget* pChild = pGtkWidget->getWidget();
3856 gtk_box_reorder_child(m_pBox, pChild, nNewPosition);
3859 virtual void sort_native_button_order() override
3861 ::sort_native_button_order(m_pBox);
3865 void set_cursor(GtkWidget* pWidget, const char *pName)
3867 if (!gtk_widget_get_realized(pWidget))
3868 gtk_widget_realize(pWidget);
3869 GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
3870 GdkCursor *pCursor = pName ? gdk_cursor_new_from_name(pDisplay, pName) : nullptr;
3871 gdk_window_set_cursor(gtk_widget_get_window(pWidget), pCursor);
3872 gdk_display_flush(pDisplay);
3873 if (pCursor)
3874 g_object_unref(pCursor);
3878 namespace
3880 Point get_csd_offset(GtkWidget* pTopLevel)
3882 // try and omit drawing CSD under wayland
3883 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pTopLevel));
3884 GList* pChild = g_list_first(pChildren);
3886 int x, y;
3887 gtk_widget_translate_coordinates(GTK_WIDGET(pChild->data),
3888 GTK_WIDGET(pTopLevel),
3889 0, 0, &x, &y);
3891 int innerborder = gtk_container_get_border_width(GTK_CONTAINER(pChild->data));
3892 g_list_free(pChildren);
3894 int outerborder = gtk_container_get_border_width(GTK_CONTAINER(pTopLevel));
3895 int totalborder = outerborder + innerborder;
3896 x -= totalborder;
3897 y -= totalborder;
3899 return Point(x, y);
3902 void do_collect_screenshot_data(GtkWidget* pItem, gpointer data)
3904 GtkWidget* pTopLevel = gtk_widget_get_toplevel(pItem);
3906 int x, y;
3907 gtk_widget_translate_coordinates(pItem, pTopLevel, 0, 0, &x, &y);
3909 Point aOffset = get_csd_offset(pTopLevel);
3911 GtkAllocation alloc;
3912 gtk_widget_get_allocation(pItem, &alloc);
3914 const basegfx::B2IPoint aCurrentTopLeft(x - aOffset.X(), y - aOffset.Y());
3915 const basegfx::B2IRange aCurrentRange(aCurrentTopLeft, aCurrentTopLeft + basegfx::B2IPoint(alloc.width, alloc.height));
3917 if (!aCurrentRange.isEmpty())
3919 weld::ScreenShotCollection* pCollection = static_cast<weld::ScreenShotCollection*>(data);
3920 pCollection->emplace_back(::get_help_id(pItem), aCurrentRange);
3923 if (GTK_IS_CONTAINER(pItem))
3924 gtk_container_forall(GTK_CONTAINER(pItem), do_collect_screenshot_data, data);
3927 tools::Rectangle get_monitor_workarea(GtkWidget* pWindow)
3929 GdkScreen* pScreen = gtk_widget_get_screen(pWindow);
3930 gint nMonitor = gdk_screen_get_monitor_at_window(pScreen, gtk_widget_get_window(pWindow));
3931 GdkRectangle aRect;
3932 gdk_screen_get_monitor_workarea(pScreen, nMonitor, &aRect);
3933 return tools::Rectangle(aRect.x, aRect.y, aRect.x + aRect.width, aRect.y + aRect.height);
3937 class GtkInstanceWindow : public GtkInstanceContainer, public virtual weld::Window
3939 private:
3940 GtkWindow* m_pWindow;
3941 rtl::Reference<SalGtkXWindow> m_xWindow; //uno api
3942 gulong m_nToplevelFocusChangedSignalId;
3944 static gboolean help_pressed(GtkAccelGroup*, GObject*, guint, GdkModifierType, gpointer widget)
3946 GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
3947 pThis->help();
3948 return true;
3951 static void signalToplevelFocusChanged(GtkWindow*, GParamSpec*, gpointer widget)
3953 GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
3954 pThis->signal_toplevel_focus_changed();
3957 bool isPositioningAllowed() const
3959 bool bPositioningAllowed = true;
3960 #if defined(GDK_WINDOWING_WAYLAND)
3961 // no X/Y positioning under Wayland
3962 GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
3963 bPositioningAllowed = !DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay);
3964 #endif
3965 return bPositioningAllowed;
3968 protected:
3969 void help();
3970 public:
3971 GtkInstanceWindow(GtkWindow* pWindow, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
3972 : GtkInstanceContainer(GTK_CONTAINER(pWindow), pBuilder, bTakeOwnership)
3973 , m_pWindow(pWindow)
3974 , m_nToplevelFocusChangedSignalId(0)
3976 const bool bIsFrameWeld = pBuilder == nullptr;
3977 if (!bIsFrameWeld)
3979 //hook up F1 to show help
3980 GtkAccelGroup *pGroup = gtk_accel_group_new();
3981 GClosure* closure = g_cclosure_new(G_CALLBACK(help_pressed), this, nullptr);
3982 gtk_accel_group_connect(pGroup, GDK_KEY_F1, static_cast<GdkModifierType>(0), GTK_ACCEL_LOCKED, closure);
3983 gtk_window_add_accel_group(pWindow, pGroup);
3987 virtual void set_title(const OUString& rTitle) override
3989 ::set_title(m_pWindow, rTitle);
3992 virtual OUString get_title() const override
3994 return ::get_title(m_pWindow);
3997 virtual css::uno::Reference<css::awt::XWindow> GetXWindow() override
3999 if (!m_xWindow.is())
4000 m_xWindow.set(new SalGtkXWindow(this, m_pWidget));
4001 return css::uno::Reference<css::awt::XWindow>(m_xWindow.get());
4004 virtual void set_busy_cursor(bool bBusy) override
4006 set_cursor(m_pWidget, bBusy ? "progress" : nullptr);
4009 virtual void set_modal(bool bModal) override
4011 gtk_window_set_modal(m_pWindow, bModal);
4014 virtual bool get_modal() const override
4016 return gtk_window_get_modal(m_pWindow);
4019 virtual void resize_to_request() override
4021 gtk_window_resize(m_pWindow, 1, 1);
4024 virtual void window_move(int x, int y) override
4026 gtk_window_move(m_pWindow, x, y);
4029 virtual SystemEnvData get_system_data() const override
4031 assert(false && "nothing should call this impl, yet anyway, if ever");
4032 return SystemEnvData();
4035 virtual Size get_size() const override
4037 int current_width, current_height;
4038 gtk_window_get_size(m_pWindow, &current_width, &current_height);
4039 return Size(current_width, current_height);
4042 virtual Point get_position() const override
4044 int current_x, current_y;
4045 gtk_window_get_position(m_pWindow, &current_x, &current_y);
4046 return Point(current_x, current_y);
4049 virtual tools::Rectangle get_monitor_workarea() const override
4051 return ::get_monitor_workarea(GTK_WIDGET(m_pWindow));
4054 virtual void set_centered_on_parent(bool bTrackGeometryRequests) override
4056 if (bTrackGeometryRequests)
4057 gtk_window_set_position(m_pWindow, GTK_WIN_POS_CENTER_ALWAYS);
4058 else
4059 gtk_window_set_position(m_pWindow, GTK_WIN_POS_CENTER_ON_PARENT);
4062 virtual bool get_resizable() const override
4064 return gtk_window_get_resizable(m_pWindow);
4067 virtual bool has_toplevel_focus() const override
4069 return gtk_window_has_toplevel_focus(m_pWindow);
4072 virtual void present() override
4074 gtk_window_present(m_pWindow);
4077 virtual void set_window_state(const OString& rStr) override
4079 WindowStateData aData;
4080 ImplWindowStateFromStr( aData, rStr );
4082 auto nMask = aData.GetMask();
4083 auto nState = aData.GetState() & WindowStateState::SystemMask;
4085 if (nMask & WindowStateMask::Width && nMask & WindowStateMask::Height)
4087 gtk_window_set_default_size(m_pWindow, aData.GetWidth(), aData.GetHeight());
4089 if (nMask & WindowStateMask::State)
4091 if (nState & WindowStateState::Maximized)
4092 gtk_window_maximize(m_pWindow);
4093 else
4094 gtk_window_unmaximize(m_pWindow);
4097 if (isPositioningAllowed() && (nMask & WindowStateMask::X && nMask & WindowStateMask::Y))
4099 gtk_window_move(m_pWindow, aData.GetX(), aData.GetY());
4103 virtual OString get_window_state(WindowStateMask nMask) const override
4105 bool bPositioningAllowed = isPositioningAllowed();
4107 WindowStateData aData;
4108 WindowStateMask nAvailable = WindowStateMask::State |
4109 WindowStateMask::Width | WindowStateMask::Height;
4110 if (bPositioningAllowed)
4111 nAvailable |= WindowStateMask::X | WindowStateMask::Y;
4112 aData.SetMask(nMask & nAvailable);
4114 if (nMask & WindowStateMask::State)
4116 WindowStateState nState = WindowStateState::Normal;
4117 if (gtk_window_is_maximized(m_pWindow))
4118 nState |= WindowStateState::Maximized;
4119 aData.SetState(nState);
4122 if (bPositioningAllowed && (nMask & (WindowStateMask::X | WindowStateMask::Y)))
4124 auto aPos = get_position();
4125 aData.SetX(aPos.X());
4126 aData.SetY(aPos.Y());
4129 if (nMask & (WindowStateMask::Width | WindowStateMask::Height))
4131 auto aSize = get_size();
4132 aData.SetWidth(aSize.Width());
4133 aData.SetHeight(aSize.Height());
4136 return aData.ToStr();
4139 virtual void connect_toplevel_focus_changed(const Link<weld::Widget&, void>& rLink) override
4141 assert(!m_nToplevelFocusChangedSignalId);
4142 m_nToplevelFocusChangedSignalId = g_signal_connect(m_pWindow, "notify::has-toplevel-focus", G_CALLBACK(signalToplevelFocusChanged), this);
4143 weld::Window::connect_toplevel_focus_changed(rLink);
4146 virtual void disable_notify_events() override
4148 if (m_nToplevelFocusChangedSignalId)
4149 g_signal_handler_block(m_pWidget, m_nToplevelFocusChangedSignalId);
4150 GtkInstanceContainer::disable_notify_events();
4153 virtual void enable_notify_events() override
4155 GtkInstanceContainer::enable_notify_events();
4156 if (m_nToplevelFocusChangedSignalId)
4157 g_signal_handler_unblock(m_pWidget, m_nToplevelFocusChangedSignalId);
4160 virtual VclPtr<VirtualDevice> screenshot() override
4162 // detect if we have to manually setup its size
4163 bool bAlreadyRealized = gtk_widget_get_realized(GTK_WIDGET(m_pWindow));
4164 // has to be visible for draw to work
4165 bool bAlreadyVisible = gtk_widget_get_visible(GTK_WIDGET(m_pWindow));
4166 if (!bAlreadyVisible)
4168 if (GTK_IS_DIALOG(m_pWindow))
4169 sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pWindow))));
4170 gtk_widget_show(GTK_WIDGET(m_pWindow));
4173 if (!bAlreadyRealized)
4175 GtkAllocation allocation;
4176 gtk_widget_realize(GTK_WIDGET(m_pWindow));
4177 gtk_widget_get_allocation(GTK_WIDGET(m_pWindow), &allocation);
4178 gtk_widget_size_allocate(GTK_WIDGET(m_pWindow), &allocation);
4181 VclPtr<VirtualDevice> xOutput(VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT));
4182 xOutput->SetOutputSizePixel(get_size());
4183 cairo_surface_t* pSurface = get_underlying_cairo_surface(*xOutput);
4184 cairo_t* cr = cairo_create(pSurface);
4186 Point aOffset = get_csd_offset(GTK_WIDGET(m_pWindow));
4188 cairo_translate(cr, -aOffset.X(), -aOffset.Y());
4190 gtk_widget_draw(GTK_WIDGET(m_pWindow), cr);
4192 cairo_destroy(cr);
4194 if (!bAlreadyVisible)
4195 gtk_widget_hide(GTK_WIDGET(m_pWindow));
4196 if (!bAlreadyRealized)
4197 gtk_widget_unrealize(GTK_WIDGET(m_pWindow));
4199 return xOutput;
4202 virtual weld::ScreenShotCollection collect_screenshot_data() override
4204 weld::ScreenShotCollection aRet;
4206 gtk_container_foreach(GTK_CONTAINER(m_pWindow), do_collect_screenshot_data, &aRet);
4208 return aRet;
4211 virtual ~GtkInstanceWindow() override
4213 if (m_nToplevelFocusChangedSignalId)
4214 g_signal_handler_disconnect(m_pWindow, m_nToplevelFocusChangedSignalId);
4215 if (m_xWindow.is())
4216 m_xWindow->clear();
4220 class GtkInstanceDialog;
4222 struct DialogRunner
4224 GtkWindow* m_pDialog;
4225 GtkInstanceDialog *m_pInstance;
4226 gint m_nResponseId;
4227 GMainLoop *m_pLoop;
4228 VclPtr<vcl::Window> m_xFrameWindow;
4229 int m_nModalDepth;
4231 DialogRunner(GtkWindow* pDialog, GtkInstanceDialog* pInstance)
4232 : m_pDialog(pDialog)
4233 , m_pInstance(pInstance)
4234 , m_nResponseId(GTK_RESPONSE_NONE)
4235 , m_pLoop(nullptr)
4236 , m_nModalDepth(0)
4238 GtkWindow* pParent = gtk_window_get_transient_for(m_pDialog);
4239 GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(GTK_WIDGET(pParent)) : nullptr;
4240 m_xFrameWindow = pFrame ? pFrame->GetWindow() : nullptr;
4243 bool loop_is_running() const
4245 return m_pLoop && g_main_loop_is_running(m_pLoop);
4248 void loop_quit()
4250 if (g_main_loop_is_running(m_pLoop))
4251 g_main_loop_quit(m_pLoop);
4254 static void signal_response(GtkDialog*, gint nResponseId, gpointer data);
4255 static void signal_cancel(GtkAssistant*, gpointer data);
4257 static gboolean signal_delete(GtkDialog* pDialog, GdkEventAny*, gpointer data)
4259 DialogRunner* pThis = static_cast<DialogRunner*>(data);
4260 if (GTK_IS_ASSISTANT(pThis->m_pDialog))
4262 // An assistant isn't a dialog, but we want to treat it like one
4263 signal_response(pDialog, GTK_RESPONSE_DELETE_EVENT, data);
4265 else
4266 pThis->loop_quit();
4267 return true; /* Do not destroy */
4270 static void signal_destroy(GtkDialog*, gpointer data)
4272 DialogRunner* pThis = static_cast<DialogRunner*>(data);
4273 pThis->loop_quit();
4276 void inc_modal_count()
4278 if (m_xFrameWindow)
4280 m_xFrameWindow->IncModalCount();
4281 if (m_nModalDepth == 0)
4282 m_xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(true);
4283 ++m_nModalDepth;
4287 void dec_modal_count()
4289 if (m_xFrameWindow)
4291 m_xFrameWindow->DecModalCount();
4292 --m_nModalDepth;
4293 if (m_nModalDepth == 0)
4294 m_xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(false);
4298 // same as gtk_dialog_run except that unmap doesn't auto-respond
4299 // so we can hide the dialog and restore it without a response getting
4300 // triggered
4301 gint run()
4303 g_object_ref(m_pDialog);
4305 inc_modal_count();
4307 bool bWasModal = gtk_window_get_modal(m_pDialog);
4308 if (!bWasModal)
4309 gtk_window_set_modal(m_pDialog, true);
4311 if (!gtk_widget_get_visible(GTK_WIDGET(m_pDialog)))
4312 gtk_widget_show(GTK_WIDGET(m_pDialog));
4314 gulong nSignalResponseId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signal_response), this) : 0;
4315 gulong nSignalCancelId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signal_cancel), this) : 0;
4316 gulong nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signal_delete), this);
4317 gulong nSignalDestroyId = g_signal_connect(m_pDialog, "destroy", G_CALLBACK(signal_destroy), this);
4319 m_pLoop = g_main_loop_new(nullptr, false);
4320 m_nResponseId = GTK_RESPONSE_NONE;
4322 gdk_threads_leave();
4323 g_main_loop_run(m_pLoop);
4324 gdk_threads_enter();
4326 g_main_loop_unref(m_pLoop);
4328 m_pLoop = nullptr;
4330 if (!bWasModal)
4331 gtk_window_set_modal(m_pDialog, false);
4333 if (nSignalResponseId)
4334 g_signal_handler_disconnect(m_pDialog, nSignalResponseId);
4335 if (nSignalCancelId)
4336 g_signal_handler_disconnect(m_pDialog, nSignalCancelId);
4337 g_signal_handler_disconnect(m_pDialog, nSignalDeleteId);
4338 g_signal_handler_disconnect(m_pDialog, nSignalDestroyId);
4340 dec_modal_count();
4342 g_object_unref(m_pDialog);
4344 return m_nResponseId;
4347 ~DialogRunner()
4349 if (m_xFrameWindow && m_nModalDepth)
4351 // if, like the calc validation dialog does, the modality was
4352 // toggled off during execution ensure that on cleanup the parent
4353 // is left in the state it was found
4354 while (m_nModalDepth++ < 0)
4355 m_xFrameWindow->IncModalCount();
4362 typedef std::set<GtkWidget*> winset;
4364 namespace
4366 void hideUnless(GtkContainer *pTop, const winset& rVisibleWidgets,
4367 std::vector<GtkWidget*> &rWasVisibleWidgets)
4369 GList* pChildren = gtk_container_get_children(pTop);
4370 for (GList* pEntry = g_list_first(pChildren); pEntry; pEntry = g_list_next(pEntry))
4372 GtkWidget* pChild = static_cast<GtkWidget*>(pEntry->data);
4373 if (!gtk_widget_get_visible(pChild))
4374 continue;
4375 if (rVisibleWidgets.find(pChild) == rVisibleWidgets.end())
4377 g_object_ref(pChild);
4378 rWasVisibleWidgets.emplace_back(pChild);
4379 gtk_widget_hide(pChild);
4381 else if (GTK_IS_CONTAINER(pChild))
4383 hideUnless(GTK_CONTAINER(pChild), rVisibleWidgets, rWasVisibleWidgets);
4386 g_list_free(pChildren);
4389 class GtkInstanceButton;
4391 class GtkInstanceDialog : public GtkInstanceWindow, public virtual weld::Dialog
4393 private:
4394 GtkWindow* m_pDialog;
4395 DialogRunner m_aDialogRun;
4396 std::shared_ptr<weld::DialogController> m_xDialogController;
4397 // Used to keep ourself alive during a runAsync(when doing runAsync without a DialogController)
4398 std::shared_ptr<weld::Dialog> m_xRunAsyncSelf;
4399 std::function<void(sal_Int32)> m_aFunc;
4400 gulong m_nCloseSignalId;
4401 gulong m_nResponseSignalId;
4402 gulong m_nCancelSignalId;
4403 gulong m_nSignalDeleteId;
4405 // for calc ref dialog that shrink to range selection widgets and resize back
4406 GtkWidget* m_pRefEdit;
4407 std::vector<GtkWidget*> m_aHiddenWidgets; // vector of hidden Controls
4408 int m_nOldEditWidth; // Original width of the input field
4409 int m_nOldEditWidthReq; // Original width request of the input field
4410 int m_nOldBorderWidth; // border width for expanded dialog
4412 void signal_close()
4414 close(true);
4417 static void signalClose(GtkWidget*, gpointer widget)
4419 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
4420 pThis->signal_close();
4423 static void signalAsyncResponse(GtkWidget*, gint ret, gpointer widget)
4425 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
4426 pThis->asyncresponse(ret);
4429 static void signalAsyncCancel(GtkAssistant*, gpointer widget)
4431 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
4432 // make esc in an assistant act as if cancel button was pressed
4433 pThis->close(false);
4436 static gboolean signalAsyncDelete(GtkWidget* pDialog, GdkEventAny*, gpointer widget)
4438 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
4439 if (GTK_IS_ASSISTANT(pThis->m_pDialog))
4441 // An assistant isn't a dialog, but we want to treat it like one
4442 signalAsyncResponse(pDialog, GTK_RESPONSE_DELETE_EVENT, widget);
4444 return true; /* Do not destroy */
4447 static int GtkToVcl(int ret)
4449 if (ret == GTK_RESPONSE_OK)
4450 ret = RET_OK;
4451 else if (ret == GTK_RESPONSE_CANCEL)
4452 ret = RET_CANCEL;
4453 else if (ret == GTK_RESPONSE_DELETE_EVENT)
4454 ret = RET_CANCEL;
4455 else if (ret == GTK_RESPONSE_CLOSE)
4456 ret = RET_CLOSE;
4457 else if (ret == GTK_RESPONSE_YES)
4458 ret = RET_YES;
4459 else if (ret == GTK_RESPONSE_NO)
4460 ret = RET_NO;
4461 else if (ret == GTK_RESPONSE_HELP)
4462 ret = RET_HELP;
4463 return ret;
4466 static int VclToGtk(int nResponse)
4468 if (nResponse == RET_OK)
4469 return GTK_RESPONSE_OK;
4470 else if (nResponse == RET_CANCEL)
4471 return GTK_RESPONSE_CANCEL;
4472 else if (nResponse == RET_CLOSE)
4473 return GTK_RESPONSE_CLOSE;
4474 else if (nResponse == RET_YES)
4475 return GTK_RESPONSE_YES;
4476 else if (nResponse == RET_NO)
4477 return GTK_RESPONSE_NO;
4478 else if (nResponse == RET_HELP)
4479 return GTK_RESPONSE_HELP;
4480 return nResponse;
4483 void asyncresponse(gint ret);
4485 static void signalActivate(GtkMenuItem*, gpointer data)
4487 bool* pActivate = static_cast<bool*>(data);
4488 *pActivate = true;
4491 bool signal_screenshot_popup_menu(const GdkEventButton* pEvent)
4493 GtkWidget *pMenu = gtk_menu_new();
4495 GtkWidget* pMenuItem = gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(VclResId(SV_BUTTONTEXT_SCREENSHOT)).getStr());
4496 gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);
4497 bool bActivate(false);
4498 g_signal_connect(pMenuItem, "activate", G_CALLBACK(signalActivate), &bActivate);
4499 gtk_widget_show(pMenuItem);
4501 int button, event_time;
4502 if (pEvent)
4504 button = pEvent->button;
4505 event_time = pEvent->time;
4507 else
4509 button = 0;
4510 event_time = gtk_get_current_event_time();
4513 gtk_menu_attach_to_widget(GTK_MENU(pMenu), GTK_WIDGET(m_pDialog), nullptr);
4515 GMainLoop* pLoop = g_main_loop_new(nullptr, true);
4516 gulong nSignalId = g_signal_connect_swapped(G_OBJECT(pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
4518 gtk_menu_popup(GTK_MENU(pMenu), nullptr, nullptr, nullptr, nullptr, button, event_time);
4520 if (g_main_loop_is_running(pLoop))
4522 gdk_threads_leave();
4523 g_main_loop_run(pLoop);
4524 gdk_threads_enter();
4527 g_main_loop_unref(pLoop);
4528 g_signal_handler_disconnect(pMenu, nSignalId);
4529 gtk_menu_detach(GTK_MENU(pMenu));
4531 if (bActivate)
4533 // open screenshot annotation dialog
4534 VclAbstractDialogFactory* pFact = VclAbstractDialogFactory::Create();
4535 VclPtr<AbstractScreenshotAnnotationDlg> xTmp = pFact->CreateScreenshotAnnotationDlg(*this);
4536 ScopedVclPtr<AbstractScreenshotAnnotationDlg> xDialog(xTmp);
4537 xDialog->Execute();
4540 return false;
4543 static gboolean signalScreenshotPopupMenu(GtkWidget*, gpointer widget)
4545 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
4546 return pThis->signal_screenshot_popup_menu(nullptr);
4549 static gboolean signalScreenshotButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
4551 GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
4552 SolarMutexGuard aGuard;
4553 return pThis->signal_screenshot_button(pEvent);
4556 bool signal_screenshot_button(GdkEventButton* pEvent)
4558 if (gdk_event_triggers_context_menu(reinterpret_cast<GdkEvent*>(pEvent)) && pEvent->type == GDK_BUTTON_PRESS)
4560 //if handled for context menu, stop processing
4561 return signal_screenshot_popup_menu(pEvent);
4563 return false;
4566 public:
4567 GtkInstanceDialog(GtkWindow* pDialog, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
4568 : GtkInstanceWindow(pDialog, pBuilder, bTakeOwnership)
4569 , m_pDialog(pDialog)
4570 , m_aDialogRun(pDialog, this)
4571 , m_nResponseSignalId(0)
4572 , m_nCancelSignalId(0)
4573 , m_nSignalDeleteId(0)
4574 , m_pRefEdit(nullptr)
4575 , m_nOldEditWidth(0)
4576 , m_nOldEditWidthReq(0)
4577 , m_nOldBorderWidth(0)
4579 if (GTK_IS_DIALOG(m_pDialog) || GTK_IS_ASSISTANT(m_pDialog))
4580 m_nCloseSignalId = g_signal_connect(m_pDialog, "close", G_CALLBACK(signalClose), this);
4581 else
4582 m_nCloseSignalId = 0;
4583 const bool bScreenshotMode(officecfg::Office::Common::Misc::ScreenshotMode::get());
4584 if (bScreenshotMode)
4586 g_signal_connect(m_pDialog, "popup-menu", G_CALLBACK(signalScreenshotPopupMenu), this);
4587 g_signal_connect(m_pDialog, "button-press-event", G_CALLBACK(signalScreenshotButton), this);
4591 virtual bool runAsync(std::shared_ptr<weld::DialogController> rDialogController, const std::function<void(sal_Int32)>& func) override
4593 assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
4595 m_xDialogController = rDialogController;
4596 m_aFunc = func;
4598 if (get_modal())
4599 m_aDialogRun.inc_modal_count();
4600 show();
4602 m_nResponseSignalId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signalAsyncResponse), this) : 0;
4603 m_nCancelSignalId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signalAsyncCancel), this) : 0;
4604 m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);
4606 return true;
4609 virtual bool runAsync(std::shared_ptr<Dialog> const & rxSelf, const std::function<void(sal_Int32)>& func) override
4611 assert( rxSelf.get() == this );
4612 assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
4614 // In order to store a shared_ptr to ourself, we have to have been constructed by make_shared,
4615 // which is that rxSelf enforces.
4616 m_xRunAsyncSelf = rxSelf;
4617 m_aFunc = func;
4619 if (get_modal())
4620 m_aDialogRun.inc_modal_count();
4621 show();
4623 m_nResponseSignalId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signalAsyncResponse), this) : 0;
4624 m_nCancelSignalId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signalAsyncCancel), this) : 0;
4625 m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);
4627 return true;
4630 GtkInstanceButton* has_click_handler(int nResponse);
4632 virtual int run() override;
4634 virtual void show() override
4636 if (gtk_widget_get_visible(m_pWidget))
4637 return;
4638 if (GTK_IS_DIALOG(m_pDialog))
4639 sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
4640 GtkInstanceWindow::show();
4643 virtual void set_modal(bool bModal) override
4645 if (get_modal() == bModal)
4646 return;
4647 GtkInstanceWindow::set_modal(bModal);
4648 /* if change the dialog modality while its running, then also change the parent LibreOffice window
4649 modal count, we typically expect the dialog modality to be restored to its original state
4651 This change modality while running case is for...
4653 a) the calc/chart dialogs which put up an extra range chooser
4654 dialog, hides the original, the user can select a range of cells and
4655 on completion the original dialog is restored
4657 b) the validity dialog in calc
4659 // tdf#135567 we know we are running in the sync case if loop_is_running is true
4660 // but for the async case we instead check for m_xDialogController which is set in
4661 // runAsync and cleared in asyncresponse
4662 if (m_aDialogRun.loop_is_running() || m_xDialogController)
4664 if (bModal)
4665 m_aDialogRun.inc_modal_count();
4666 else
4667 m_aDialogRun.dec_modal_count();
4671 virtual void response(int nResponse) override;
4673 virtual void add_button(const OUString& rText, int nResponse, const OString& rHelpId) override
4675 GtkWidget* pWidget = gtk_dialog_add_button(GTK_DIALOG(m_pDialog), MapToGtkAccelerator(rText).getStr(), VclToGtk(nResponse));
4676 if (!rHelpId.isEmpty())
4677 ::set_help_id(pWidget, rHelpId);
4680 virtual void set_default_response(int nResponse) override
4682 gtk_dialog_set_default_response(GTK_DIALOG(m_pDialog), VclToGtk(nResponse));
4685 virtual GtkButton* get_widget_for_response(int nGtkResponse)
4687 return GTK_BUTTON(gtk_dialog_get_widget_for_response(GTK_DIALOG(m_pDialog), nGtkResponse));
4690 virtual weld::Button* weld_widget_for_response(int nVclResponse) override;
4692 virtual Container* weld_content_area() override
4694 return new GtkInstanceContainer(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog))), m_pBuilder, false);
4697 virtual void collapse(weld::Widget* pEdit, weld::Widget* pButton) override
4699 GtkInstanceWidget* pVclEdit = dynamic_cast<GtkInstanceWidget*>(pEdit);
4700 assert(pVclEdit);
4701 GtkInstanceWidget* pVclButton = dynamic_cast<GtkInstanceWidget*>(pButton);
4703 GtkWidget* pRefEdit = pVclEdit->getWidget();
4704 GtkWidget* pRefBtn = pVclButton ? pVclButton->getWidget() : nullptr;
4706 m_nOldEditWidth = gtk_widget_get_allocated_width(pRefEdit);
4708 gtk_widget_get_size_request(pRefEdit, &m_nOldEditWidthReq, nullptr);
4710 //We want just pRefBtn and pRefEdit to be shown
4711 //mark widgets we want to be visible, starting with pRefEdit
4712 //and all its direct parents.
4713 winset aVisibleWidgets;
4714 GtkWidget *pContentArea = gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog));
4715 for (GtkWidget *pCandidate = pRefEdit;
4716 pCandidate && pCandidate != pContentArea && gtk_widget_get_visible(pCandidate);
4717 pCandidate = gtk_widget_get_parent(pCandidate))
4719 aVisibleWidgets.insert(pCandidate);
4721 //same again with pRefBtn, except stop if there's a
4722 //shared parent in the existing widgets
4723 for (GtkWidget *pCandidate = pRefBtn;
4724 pCandidate && pCandidate != pContentArea && gtk_widget_get_visible(pCandidate);
4725 pCandidate = gtk_widget_get_parent(pCandidate))
4727 if (aVisibleWidgets.insert(pCandidate).second)
4728 break;
4731 //hide everything except the aVisibleWidgets
4732 hideUnless(GTK_CONTAINER(pContentArea), aVisibleWidgets, m_aHiddenWidgets);
4734 gtk_widget_set_size_request(pRefEdit, m_nOldEditWidth, -1);
4735 m_nOldBorderWidth = gtk_container_get_border_width(GTK_CONTAINER(m_pDialog));
4736 gtk_container_set_border_width(GTK_CONTAINER(m_pDialog), 0);
4737 if (GtkWidget* pActionArea = gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog)))
4738 gtk_widget_hide(pActionArea);
4740 // calc's insert->function is springing back to its original size if the ref-button
4741 // is used to shrink the dialog down and then the user clicks in the calc area to do
4742 // the selection
4743 #if defined(GDK_WINDOWING_WAYLAND)
4744 bool bWorkaroundSizeSpringingBack = DLSYM_GDK_IS_WAYLAND_DISPLAY(gtk_widget_get_display(m_pWidget));
4745 if (bWorkaroundSizeSpringingBack)
4746 gtk_widget_unmap(GTK_WIDGET(m_pDialog));
4747 #endif
4749 resize_to_request();
4751 #if defined(GDK_WINDOWING_WAYLAND)
4752 if (bWorkaroundSizeSpringingBack)
4753 gtk_widget_map(GTK_WIDGET(m_pDialog));
4754 #endif
4756 m_pRefEdit = pRefEdit;
4759 virtual void undo_collapse() override
4761 // All others: Show();
4762 for (GtkWidget* pWindow : m_aHiddenWidgets)
4764 gtk_widget_show(pWindow);
4765 g_object_unref(pWindow);
4767 m_aHiddenWidgets.clear();
4769 gtk_widget_set_size_request(m_pRefEdit, m_nOldEditWidthReq, -1);
4770 m_pRefEdit = nullptr;
4771 gtk_container_set_border_width(GTK_CONTAINER(m_pDialog), m_nOldBorderWidth);
4772 if (GtkWidget* pActionArea = gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog)))
4773 gtk_widget_show(pActionArea);
4774 resize_to_request();
4775 present();
4778 void close(bool bCloseSignal);
4780 virtual void SetInstallLOKNotifierHdl(const Link<void*, vcl::ILibreOfficeKitNotifier*>&) override
4782 //not implemented for the gtk variant
4785 virtual ~GtkInstanceDialog() override
4787 if (!m_aHiddenWidgets.empty())
4789 for (GtkWidget* pWindow : m_aHiddenWidgets)
4790 g_object_unref(pWindow);
4791 m_aHiddenWidgets.clear();
4794 if (m_nCloseSignalId)
4795 g_signal_handler_disconnect(m_pDialog, m_nCloseSignalId);
4796 assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
4802 void DialogRunner::signal_response(GtkDialog*, gint nResponseId, gpointer data)
4804 DialogRunner* pThis = static_cast<DialogRunner*>(data);
4806 // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
4807 if (nResponseId == GTK_RESPONSE_DELETE_EVENT)
4809 pThis->m_pInstance->close(false);
4810 return;
4813 pThis->m_nResponseId = nResponseId;
4814 pThis->loop_quit();
4817 void DialogRunner::signal_cancel(GtkAssistant*, gpointer data)
4819 DialogRunner* pThis = static_cast<DialogRunner*>(data);
4821 // make esc in an assistant act as if cancel button was pressed
4822 pThis->m_pInstance->close(false);
4825 namespace {
4827 class GtkInstanceMessageDialog : public GtkInstanceDialog, public virtual weld::MessageDialog
4829 private:
4830 GtkMessageDialog* m_pMessageDialog;
4831 public:
4832 GtkInstanceMessageDialog(GtkMessageDialog* pMessageDialog, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
4833 : GtkInstanceDialog(GTK_WINDOW(pMessageDialog), pBuilder, bTakeOwnership)
4834 , m_pMessageDialog(pMessageDialog)
4838 virtual void set_primary_text(const OUString& rText) override
4840 ::set_primary_text(m_pMessageDialog, rText);
4843 virtual OUString get_primary_text() const override
4845 return ::get_primary_text(m_pMessageDialog);
4848 virtual void set_secondary_text(const OUString& rText) override
4850 ::set_secondary_text(m_pMessageDialog, rText);
4853 virtual OUString get_secondary_text() const override
4855 return ::get_secondary_text(m_pMessageDialog);
4858 virtual Container* weld_message_area() override
4860 return new GtkInstanceContainer(GTK_CONTAINER(gtk_message_dialog_get_message_area(m_pMessageDialog)), m_pBuilder, false);
4864 class GtkInstanceAssistant : public GtkInstanceDialog, public virtual weld::Assistant
4866 private:
4867 GtkAssistant* m_pAssistant;
4868 GtkWidget* m_pSidebar;
4869 GtkWidget* m_pSidebarEventBox;
4870 GtkButtonBox* m_pButtonBox;
4871 GtkButton* m_pHelp;
4872 GtkButton* m_pBack;
4873 GtkButton* m_pNext;
4874 GtkButton* m_pFinish;
4875 GtkButton* m_pCancel;
4876 gulong m_nButtonPressSignalId;
4877 std::vector<std::unique_ptr<GtkInstanceContainer>> m_aPages;
4878 std::map<OString, bool> m_aNotClickable;
4880 int find_page(const OString& rIdent) const
4882 int nPages = gtk_assistant_get_n_pages(m_pAssistant);
4883 for (int i = 0; i < nPages; ++i)
4885 GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, i);
4886 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pPage));
4887 if (g_strcmp0(pStr, rIdent.getStr()) == 0)
4888 return i;
4890 return -1;
4893 static void wrap_sidebar_label(GtkWidget *pWidget, gpointer /*user_data*/)
4895 if (GTK_IS_LABEL(pWidget))
4897 gtk_label_set_line_wrap(GTK_LABEL(pWidget), true);
4898 gtk_label_set_width_chars(GTK_LABEL(pWidget), 22);
4899 gtk_label_set_max_width_chars(GTK_LABEL(pWidget), 22);
4903 static void find_sidebar(GtkWidget *pWidget, gpointer user_data)
4905 if (g_strcmp0(gtk_buildable_get_name(GTK_BUILDABLE(pWidget)), "sidebar") == 0)
4907 GtkWidget **ppSidebar = static_cast<GtkWidget**>(user_data);
4908 *ppSidebar = pWidget;
4910 if (GTK_IS_CONTAINER(pWidget))
4911 gtk_container_forall(GTK_CONTAINER(pWidget), find_sidebar, user_data);
4914 static void signalHelpClicked(GtkButton*, gpointer widget)
4916 GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
4917 pThis->signal_help_clicked();
4920 void signal_help_clicked()
4922 help();
4925 static gboolean signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
4927 GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
4928 SolarMutexGuard aGuard;
4929 return pThis->signal_button(pEvent);
4932 bool signal_button(const GdkEventButton* pEvent)
4934 int nNewCurrentPage = -1;
4936 GtkAllocation allocation;
4938 int nPageIndex = 0;
4939 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pSidebar));
4940 for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
4942 GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
4943 if (!gtk_widget_get_visible(pWidget))
4944 continue;
4946 gtk_widget_get_allocation(pWidget, &allocation);
4948 gint dest_x1, dest_y1;
4949 gtk_widget_translate_coordinates(pWidget,
4950 m_pSidebarEventBox,
4953 &dest_x1,
4954 &dest_y1);
4956 gint dest_x2, dest_y2;
4957 gtk_widget_translate_coordinates(pWidget,
4958 m_pSidebarEventBox,
4959 allocation.width,
4960 allocation.height,
4961 &dest_x2,
4962 &dest_y2);
4965 if (pEvent->x >= dest_x1 && pEvent->x <= dest_x2 && pEvent->y >= dest_y1 && pEvent->y <= dest_y2)
4967 nNewCurrentPage = nPageIndex;
4968 break;
4971 ++nPageIndex;
4973 g_list_free(pChildren);
4975 if (nNewCurrentPage != -1 && nNewCurrentPage != get_current_page())
4977 OString sIdent = get_page_ident(nNewCurrentPage);
4978 if (!m_aNotClickable[sIdent] && !signal_jump_page(sIdent))
4979 set_current_page(nNewCurrentPage);
4982 return false;
4985 public:
4986 GtkInstanceAssistant(GtkAssistant* pAssistant, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
4987 : GtkInstanceDialog(GTK_WINDOW(pAssistant), pBuilder, bTakeOwnership)
4988 , m_pAssistant(pAssistant)
4989 , m_pSidebar(nullptr)
4991 m_pButtonBox = GTK_BUTTON_BOX(gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL));
4992 gtk_button_box_set_layout(m_pButtonBox, GTK_BUTTONBOX_END);
4993 gtk_box_set_spacing(GTK_BOX(m_pButtonBox), 6);
4995 m_pBack = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Back)).getStr()));
4996 gtk_widget_set_can_default(GTK_WIDGET(m_pBack), true);
4997 gtk_buildable_set_name(GTK_BUILDABLE(m_pBack), "previous");
4998 gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pBack), false, false, 0);
5000 m_pNext = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Next)).getStr()));
5001 gtk_widget_set_can_default(GTK_WIDGET(m_pNext), true);
5002 gtk_buildable_set_name(GTK_BUILDABLE(m_pNext), "next");
5003 gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pNext), false, false, 0);
5005 m_pCancel = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Cancel)).getStr()));
5006 gtk_widget_set_can_default(GTK_WIDGET(m_pCancel), true);
5007 gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pCancel), false, false, 0);
5009 m_pFinish = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Finish)).getStr()));
5010 gtk_widget_set_can_default(GTK_WIDGET(m_pFinish), true);
5011 gtk_buildable_set_name(GTK_BUILDABLE(m_pFinish), "finish");
5012 gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pFinish), false, false, 0);
5014 m_pHelp = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Help)).getStr()));
5015 gtk_widget_set_can_default(GTK_WIDGET(m_pHelp), true);
5016 g_signal_connect(m_pHelp, "clicked", G_CALLBACK(signalHelpClicked), this);
5017 gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pHelp), false, false, 0);
5019 gtk_assistant_add_action_widget(pAssistant, GTK_WIDGET(m_pButtonBox));
5020 gtk_button_box_set_child_secondary(m_pButtonBox, GTK_WIDGET(m_pHelp), true);
5021 gtk_widget_set_hexpand(GTK_WIDGET(m_pButtonBox), true);
5023 GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pButtonBox));
5024 gtk_container_child_set(GTK_CONTAINER(pParent), GTK_WIDGET(m_pButtonBox), "expand", true, "fill", true, nullptr);
5025 gtk_widget_set_halign(pParent, GTK_ALIGN_FILL);
5027 // Hide the built-in ones early so we get a nice optimal size for the width without
5028 // including the unused contents
5029 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pParent));
5030 for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
5032 GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
5033 gtk_widget_hide(pWidget);
5035 g_list_free(pChildren);
5037 gtk_widget_show_all(GTK_WIDGET(m_pButtonBox));
5039 find_sidebar(GTK_WIDGET(m_pAssistant), &m_pSidebar);
5041 m_pSidebarEventBox = ::ensureEventWidget(m_pSidebar);
5042 m_nButtonPressSignalId = m_pSidebarEventBox ? g_signal_connect(m_pSidebarEventBox, "button-press-event", G_CALLBACK(signalButton), this) : 0;
5045 virtual int get_current_page() const override
5047 return gtk_assistant_get_current_page(m_pAssistant);
5050 virtual int get_n_pages() const override
5052 return gtk_assistant_get_n_pages(m_pAssistant);
5055 virtual OString get_page_ident(int nPage) const override
5057 const GtkWidget* pWidget = gtk_assistant_get_nth_page(m_pAssistant, nPage);
5058 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pWidget));
5059 return OString(pStr, pStr ? strlen(pStr) : 0);
5062 virtual OString get_current_page_ident() const override
5064 return get_page_ident(get_current_page());
5067 virtual void set_current_page(int nPage) override
5069 OString sDialogTitle(gtk_window_get_title(GTK_WINDOW(m_pAssistant)));
5071 gtk_assistant_set_current_page(m_pAssistant, nPage);
5073 // if the page doesn't have a title, then the dialog will now have no
5074 // title, so restore the original title as a fallback
5075 GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nPage);
5076 if (!gtk_assistant_get_page_title(m_pAssistant, pPage))
5077 gtk_window_set_title(GTK_WINDOW(m_pAssistant), sDialogTitle.getStr());
5080 virtual void set_current_page(const OString& rIdent) override
5082 int nPage = find_page(rIdent);
5083 if (nPage == -1)
5084 return;
5085 set_current_page(nPage);
5088 virtual void set_page_title(const OString& rIdent, const OUString& rTitle) override
5090 int nIndex = find_page(rIdent);
5091 if (nIndex == -1)
5092 return;
5093 GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nIndex);
5094 gtk_assistant_set_page_title(m_pAssistant, pPage,
5095 OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
5096 gtk_container_forall(GTK_CONTAINER(m_pSidebar), wrap_sidebar_label, nullptr);
5099 virtual OUString get_page_title(const OString& rIdent) const override
5101 int nIndex = find_page(rIdent);
5102 if (nIndex == -1)
5103 return OUString();
5104 GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nIndex);
5105 const gchar* pStr = gtk_assistant_get_page_title(m_pAssistant, pPage);
5106 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
5109 virtual void set_page_sensitive(const OString& rIdent, bool bSensitive) override
5111 m_aNotClickable[rIdent] = !bSensitive;
5114 virtual void set_page_index(const OString& rIdent, int nNewIndex) override
5116 int nOldIndex = find_page(rIdent);
5117 if (nOldIndex == -1)
5118 return;
5120 if (nOldIndex == nNewIndex)
5121 return;
5123 GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nOldIndex);
5125 g_object_ref(pPage);
5126 OString sTitle(gtk_assistant_get_page_title(m_pAssistant, pPage));
5127 gtk_assistant_remove_page(m_pAssistant, nOldIndex);
5128 gtk_assistant_insert_page(m_pAssistant, pPage, nNewIndex);
5129 gtk_assistant_set_page_type(m_pAssistant, pPage, GTK_ASSISTANT_PAGE_CUSTOM);
5130 gtk_assistant_set_page_title(m_pAssistant, pPage, sTitle.getStr());
5131 gtk_container_forall(GTK_CONTAINER(m_pSidebar), wrap_sidebar_label, nullptr);
5132 g_object_unref(pPage);
5135 virtual weld::Container* append_page(const OString& rIdent) override
5137 disable_notify_events();
5139 GtkWidget *pChild = gtk_grid_new();
5140 gtk_buildable_set_name(GTK_BUILDABLE(pChild), rIdent.getStr());
5141 gtk_assistant_append_page(m_pAssistant, pChild);
5142 gtk_assistant_set_page_type(m_pAssistant, pChild, GTK_ASSISTANT_PAGE_CUSTOM);
5143 gtk_widget_show(pChild);
5145 enable_notify_events();
5147 m_aPages.emplace_back(new GtkInstanceContainer(GTK_CONTAINER(pChild), m_pBuilder, false));
5149 return m_aPages.back().get();
5152 virtual void set_page_side_help_id(const OString& rHelpId) override
5154 if (!m_pSidebar)
5155 return;
5156 ::set_help_id(m_pSidebar, rHelpId);
5159 virtual GtkButton* get_widget_for_response(int nGtkResponse) override
5161 GtkButton* pButton = nullptr;
5162 if (nGtkResponse == GTK_RESPONSE_YES)
5163 pButton = m_pNext;
5164 else if (nGtkResponse == GTK_RESPONSE_NO)
5165 pButton = m_pBack;
5166 else if (nGtkResponse == GTK_RESPONSE_OK)
5167 pButton = m_pFinish;
5168 else if (nGtkResponse == GTK_RESPONSE_CANCEL)
5169 pButton = m_pCancel;
5170 else if (nGtkResponse == GTK_RESPONSE_HELP)
5171 pButton = m_pHelp;
5172 return pButton;
5175 virtual ~GtkInstanceAssistant() override
5177 if (m_nButtonPressSignalId)
5178 g_signal_handler_disconnect(m_pSidebarEventBox, m_nButtonPressSignalId);
5182 class GtkInstanceFrame : public GtkInstanceContainer, public virtual weld::Frame
5184 private:
5185 GtkFrame* m_pFrame;
5186 public:
5187 GtkInstanceFrame(GtkFrame* pFrame, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
5188 : GtkInstanceContainer(GTK_CONTAINER(pFrame), pBuilder, bTakeOwnership)
5189 , m_pFrame(pFrame)
5193 virtual void set_label(const OUString& rText) override
5195 gtk_label_set_label(GTK_LABEL(gtk_frame_get_label_widget(m_pFrame)), rText.replaceFirst("~", "").toUtf8().getStr());
5198 virtual OUString get_label() const override
5200 const gchar* pStr = gtk_frame_get_label(m_pFrame);
5201 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
5204 virtual std::unique_ptr<weld::Label> weld_label_widget() const override;
5207 class GtkInstancePaned : public GtkInstanceContainer, public virtual weld::Paned
5209 private:
5210 GtkPaned* m_pPaned;
5211 public:
5212 GtkInstancePaned(GtkPaned* pPaned, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
5213 : GtkInstanceContainer(GTK_CONTAINER(pPaned), pBuilder, bTakeOwnership)
5214 , m_pPaned(pPaned)
5218 virtual void set_position(int nPos) override
5220 gtk_paned_set_position(m_pPaned, nPos);
5223 virtual int get_position() const override
5225 return gtk_paned_get_position(m_pPaned);
5231 static GType crippled_viewport_get_type();
5233 #define CRIPPLED_TYPE_VIEWPORT (crippled_viewport_get_type ())
5234 #define CRIPPLED_VIEWPORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CRIPPLED_TYPE_VIEWPORT, CrippledViewport))
5235 #ifndef NDEBUG
5236 # define CRIPPLED_IS_VIEWPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CRIPPLED_TYPE_VIEWPORT))
5237 #endif
5239 namespace {
5241 struct CrippledViewport
5243 GtkViewport viewport;
5245 GtkAdjustment *hadjustment;
5246 GtkAdjustment *vadjustment;
5251 enum
5253 PROP_0,
5254 PROP_HADJUSTMENT,
5255 PROP_VADJUSTMENT,
5256 PROP_HSCROLL_POLICY,
5257 PROP_VSCROLL_POLICY,
5258 PROP_SHADOW_TYPE
5261 static void viewport_set_adjustment(CrippledViewport *viewport,
5262 GtkOrientation orientation,
5263 GtkAdjustment *adjustment)
5265 if (!adjustment)
5266 adjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
5268 if (orientation == GTK_ORIENTATION_HORIZONTAL)
5270 if (viewport->hadjustment)
5271 g_object_unref(viewport->hadjustment);
5272 viewport->hadjustment = adjustment;
5274 else
5276 if (viewport->vadjustment)
5277 g_object_unref(viewport->vadjustment);
5278 viewport->vadjustment = adjustment;
5281 g_object_ref_sink(adjustment);
5284 static void
5285 crippled_viewport_set_property(GObject* object,
5286 guint prop_id,
5287 const GValue* value,
5288 GParamSpec* /*pspec*/)
5290 CrippledViewport *viewport = CRIPPLED_VIEWPORT(object);
5292 switch (prop_id)
5294 case PROP_HADJUSTMENT:
5295 viewport_set_adjustment(viewport, GTK_ORIENTATION_HORIZONTAL, GTK_ADJUSTMENT(g_value_get_object(value)));
5296 break;
5297 case PROP_VADJUSTMENT:
5298 viewport_set_adjustment(viewport, GTK_ORIENTATION_VERTICAL, GTK_ADJUSTMENT(g_value_get_object(value)));
5299 break;
5300 case PROP_HSCROLL_POLICY:
5301 case PROP_VSCROLL_POLICY:
5302 break;
5303 default:
5304 SAL_WARN( "vcl.gtk", "unknown property\n");
5305 break;
5309 static void
5310 crippled_viewport_get_property(GObject* object,
5311 guint prop_id,
5312 GValue* value,
5313 GParamSpec* /*pspec*/)
5315 CrippledViewport *viewport = CRIPPLED_VIEWPORT(object);
5317 switch (prop_id)
5319 case PROP_HADJUSTMENT:
5320 g_value_set_object(value, viewport->hadjustment);
5321 break;
5322 case PROP_VADJUSTMENT:
5323 g_value_set_object(value, viewport->vadjustment);
5324 break;
5325 case PROP_HSCROLL_POLICY:
5326 g_value_set_enum(value, GTK_SCROLL_MINIMUM);
5327 break;
5328 case PROP_VSCROLL_POLICY:
5329 g_value_set_enum(value, GTK_SCROLL_MINIMUM);
5330 break;
5331 default:
5332 SAL_WARN( "vcl.gtk", "unknown property\n");
5333 break;
5337 static void crippled_viewport_class_init(GtkViewportClass *klass)
5339 GObjectClass* o_class = G_OBJECT_CLASS(klass);
5341 /* GObject signals */
5342 o_class->set_property = crippled_viewport_set_property;
5343 o_class->get_property = crippled_viewport_get_property;
5345 /* Properties */
5346 g_object_class_override_property(o_class, PROP_HADJUSTMENT, "hadjustment");
5347 g_object_class_override_property(o_class, PROP_VADJUSTMENT, "vadjustment");
5348 g_object_class_override_property(o_class, PROP_HSCROLL_POLICY, "hscroll-policy");
5349 g_object_class_override_property(o_class, PROP_VSCROLL_POLICY, "vscroll-policy");
5352 GType crippled_viewport_get_type()
5354 static GType type = 0;
5356 if (!type)
5358 static const GTypeInfo tinfo =
5360 sizeof (GtkViewportClass),
5361 nullptr, /* base init */
5362 nullptr, /* base finalize */
5363 reinterpret_cast<GClassInitFunc>(crippled_viewport_class_init), /* class init */
5364 nullptr, /* class finalize */
5365 nullptr, /* class data */
5366 sizeof (CrippledViewport), /* instance size */
5367 0, /* nb preallocs */
5368 nullptr, /* instance init */
5369 nullptr /* value table */
5372 type = g_type_register_static( GTK_TYPE_VIEWPORT, "CrippledViewport",
5373 &tinfo, GTypeFlags(0));
5376 return type;
5379 #define CUSTOM_TYPE_CELL_RENDERER_SURFACE (custom_cell_renderer_surface_get_type())
5380 #define CUSTOM_CELL_RENDERER_SURFACE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), CUSTOM_TYPE_CELL_RENDERER_SURFACE, CustomCellRendererSurface))
5381 #define CUSTOM_IS_CELL_RENDERER_SURFACE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CUSTOM_TYPE_CELL_RENDERER_SURFACE))
5383 namespace {
5385 struct CustomCellRendererSurface
5387 GtkCellRendererText parent;
5388 VclPtr<VirtualDevice> device;
5389 gchar *id;
5390 gpointer instance;
5393 struct CustomCellRendererSurfaceClass
5395 GtkCellRendererTextClass parent_class;
5398 enum
5400 PROP_ID = 10000,
5401 PROP_INSTANCE_TREE_VIEW = 10001
5405 static gpointer custom_cell_renderer_surface_parent_class;
5407 static GType custom_cell_renderer_surface_get_type();
5408 static void custom_cell_renderer_surface_class_init(CustomCellRendererSurfaceClass *klass);
5410 GType custom_cell_renderer_surface_get_type()
5412 static GType type = 0;
5414 if (!type)
5416 static const GTypeInfo tinfo =
5418 sizeof (CustomCellRendererSurfaceClass),
5419 nullptr, /* base init */
5420 nullptr, /* base finalize */
5421 reinterpret_cast<GClassInitFunc>(custom_cell_renderer_surface_class_init), /* class init */
5422 nullptr, /* class finalize */
5423 nullptr, /* class data */
5424 sizeof (CustomCellRendererSurface), /* instance size */
5425 0, /* nb preallocs */
5426 nullptr, /* instance init */
5427 nullptr /* value table */
5430 // inherit from GtkCellRendererText so we can set the "text" property and get a11y support for that
5431 type = g_type_register_static(GTK_TYPE_CELL_RENDERER_TEXT, "CustomCellRendererSurface",
5432 &tinfo, GTypeFlags(0));
5435 return type;
5438 static void custom_cell_renderer_surface_get_property(GObject *object,
5439 guint param_id,
5440 GValue *value,
5441 GParamSpec *pspec)
5443 CustomCellRendererSurface *cellsurface = CUSTOM_CELL_RENDERER_SURFACE(object);
5445 switch (param_id)
5447 case PROP_ID:
5448 g_value_set_string(value, cellsurface->id);
5449 break;
5450 case PROP_INSTANCE_TREE_VIEW:
5451 g_value_set_pointer(value, cellsurface->instance);
5452 break;
5453 default:
5454 G_OBJECT_CLASS(custom_cell_renderer_surface_parent_class)->get_property(object, param_id, value, pspec);
5455 break;
5459 static void custom_cell_renderer_surface_set_property(GObject *object,
5460 guint param_id,
5461 const GValue *value,
5462 GParamSpec *pspec)
5464 CustomCellRendererSurface *cellsurface = CUSTOM_CELL_RENDERER_SURFACE(object);
5466 switch (param_id)
5468 case PROP_ID:
5469 g_free(cellsurface->id);
5470 cellsurface->id = g_value_dup_string(value);
5471 break;
5472 case PROP_INSTANCE_TREE_VIEW:
5473 cellsurface->instance = g_value_get_pointer(value);
5474 break;
5475 default:
5476 G_OBJECT_CLASS(custom_cell_renderer_surface_parent_class)->set_property(object, param_id, value, pspec);
5477 break;
5481 static bool custom_cell_renderer_surface_get_preferred_size(GtkCellRenderer *cell,
5482 GtkOrientation orientation,
5483 gint *minimum_size,
5484 gint *natural_size);
5486 static void custom_cell_renderer_surface_render(GtkCellRenderer* cell,
5487 cairo_t* cr,
5488 GtkWidget* widget,
5489 const GdkRectangle* background_area,
5490 const GdkRectangle* cell_area,
5491 GtkCellRendererState flags);
5493 static void custom_cell_renderer_surface_finalize(GObject *object)
5495 CustomCellRendererSurface *cellsurface = CUSTOM_CELL_RENDERER_SURFACE(object);
5497 g_free(cellsurface->id);
5498 cellsurface->device.disposeAndClear();
5500 G_OBJECT_CLASS(custom_cell_renderer_surface_parent_class)->finalize(object);
5503 static void custom_cell_renderer_surface_get_preferred_width(GtkCellRenderer *cell,
5504 GtkWidget *widget,
5505 gint *minimum_size,
5506 gint *natural_size)
5508 if (!custom_cell_renderer_surface_get_preferred_size(cell, GTK_ORIENTATION_HORIZONTAL,
5509 minimum_size, natural_size))
5511 // fallback to parent if we're empty
5512 GTK_CELL_RENDERER_CLASS(custom_cell_renderer_surface_parent_class)->get_preferred_width(cell,
5513 widget, minimum_size, natural_size);
5517 static void custom_cell_renderer_surface_get_preferred_height(GtkCellRenderer *cell,
5518 GtkWidget *widget,
5519 gint *minimum_size,
5520 gint *natural_size)
5522 if (!custom_cell_renderer_surface_get_preferred_size(cell, GTK_ORIENTATION_VERTICAL,
5523 minimum_size, natural_size))
5525 // fallback to parent if we're empty
5526 GTK_CELL_RENDERER_CLASS(custom_cell_renderer_surface_parent_class)->get_preferred_height(cell,
5527 widget, minimum_size, natural_size);
5532 static void custom_cell_renderer_surface_get_preferred_height_for_width(GtkCellRenderer *cell,
5533 GtkWidget *widget,
5534 gint /*width*/,
5535 gint *minimum_height,
5536 gint *natural_height)
5538 gtk_cell_renderer_get_preferred_height(cell, widget, minimum_height, natural_height);
5541 static void custom_cell_renderer_surface_get_preferred_width_for_height(GtkCellRenderer *cell,
5542 GtkWidget *widget,
5543 gint /*height*/,
5544 gint *minimum_width,
5545 gint *natural_width)
5547 gtk_cell_renderer_get_preferred_width(cell, widget, minimum_width, natural_width);
5550 void custom_cell_renderer_surface_class_init(CustomCellRendererSurfaceClass *klass)
5552 GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS(klass);
5553 GObjectClass *object_class = G_OBJECT_CLASS(klass);
5555 /* Hook up functions to set and get our custom cell renderer properties */
5556 object_class->get_property = custom_cell_renderer_surface_get_property;
5557 object_class->set_property = custom_cell_renderer_surface_set_property;
5559 custom_cell_renderer_surface_parent_class = g_type_class_peek_parent(klass);
5560 object_class->finalize = custom_cell_renderer_surface_finalize;
5562 cell_class->get_preferred_width = custom_cell_renderer_surface_get_preferred_width;
5563 cell_class->get_preferred_height = custom_cell_renderer_surface_get_preferred_height;
5564 cell_class->get_preferred_width_for_height = custom_cell_renderer_surface_get_preferred_width_for_height;
5565 cell_class->get_preferred_height_for_width = custom_cell_renderer_surface_get_preferred_height_for_width;
5567 cell_class->render = custom_cell_renderer_surface_render;
5569 g_object_class_install_property(object_class,
5570 PROP_ID,
5571 g_param_spec_string("id",
5572 "ID",
5573 "The ID of the custom data",
5574 nullptr,
5575 G_PARAM_READWRITE));
5577 g_object_class_install_property(object_class,
5578 PROP_INSTANCE_TREE_VIEW,
5579 g_param_spec_pointer("instance",
5580 "Instance",
5581 "The GtkInstanceTreeView",
5582 G_PARAM_READWRITE));
5584 gtk_cell_renderer_class_set_accessible_type(cell_class, GTK_TYPE_TEXT_CELL_ACCESSIBLE);
5587 static GtkCellRenderer* custom_cell_renderer_surface_new()
5589 return GTK_CELL_RENDERER(g_object_new(CUSTOM_TYPE_CELL_RENDERER_SURFACE, nullptr));
5592 static VclPolicyType GtkToVcl(GtkPolicyType eType)
5594 VclPolicyType eRet(VclPolicyType::NEVER);
5595 switch (eType)
5597 case GTK_POLICY_ALWAYS:
5598 eRet = VclPolicyType::ALWAYS;
5599 break;
5600 case GTK_POLICY_AUTOMATIC:
5601 eRet = VclPolicyType::AUTOMATIC;
5602 break;
5603 case GTK_POLICY_EXTERNAL:
5604 case GTK_POLICY_NEVER:
5605 eRet = VclPolicyType::NEVER;
5606 break;
5608 return eRet;
5611 static GtkPolicyType VclToGtk(VclPolicyType eType)
5613 GtkPolicyType eRet(GTK_POLICY_ALWAYS);
5614 switch (eType)
5616 case VclPolicyType::ALWAYS:
5617 eRet = GTK_POLICY_ALWAYS;
5618 break;
5619 case VclPolicyType::AUTOMATIC:
5620 eRet = GTK_POLICY_AUTOMATIC;
5621 break;
5622 case VclPolicyType::NEVER:
5623 eRet = GTK_POLICY_NEVER;
5624 break;
5626 return eRet;
5629 static GtkMessageType VclToGtk(VclMessageType eType)
5631 GtkMessageType eRet(GTK_MESSAGE_INFO);
5632 switch (eType)
5634 case VclMessageType::Info:
5635 eRet = GTK_MESSAGE_INFO;
5636 break;
5637 case VclMessageType::Warning:
5638 eRet = GTK_MESSAGE_WARNING;
5639 break;
5640 case VclMessageType::Question:
5641 eRet = GTK_MESSAGE_QUESTION;
5642 break;
5643 case VclMessageType::Error:
5644 eRet = GTK_MESSAGE_ERROR;
5645 break;
5646 case VclMessageType::Other:
5647 eRet = GTK_MESSAGE_OTHER;
5648 break;
5650 return eRet;
5653 static GtkButtonsType VclToGtk(VclButtonsType eType)
5655 GtkButtonsType eRet(GTK_BUTTONS_NONE);
5656 switch (eType)
5658 case VclButtonsType::NONE:
5659 eRet = GTK_BUTTONS_NONE;
5660 break;
5661 case VclButtonsType::Ok:
5662 eRet = GTK_BUTTONS_OK;
5663 break;
5664 case VclButtonsType::Close:
5665 eRet = GTK_BUTTONS_CLOSE;
5666 break;
5667 case VclButtonsType::Cancel:
5668 eRet = GTK_BUTTONS_CANCEL;
5669 break;
5670 case VclButtonsType::YesNo:
5671 eRet = GTK_BUTTONS_YES_NO;
5672 break;
5673 case VclButtonsType::OkCancel:
5674 eRet = GTK_BUTTONS_OK_CANCEL;
5675 break;
5677 return eRet;
5680 static GtkSelectionMode VclToGtk(SelectionMode eType)
5682 GtkSelectionMode eRet(GTK_SELECTION_NONE);
5683 switch (eType)
5685 case SelectionMode::NONE:
5686 eRet = GTK_SELECTION_NONE;
5687 break;
5688 case SelectionMode::Single:
5689 eRet = GTK_SELECTION_SINGLE;
5690 break;
5691 case SelectionMode::Range:
5692 eRet = GTK_SELECTION_BROWSE;
5693 break;
5694 case SelectionMode::Multiple:
5695 eRet = GTK_SELECTION_MULTIPLE;
5696 break;
5698 return eRet;
5701 namespace {
5703 class GtkInstanceScrolledWindow final : public GtkInstanceContainer, public virtual weld::ScrolledWindow
5705 private:
5706 GtkScrolledWindow* m_pScrolledWindow;
5707 GtkWidget *m_pOrigViewport;
5708 GtkAdjustment* m_pVAdjustment;
5709 GtkAdjustment* m_pHAdjustment;
5710 gulong m_nVAdjustChangedSignalId;
5711 gulong m_nHAdjustChangedSignalId;
5713 static void signalVAdjustValueChanged(GtkAdjustment*, gpointer widget)
5715 GtkInstanceScrolledWindow* pThis = static_cast<GtkInstanceScrolledWindow*>(widget);
5716 SolarMutexGuard aGuard;
5717 pThis->signal_vadjustment_changed();
5720 static void signalHAdjustValueChanged(GtkAdjustment*, gpointer widget)
5722 GtkInstanceScrolledWindow* pThis = static_cast<GtkInstanceScrolledWindow*>(widget);
5723 SolarMutexGuard aGuard;
5724 pThis->signal_hadjustment_changed();
5727 public:
5728 GtkInstanceScrolledWindow(GtkScrolledWindow* pScrolledWindow, GtkInstanceBuilder* pBuilder, bool bTakeOwnership, bool bUserManagedScrolling)
5729 : GtkInstanceContainer(GTK_CONTAINER(pScrolledWindow), pBuilder, bTakeOwnership)
5730 , m_pScrolledWindow(pScrolledWindow)
5731 , m_pOrigViewport(nullptr)
5732 , m_pVAdjustment(gtk_scrolled_window_get_vadjustment(m_pScrolledWindow))
5733 , m_pHAdjustment(gtk_scrolled_window_get_hadjustment(m_pScrolledWindow))
5734 , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustValueChanged), this))
5735 , m_nHAdjustChangedSignalId(g_signal_connect(m_pHAdjustment, "value-changed", G_CALLBACK(signalHAdjustValueChanged), this))
5737 if (bUserManagedScrolling)
5738 set_user_managed_scrolling();
5741 void set_user_managed_scrolling()
5743 disable_notify_events();
5744 //remove the original viewport and replace it with our bodged one which
5745 //doesn't do any scrolling and expects its child to figure it out somehow
5746 assert(!m_pOrigViewport);
5747 GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
5748 assert(GTK_IS_VIEWPORT(pViewport));
5749 GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
5750 g_object_ref(pChild);
5751 gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
5752 g_object_ref(pViewport);
5753 gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
5754 GtkWidget* pNewViewport = GTK_WIDGET(g_object_new(crippled_viewport_get_type(), nullptr));
5755 gtk_widget_show(pNewViewport);
5756 gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), pNewViewport);
5757 gtk_container_add(GTK_CONTAINER(pNewViewport), pChild);
5758 g_object_unref(pChild);
5759 m_pOrigViewport = pViewport;
5760 enable_notify_events();
5763 virtual void hadjustment_configure(int value, int lower, int upper,
5764 int step_increment, int page_increment,
5765 int page_size) override
5767 disable_notify_events();
5768 if (SwapForRTL())
5769 value = upper - (value - lower + page_size);
5770 gtk_adjustment_configure(m_pHAdjustment, value, lower, upper, step_increment, page_increment, page_size);
5771 enable_notify_events();
5774 virtual int hadjustment_get_value() const override
5776 int value = gtk_adjustment_get_value(m_pHAdjustment);
5778 if (SwapForRTL())
5780 int upper = gtk_adjustment_get_upper(m_pHAdjustment);
5781 int lower = gtk_adjustment_get_lower(m_pHAdjustment);
5782 int page_size = gtk_adjustment_get_page_size(m_pHAdjustment);
5783 value = lower + (upper - value - page_size);
5786 return value;
5789 virtual void hadjustment_set_value(int value) override
5791 disable_notify_events();
5793 if (SwapForRTL())
5795 int upper = gtk_adjustment_get_upper(m_pHAdjustment);
5796 int lower = gtk_adjustment_get_lower(m_pHAdjustment);
5797 int page_size = gtk_adjustment_get_page_size(m_pHAdjustment);
5798 value = upper - (value - lower + page_size);
5801 gtk_adjustment_set_value(m_pHAdjustment, value);
5802 enable_notify_events();
5805 virtual int hadjustment_get_upper() const override
5807 return gtk_adjustment_get_upper(m_pHAdjustment);
5810 virtual void hadjustment_set_upper(int upper) override
5812 disable_notify_events();
5813 gtk_adjustment_set_upper(m_pHAdjustment, upper);
5814 enable_notify_events();
5817 virtual int hadjustment_get_page_size() const override
5819 return gtk_adjustment_get_page_size(m_pHAdjustment);
5822 virtual void hadjustment_set_page_size(int size) override
5824 gtk_adjustment_set_page_size(m_pHAdjustment, size);
5827 virtual void hadjustment_set_page_increment(int size) override
5829 gtk_adjustment_set_page_increment(m_pHAdjustment, size);
5832 virtual void hadjustment_set_step_increment(int size) override
5834 gtk_adjustment_set_step_increment(m_pHAdjustment, size);
5837 virtual void set_hpolicy(VclPolicyType eHPolicy) override
5839 GtkPolicyType eGtkVPolicy;
5840 gtk_scrolled_window_get_policy(m_pScrolledWindow, nullptr, &eGtkVPolicy);
5841 gtk_scrolled_window_set_policy(m_pScrolledWindow, VclToGtk(eHPolicy), eGtkVPolicy);
5844 virtual VclPolicyType get_hpolicy() const override
5846 GtkPolicyType eGtkHPolicy;
5847 gtk_scrolled_window_get_policy(m_pScrolledWindow, &eGtkHPolicy, nullptr);
5848 return GtkToVcl(eGtkHPolicy);
5851 virtual int get_hscroll_height() const override
5853 if (gtk_scrolled_window_get_overlay_scrolling(m_pScrolledWindow))
5854 return 0;
5855 return gtk_widget_get_allocated_height(gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow));
5858 virtual void vadjustment_configure(int value, int lower, int upper,
5859 int step_increment, int page_increment,
5860 int page_size) override
5862 disable_notify_events();
5863 gtk_adjustment_configure(m_pVAdjustment, value, lower, upper, step_increment, page_increment, page_size);
5864 enable_notify_events();
5867 virtual int vadjustment_get_value() const override
5869 return gtk_adjustment_get_value(m_pVAdjustment);
5872 virtual void vadjustment_set_value(int value) override
5874 disable_notify_events();
5875 gtk_adjustment_set_value(m_pVAdjustment, value);
5876 enable_notify_events();
5879 virtual int vadjustment_get_upper() const override
5881 return gtk_adjustment_get_upper(m_pVAdjustment);
5884 virtual void vadjustment_set_upper(int upper) override
5886 disable_notify_events();
5887 gtk_adjustment_set_upper(m_pVAdjustment, upper);
5888 enable_notify_events();
5891 virtual int vadjustment_get_lower() const override
5893 return gtk_adjustment_get_lower(m_pVAdjustment);
5896 virtual void vadjustment_set_lower(int lower) override
5898 disable_notify_events();
5899 gtk_adjustment_set_lower(m_pVAdjustment, lower);
5900 enable_notify_events();
5903 virtual int vadjustment_get_page_size() const override
5905 return gtk_adjustment_get_page_size(m_pVAdjustment);
5908 virtual void vadjustment_set_page_size(int size) override
5910 gtk_adjustment_set_page_size(m_pVAdjustment, size);
5913 virtual void vadjustment_set_page_increment(int size) override
5915 gtk_adjustment_set_page_increment(m_pVAdjustment, size);
5918 virtual void vadjustment_set_step_increment(int size) override
5920 gtk_adjustment_set_step_increment(m_pVAdjustment, size);
5923 virtual void set_vpolicy(VclPolicyType eVPolicy) override
5925 GtkPolicyType eGtkHPolicy;
5926 gtk_scrolled_window_get_policy(m_pScrolledWindow, &eGtkHPolicy, nullptr);
5927 gtk_scrolled_window_set_policy(m_pScrolledWindow, eGtkHPolicy, VclToGtk(eVPolicy));
5930 virtual VclPolicyType get_vpolicy() const override
5932 GtkPolicyType eGtkVPolicy;
5933 gtk_scrolled_window_get_policy(m_pScrolledWindow, nullptr, &eGtkVPolicy);
5934 return GtkToVcl(eGtkVPolicy);
5937 virtual int get_vscroll_width() const override
5939 if (gtk_scrolled_window_get_overlay_scrolling(m_pScrolledWindow))
5940 return 0;
5941 return gtk_widget_get_allocated_width(gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow));
5944 virtual void disable_notify_events() override
5946 g_signal_handler_block(m_pVAdjustment, m_nVAdjustChangedSignalId);
5947 g_signal_handler_block(m_pHAdjustment, m_nHAdjustChangedSignalId);
5948 GtkInstanceContainer::disable_notify_events();
5951 virtual void enable_notify_events() override
5953 GtkInstanceContainer::enable_notify_events();
5954 g_signal_handler_unblock(m_pVAdjustment, m_nVAdjustChangedSignalId);
5955 g_signal_handler_unblock(m_pHAdjustment, m_nHAdjustChangedSignalId);
5958 virtual ~GtkInstanceScrolledWindow() override
5960 // we use GtkInstanceContainer::[disable|enable]_notify_events later on
5961 // to avoid touching these removed handlers
5962 g_signal_handler_disconnect(m_pVAdjustment, m_nVAdjustChangedSignalId);
5963 g_signal_handler_disconnect(m_pHAdjustment, m_nHAdjustChangedSignalId);
5965 //put it back the way it was
5966 if (!m_pOrigViewport)
5967 return;
5969 GtkInstanceContainer::disable_notify_events();
5971 // force in new adjustment to drop the built-in handlers on value-changed
5972 // which are getting called eventually by the gtk_container_add call
5973 // and which access the scrolled window indicators which, in the case
5974 // of user-managed scrolling windows in toolbar popups during popdown
5975 // are nullptr causing crashes when the scrolling windows is not at its
5976 // initial 0,0 position
5977 GtkAdjustment *pVAdjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
5978 gtk_scrolled_window_set_vadjustment(m_pScrolledWindow, pVAdjustment);
5979 GtkAdjustment *pHAdjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
5980 gtk_scrolled_window_set_hadjustment(m_pScrolledWindow, pHAdjustment);
5982 GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
5983 assert(CRIPPLED_IS_VIEWPORT(pViewport));
5984 GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
5985 g_object_ref(pChild);
5986 gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
5987 g_object_ref(pViewport);
5988 gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
5990 gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), m_pOrigViewport);
5991 g_object_unref(m_pOrigViewport);
5992 gtk_container_add(GTK_CONTAINER(m_pOrigViewport), pChild);
5993 g_object_unref(pChild);
5994 gtk_widget_destroy(pViewport);
5995 g_object_unref(pViewport);
5996 m_pOrigViewport = nullptr;
5997 GtkInstanceContainer::enable_notify_events();
6001 class GtkInstanceNotebook : public GtkInstanceContainer, public virtual weld::Notebook
6003 private:
6004 GtkNotebook* m_pNotebook;
6005 GtkBox* m_pOverFlowBox;
6006 GtkNotebook* m_pOverFlowNotebook;
6007 gulong m_nSwitchPageSignalId;
6008 gulong m_nOverFlowSwitchPageSignalId;
6009 gulong m_nSizeAllocateSignalId;
6010 gulong m_nFocusSignalId;
6011 gulong m_nChangeCurrentPageId;
6012 guint m_nLaunchSplitTimeoutId;
6013 bool m_bOverFlowBoxActive;
6014 bool m_bOverFlowBoxIsStart;
6015 bool m_bInternalPageChange;
6016 int m_nStartTabCount;
6017 int m_nEndTabCount;
6018 mutable std::vector<std::unique_ptr<GtkInstanceContainer>> m_aPages;
6020 static void signalSwitchPage(GtkNotebook*, GtkWidget*, guint nNewPage, gpointer widget)
6022 GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
6023 SolarMutexGuard aGuard;
6024 pThis->signal_switch_page(nNewPage);
6027 static gboolean launch_overflow_switch_page(GtkInstanceNotebook* pThis)
6029 SolarMutexGuard aGuard;
6030 pThis->signal_overflow_switch_page();
6031 return false;
6034 static void signalOverFlowSwitchPage(GtkNotebook*, GtkWidget*, guint, gpointer widget)
6036 g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(launch_overflow_switch_page), widget, nullptr);
6039 void signal_switch_page(int nNewPage)
6041 if (m_bOverFlowBoxIsStart)
6043 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
6044 // add count of overflow pages, minus the extra tab
6045 nNewPage += nOverFlowLen;
6048 bool bAllow = m_bInternalPageChange || !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
6049 if (!bAllow)
6051 g_signal_stop_emission_by_name(m_pNotebook, "switch-page");
6052 return;
6054 if (m_bOverFlowBoxActive)
6055 gtk_notebook_set_current_page(m_pOverFlowNotebook, gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1);
6056 OString sNewIdent(get_page_ident(nNewPage));
6057 if (!m_bInternalPageChange)
6058 m_aEnterPageHdl.Call(sNewIdent);
6061 void unsplit_notebooks()
6063 int nOverFlowPages = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
6064 int nMainPages = gtk_notebook_get_n_pages(m_pNotebook);
6065 int nPageIndex = 0;
6066 if (!m_bOverFlowBoxIsStart)
6067 nPageIndex += nMainPages;
6069 // take the overflow pages, and put them back at the end of the normal one
6070 int i = nMainPages;
6071 while (nOverFlowPages)
6073 OString sIdent(get_page_ident(m_pOverFlowNotebook, 0));
6074 OUString sLabel(get_tab_label_text(m_pOverFlowNotebook, 0));
6075 remove_page(m_pOverFlowNotebook, sIdent);
6077 GtkWidget* pPage = m_aPages[nPageIndex]->getWidget();
6078 insert_page(m_pNotebook, sIdent, sLabel, pPage, -1);
6080 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook,
6081 gtk_notebook_get_nth_page(m_pNotebook, i));
6082 gtk_widget_set_hexpand(pTabWidget, true);
6083 --nOverFlowPages;
6084 ++i;
6085 ++nPageIndex;
6088 // remove the dangling placeholder tab page
6089 remove_page(m_pOverFlowNotebook, "useless");
6092 // a tab has been selected on the overflow notebook
6093 void signal_overflow_switch_page()
6095 int nNewPage = gtk_notebook_get_current_page(m_pOverFlowNotebook);
6096 int nOverFlowPages = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
6097 if (nNewPage == nOverFlowPages)
6099 // the useless tab which is there because there has to be an active tab
6100 return;
6103 // check if we are allowed leave before attempting to resplit the notebooks
6104 bool bAllow = !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
6105 if (!bAllow)
6106 return;
6108 disable_notify_events();
6110 // take the overflow pages, and put them back at the end of the normal one
6111 unsplit_notebooks();
6113 // now redo the split, the pages will be split the other way around this time
6114 std::swap(m_nStartTabCount, m_nEndTabCount);
6115 split_notebooks();
6117 gtk_notebook_set_current_page(m_pNotebook, nNewPage);
6119 enable_notify_events();
6121 // trigger main notebook switch-page callback
6122 OString sNewIdent(get_page_ident(m_pNotebook, nNewPage));
6123 m_aEnterPageHdl.Call(sNewIdent);
6126 static OString get_page_ident(GtkNotebook *pNotebook, guint nPage)
6128 const GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, gtk_notebook_get_nth_page(pNotebook, nPage));
6129 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pTabWidget));
6130 return OString(pStr, pStr ? strlen(pStr) : 0);
6133 static gint get_page_number(GtkNotebook *pNotebook, const OString& rIdent)
6135 gint nPages = gtk_notebook_get_n_pages(pNotebook);
6136 for (gint i = 0; i < nPages; ++i)
6138 const GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, gtk_notebook_get_nth_page(pNotebook, i));
6139 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pTabWidget));
6140 if (pStr && strcmp(pStr, rIdent.getStr()) == 0)
6141 return i;
6143 return -1;
6146 int remove_page(GtkNotebook *pNotebook, const OString& rIdent)
6148 disable_notify_events();
6149 int nPageNumber = get_page_number(pNotebook, rIdent);
6150 gtk_notebook_remove_page(pNotebook, nPageNumber);
6151 enable_notify_events();
6152 return nPageNumber;
6155 static OUString get_tab_label_text(GtkNotebook *pNotebook, guint nPage)
6157 const gchar* pStr = gtk_notebook_get_tab_label_text(pNotebook, gtk_notebook_get_nth_page(pNotebook, nPage));
6158 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
6161 static void set_tab_label_text(GtkNotebook *pNotebook, guint nPage, const OUString& rText)
6163 OString sUtf8(rText.toUtf8());
6165 GtkWidget* pPage = gtk_notebook_get_nth_page(pNotebook, nPage);
6167 // tdf#128241 if there's already a label here, reuse it so the buildable
6168 // name remains the same, gtk_notebook_set_tab_label_text will replace
6169 // the label widget with a new one
6170 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, pPage);
6171 if (pTabWidget && GTK_IS_LABEL(pTabWidget))
6173 gtk_label_set_label(GTK_LABEL(pTabWidget), sUtf8.getStr());
6174 return;
6177 gtk_notebook_set_tab_label_text(pNotebook, pPage, sUtf8.getStr());
6180 void append_useless_page(GtkNotebook *pNotebook)
6182 disable_notify_events();
6184 GtkWidget *pTabWidget = gtk_fixed_new();
6185 gtk_buildable_set_name(GTK_BUILDABLE(pTabWidget), "useless");
6187 GtkWidget *pChild = gtk_grid_new();
6188 gtk_notebook_append_page(pNotebook, pChild, pTabWidget);
6189 gtk_widget_show(pChild);
6190 gtk_widget_show(pTabWidget);
6192 enable_notify_events();
6195 void insert_page(GtkNotebook *pNotebook, const OString& rIdent, const OUString& rLabel, GtkWidget *pChild, int nPos)
6197 disable_notify_events();
6199 GtkWidget *pTabWidget = gtk_label_new_with_mnemonic(MapToGtkAccelerator(rLabel).getStr());
6200 gtk_buildable_set_name(GTK_BUILDABLE(pTabWidget), rIdent.getStr());
6202 gtk_notebook_insert_page(pNotebook, pChild, pTabWidget, nPos);
6203 gtk_widget_show(pChild);
6204 gtk_widget_show(pTabWidget);
6206 if (nPos != -1)
6208 unsigned int nPageIndex = static_cast<unsigned int>(nPos);
6209 if (nPageIndex < m_aPages.size())
6210 m_aPages.insert(m_aPages.begin() + nPageIndex, nullptr);
6213 enable_notify_events();
6216 void make_overflow_boxes()
6218 m_pOverFlowBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
6219 GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pNotebook));
6220 gtk_container_add(GTK_CONTAINER(pParent), GTK_WIDGET(m_pOverFlowBox));
6221 gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pOverFlowNotebook), false, false, 0);
6222 g_object_ref(m_pNotebook);
6223 gtk_container_remove(GTK_CONTAINER(pParent), GTK_WIDGET(m_pNotebook));
6224 gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pNotebook), true, true, 0);
6225 g_object_unref(m_pNotebook);
6226 gtk_widget_show(GTK_WIDGET(m_pOverFlowBox));
6229 void split_notebooks()
6231 // get the original preferred size for the notebook, the sane width
6232 // expected here depends on the notebooks all initially having
6233 // scrollable tabs enabled
6234 GtkAllocation alloc;
6235 gtk_widget_get_allocation(GTK_WIDGET(m_pNotebook), &alloc);
6237 // toggle the direction of the split since the last time
6238 m_bOverFlowBoxIsStart = !m_bOverFlowBoxIsStart;
6239 if (!m_pOverFlowBox)
6240 make_overflow_boxes();
6242 // don't scroll the tabs anymore
6243 gtk_notebook_set_scrollable(m_pNotebook, false);
6245 gtk_widget_freeze_child_notify(GTK_WIDGET(m_pNotebook));
6246 gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
6248 gtk_widget_show(GTK_WIDGET(m_pOverFlowNotebook));
6250 gint nPages;
6252 GtkRequisition size1, size2;
6254 if (!m_nStartTabCount && !m_nEndTabCount)
6256 nPages = gtk_notebook_get_n_pages(m_pNotebook);
6258 std::vector<int> aLabelWidths;
6259 //move tabs to the overflow notebook
6260 for (int i = 0; i < nPages; ++i)
6262 OUString sLabel(get_tab_label_text(m_pNotebook, i));
6263 aLabelWidths.push_back(get_pixel_size(sLabel).Width());
6265 int row_width = std::accumulate(aLabelWidths.begin(), aLabelWidths.end(), 0) / 2;
6266 int count = 0;
6267 for (int i = 0; i < nPages; ++i)
6269 count += aLabelWidths[i];
6270 if (count >= row_width)
6272 m_nStartTabCount = i;
6273 break;
6277 m_nEndTabCount = nPages - m_nStartTabCount;
6280 //move the tabs to the overflow notebook
6281 int i = 0;
6282 int nOverFlowPages = m_nStartTabCount;
6283 while (nOverFlowPages)
6285 OString sIdent(get_page_ident(m_pNotebook, 0));
6286 OUString sLabel(get_tab_label_text(m_pNotebook, 0));
6287 remove_page(m_pNotebook, sIdent);
6288 insert_page(m_pOverFlowNotebook, sIdent, sLabel, gtk_grid_new(), -1);
6289 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pOverFlowNotebook,
6290 gtk_notebook_get_nth_page(m_pOverFlowNotebook, i));
6291 gtk_widget_set_hexpand(pTabWidget, true);
6293 --nOverFlowPages;
6294 ++i;
6297 for (i = 0; i < m_nEndTabCount; ++i)
6299 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook,
6300 gtk_notebook_get_nth_page(m_pNotebook, i));
6301 gtk_widget_set_hexpand(pTabWidget, true);
6304 // have to have some tab as the active tab of the overflow notebook
6305 append_useless_page(m_pOverFlowNotebook);
6306 gtk_notebook_set_current_page(m_pOverFlowNotebook, gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1);
6307 if (gtk_widget_has_focus(GTK_WIDGET(m_pOverFlowNotebook)))
6308 gtk_widget_grab_focus(GTK_WIDGET(m_pNotebook));
6310 // add this temporarily to the normal notebook to measure how wide
6311 // the row would be if switched to the other notebook
6312 append_useless_page(m_pNotebook);
6314 gtk_widget_get_preferred_size(GTK_WIDGET(m_pNotebook), nullptr, &size1);
6315 gtk_widget_get_preferred_size(GTK_WIDGET(m_pOverFlowNotebook), nullptr, &size2);
6317 auto nWidth = std::max(size1.width, size2.width);
6318 gtk_widget_set_size_request(GTK_WIDGET(m_pNotebook), nWidth, alloc.height);
6319 gtk_widget_set_size_request(GTK_WIDGET(m_pOverFlowNotebook), nWidth, -1);
6321 // remove it once we've measured it
6322 remove_page(m_pNotebook, "useless");
6324 gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
6325 gtk_widget_thaw_child_notify(GTK_WIDGET(m_pNotebook));
6327 m_bOverFlowBoxActive = true;
6330 static gboolean launch_split_notebooks(GtkInstanceNotebook* pThis)
6332 int nCurrentPage = pThis->get_current_page();
6333 pThis->split_notebooks();
6334 pThis->set_current_page(nCurrentPage);
6335 pThis->m_nLaunchSplitTimeoutId = 0;
6336 return false;
6339 // tdf#120371
6340 // https://developer.gnome.org/hig-book/unstable/controls-notebooks.html.en#controls-too-many-tabs
6341 // if no of tabs > 6, but only if the notebook would auto-scroll, then split the tabs over
6342 // two notebooks. Checking for the auto-scroll allows themes like Ambience under Ubuntu 16.04 to keep
6343 // tabs in a single row when they would fit
6344 void signal_notebook_size_allocate()
6346 if (m_bOverFlowBoxActive || m_nLaunchSplitTimeoutId)
6347 return;
6348 disable_notify_events();
6349 gint nPages = gtk_notebook_get_n_pages(m_pNotebook);
6350 if (nPages > 6 && gtk_notebook_get_tab_pos(m_pNotebook) == GTK_POS_TOP)
6352 for (gint i = 0; i < nPages; ++i)
6354 GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook, gtk_notebook_get_nth_page(m_pNotebook, i));
6355 if (!gtk_widget_get_child_visible(pTabWidget))
6357 m_nLaunchSplitTimeoutId = g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(launch_split_notebooks), this, nullptr);
6358 break;
6362 enable_notify_events();
6365 static void signalSizeAllocate(GtkWidget*, GdkRectangle*, gpointer widget)
6367 GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
6368 pThis->signal_notebook_size_allocate();
6371 bool signal_focus(GtkDirectionType direction)
6373 if (!m_bOverFlowBoxActive)
6374 return false;
6376 int nPage = gtk_notebook_get_current_page(m_pNotebook);
6377 if (direction == GTK_DIR_LEFT && nPage == 0)
6379 auto nOverFlowLen = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
6380 gtk_notebook_set_current_page(m_pOverFlowNotebook, nOverFlowLen - 1);
6381 return true;
6383 else if (direction == GTK_DIR_RIGHT && nPage == gtk_notebook_get_n_pages(m_pNotebook) - 1)
6385 gtk_notebook_set_current_page(m_pOverFlowNotebook, 0);
6386 return true;
6389 return false;
6392 static gboolean signalFocus(GtkNotebook* notebook, GtkDirectionType direction, gpointer widget)
6394 // if the notebook widget itself has focus
6395 if (gtk_widget_is_focus(GTK_WIDGET(notebook)))
6397 GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
6398 return pThis->signal_focus(direction);
6400 return false;
6403 // ctrl + page_up/ page_down
6404 bool signal_change_current_page(gint arg1)
6406 bool bHandled = signal_focus(arg1 < 0 ? GTK_DIR_LEFT : GTK_DIR_RIGHT);
6407 if (bHandled)
6408 g_signal_stop_emission_by_name(m_pNotebook, "change-current-page");
6409 return false;
6412 static gboolean signalChangeCurrentPage(GtkNotebook*, gint arg1, gpointer widget)
6414 if (arg1 == 0)
6415 return true;
6416 GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
6417 return pThis->signal_change_current_page(arg1);
6420 public:
6421 GtkInstanceNotebook(GtkNotebook* pNotebook, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
6422 : GtkInstanceContainer(GTK_CONTAINER(pNotebook), pBuilder, bTakeOwnership)
6423 , m_pNotebook(pNotebook)
6424 , m_pOverFlowBox(nullptr)
6425 , m_pOverFlowNotebook(GTK_NOTEBOOK(gtk_notebook_new()))
6426 , m_nSwitchPageSignalId(g_signal_connect(pNotebook, "switch-page", G_CALLBACK(signalSwitchPage), this))
6427 , m_nOverFlowSwitchPageSignalId(g_signal_connect(m_pOverFlowNotebook, "switch-page", G_CALLBACK(signalOverFlowSwitchPage), this))
6428 , m_nFocusSignalId(g_signal_connect(pNotebook, "focus", G_CALLBACK(signalFocus), this))
6429 , m_nChangeCurrentPageId(g_signal_connect(pNotebook, "change-current-page", G_CALLBACK(signalChangeCurrentPage), this))
6430 , m_nLaunchSplitTimeoutId(0)
6431 , m_bOverFlowBoxActive(false)
6432 , m_bOverFlowBoxIsStart(false)
6433 , m_bInternalPageChange(false)
6434 , m_nStartTabCount(0)
6435 , m_nEndTabCount(0)
6437 gtk_widget_add_events(GTK_WIDGET(pNotebook), GDK_SCROLL_MASK);
6438 if (get_n_pages() > 6)
6439 m_nSizeAllocateSignalId = g_signal_connect_after(pNotebook, "size-allocate", G_CALLBACK(signalSizeAllocate), this);
6440 else
6441 m_nSizeAllocateSignalId = 0;
6442 gtk_notebook_set_show_border(m_pOverFlowNotebook, false);
6444 // tdf#122623 it's nigh impossible to have a GtkNotebook without an active (checked) tab, so try and theme
6445 // the unwanted tab into invisibility
6446 GtkStyleContext *pNotebookContext = gtk_widget_get_style_context(GTK_WIDGET(m_pOverFlowNotebook));
6447 GtkCssProvider *pProvider = gtk_css_provider_new();
6448 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; }";
6449 gtk_css_provider_load_from_data(pProvider, data, -1, nullptr);
6450 gtk_style_context_add_provider(pNotebookContext, GTK_STYLE_PROVIDER(pProvider),
6451 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
6454 virtual int get_current_page() const override
6456 int nPage = gtk_notebook_get_current_page(m_pNotebook);
6457 if (nPage == -1)
6458 return nPage;
6459 if (m_bOverFlowBoxIsStart)
6461 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
6462 // add count of overflow pages, minus the extra tab
6463 nPage += nOverFlowLen;
6465 return nPage;
6468 virtual OString get_page_ident(int nPage) const override
6470 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
6471 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
6472 if (m_bOverFlowBoxIsStart)
6474 if (nPage < nOverFlowLen)
6475 return get_page_ident(m_pOverFlowNotebook, nPage);
6476 nPage -= nOverFlowLen;
6477 return get_page_ident(m_pNotebook, nPage);
6479 else
6481 if (nPage < nMainLen)
6482 return get_page_ident(m_pNotebook, nPage);
6483 nPage -= nMainLen;
6484 return get_page_ident(m_pOverFlowNotebook, nPage);
6488 virtual OString get_current_page_ident() const override
6490 const int nPage = get_current_page();
6491 return nPage != -1 ? get_page_ident(nPage) : OString();
6494 virtual int get_page_index(const OString& rIdent) const override
6496 auto nMainIndex = get_page_number(m_pNotebook, rIdent);
6497 auto nOverFlowIndex = get_page_number(m_pOverFlowNotebook, rIdent);
6499 if (nMainIndex == -1 && nOverFlowIndex == -1)
6500 return -1;
6502 if (m_bOverFlowBoxIsStart)
6504 if (nOverFlowIndex != -1)
6505 return nOverFlowIndex;
6506 else
6508 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
6509 return nMainIndex + nOverFlowLen;
6512 else
6514 if (nMainIndex != -1)
6515 return nMainIndex;
6516 else
6518 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
6519 return nOverFlowIndex + nMainLen;
6524 virtual weld::Container* get_page(const OString& rIdent) const override
6526 int nPage = get_page_index(rIdent);
6527 if (nPage < 0)
6528 return nullptr;
6530 GtkContainer* pChild;
6531 if (m_bOverFlowBoxIsStart)
6533 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
6534 if (nPage < nOverFlowLen)
6535 pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pOverFlowNotebook, nPage));
6536 else
6538 nPage -= nOverFlowLen;
6539 pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pNotebook, nPage));
6542 else
6544 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
6545 if (nPage < nMainLen)
6546 pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pNotebook, nPage));
6547 else
6549 nPage -= nMainLen;
6550 pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pOverFlowNotebook, nPage));
6554 unsigned int nPageIndex = static_cast<unsigned int>(nPage);
6555 if (m_aPages.size() < nPageIndex + 1)
6556 m_aPages.resize(nPageIndex + 1);
6557 if (!m_aPages[nPageIndex])
6558 m_aPages[nPageIndex].reset(new GtkInstanceContainer(pChild, m_pBuilder, false));
6559 return m_aPages[nPageIndex].get();
6562 virtual void set_current_page(int nPage) override
6564 // normally we'd call disable_notify_events/enable_notify_events here,
6565 // but the notebook is complicated by the need to support the
6566 // double-decker hackery so for simplicity just flag that the page
6567 // change is not a directly user-triggered one
6568 bool bInternalPageChange = m_bInternalPageChange;
6569 m_bInternalPageChange = true;
6571 if (m_bOverFlowBoxIsStart)
6573 auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
6574 if (nPage < nOverFlowLen)
6575 gtk_notebook_set_current_page(m_pOverFlowNotebook, nPage);
6576 else
6578 nPage -= nOverFlowLen;
6579 gtk_notebook_set_current_page(m_pNotebook, nPage);
6582 else
6584 auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
6585 if (nPage < nMainLen)
6586 gtk_notebook_set_current_page(m_pNotebook, nPage);
6587 else
6589 nPage -= nMainLen;
6590 gtk_notebook_set_current_page(m_pOverFlowNotebook, nPage);
6594 m_bInternalPageChange = bInternalPageChange;
6597 virtual void set_current_page(const OString& rIdent) override
6599 gint nPage = get_page_index(rIdent);
6600 set_current_page(nPage);
6603 virtual int get_n_pages() const override
6605 int nLen = gtk_notebook_get_n_pages(m_pNotebook);
6606 if (m_bOverFlowBoxActive)
6607 nLen += gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
6608 return nLen;
6611 virtual OUString get_tab_label_text(const OString& rIdent) const override
6613 gint nPageNum = get_page_number(m_pNotebook, rIdent);
6614 if (nPageNum != -1)
6615 return get_tab_label_text(m_pNotebook, nPageNum);
6616 nPageNum = get_page_number(m_pOverFlowNotebook, rIdent);
6617 if (nPageNum != -1)
6618 return get_tab_label_text(m_pOverFlowNotebook, nPageNum);
6619 return OUString();
6622 virtual void set_tab_label_text(const OString& rIdent, const OUString& rText) override
6624 gint nPageNum = get_page_number(m_pNotebook, rIdent);
6625 if (nPageNum != -1)
6627 set_tab_label_text(m_pNotebook, nPageNum, rText);
6628 return;
6630 nPageNum = get_page_number(m_pOverFlowNotebook, rIdent);
6631 if (nPageNum != -1)
6633 set_tab_label_text(m_pOverFlowNotebook, nPageNum, rText);
6637 virtual void disable_notify_events() override
6639 g_signal_handler_block(m_pNotebook, m_nSwitchPageSignalId);
6640 g_signal_handler_block(m_pNotebook, m_nFocusSignalId);
6641 g_signal_handler_block(m_pNotebook, m_nChangeCurrentPageId);
6642 g_signal_handler_block(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
6643 gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
6644 GtkInstanceContainer::disable_notify_events();
6647 virtual void enable_notify_events() override
6649 GtkInstanceContainer::enable_notify_events();
6650 gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
6651 g_signal_handler_unblock(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
6652 g_signal_handler_unblock(m_pNotebook, m_nSwitchPageSignalId);
6653 g_signal_handler_unblock(m_pNotebook, m_nFocusSignalId);
6654 g_signal_handler_unblock(m_pNotebook, m_nChangeCurrentPageId);
6657 void reset_split_data()
6659 // reset overflow and allow it to be recalculated if necessary
6660 gtk_widget_hide(GTK_WIDGET(m_pOverFlowNotebook));
6661 m_bOverFlowBoxActive = false;
6662 m_nStartTabCount = 0;
6663 m_nEndTabCount = 0;
6666 virtual void remove_page(const OString& rIdent) override
6668 if (m_bOverFlowBoxActive)
6670 unsplit_notebooks();
6671 reset_split_data();
6674 unsigned int nPageIndex = remove_page(m_pNotebook, rIdent);
6675 if (nPageIndex < m_aPages.size())
6676 m_aPages.erase(m_aPages.begin() + nPageIndex);
6679 virtual void insert_page(const OString& rIdent, const OUString& rLabel, int nPos) override
6681 if (m_bOverFlowBoxActive)
6683 unsplit_notebooks();
6684 reset_split_data();
6687 // reset overflow and allow it to be recalculated if necessary
6688 gtk_widget_hide(GTK_WIDGET(m_pOverFlowNotebook));
6689 m_bOverFlowBoxActive = false;
6691 insert_page(m_pNotebook, rIdent, rLabel, gtk_grid_new(), nPos);
6694 virtual ~GtkInstanceNotebook() override
6696 if (m_nLaunchSplitTimeoutId)
6697 g_source_remove(m_nLaunchSplitTimeoutId);
6698 if (m_nSizeAllocateSignalId)
6699 g_signal_handler_disconnect(m_pNotebook, m_nSizeAllocateSignalId);
6700 g_signal_handler_disconnect(m_pNotebook, m_nSwitchPageSignalId);
6701 g_signal_handler_disconnect(m_pNotebook, m_nFocusSignalId);
6702 g_signal_handler_disconnect(m_pNotebook, m_nChangeCurrentPageId);
6703 g_signal_handler_disconnect(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
6704 gtk_widget_destroy(GTK_WIDGET(m_pOverFlowNotebook));
6705 if (m_pOverFlowBox)
6707 // put it back to how we found it initially
6708 GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pOverFlowBox));
6709 g_object_ref(m_pNotebook);
6710 gtk_container_remove(GTK_CONTAINER(m_pOverFlowBox), GTK_WIDGET(m_pNotebook));
6711 gtk_container_add(GTK_CONTAINER(pParent), GTK_WIDGET(m_pNotebook));
6712 g_object_unref(m_pNotebook);
6714 gtk_widget_destroy(GTK_WIDGET(m_pOverFlowBox));
6719 PangoAttrList* create_attr_list(const vcl::Font& rFont)
6721 PangoAttrList* pAttrList = pango_attr_list_new();
6722 pango_attr_list_insert(pAttrList, pango_attr_family_new(OUStringToOString(rFont.GetFamilyName(), RTL_TEXTENCODING_UTF8).getStr()));
6723 pango_attr_list_insert(pAttrList, pango_attr_size_new(rFont.GetFontSize().Height() * PANGO_SCALE));
6724 switch (rFont.GetItalic())
6726 case ITALIC_NONE:
6727 pango_attr_list_insert(pAttrList, pango_attr_style_new(PANGO_STYLE_NORMAL));
6728 break;
6729 case ITALIC_NORMAL:
6730 pango_attr_list_insert(pAttrList, pango_attr_style_new(PANGO_STYLE_ITALIC));
6731 break;
6732 case ITALIC_OBLIQUE:
6733 pango_attr_list_insert(pAttrList, pango_attr_style_new(PANGO_STYLE_OBLIQUE));
6734 break;
6735 default:
6736 break;
6738 switch (rFont.GetWeight())
6740 case WEIGHT_ULTRALIGHT:
6741 pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRALIGHT));
6742 break;
6743 case WEIGHT_LIGHT:
6744 pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_LIGHT));
6745 break;
6746 case WEIGHT_NORMAL:
6747 pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_NORMAL));
6748 break;
6749 case WEIGHT_BOLD:
6750 pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
6751 break;
6752 case WEIGHT_ULTRABOLD:
6753 pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRABOLD));
6754 break;
6755 default:
6756 break;
6758 switch (rFont.GetWidthType())
6760 case WIDTH_ULTRA_CONDENSED:
6761 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_CONDENSED));
6762 break;
6763 case WIDTH_EXTRA_CONDENSED:
6764 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_CONDENSED));
6765 break;
6766 case WIDTH_CONDENSED:
6767 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_CONDENSED));
6768 break;
6769 case WIDTH_SEMI_CONDENSED:
6770 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_CONDENSED));
6771 break;
6772 case WIDTH_NORMAL:
6773 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_NORMAL));
6774 break;
6775 case WIDTH_SEMI_EXPANDED:
6776 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_EXPANDED));
6777 break;
6778 case WIDTH_EXPANDED:
6779 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXPANDED));
6780 break;
6781 case WIDTH_EXTRA_EXPANDED:
6782 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_EXPANDED));
6783 break;
6784 case WIDTH_ULTRA_EXPANDED:
6785 pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_EXPANDED));
6786 break;
6787 default:
6788 break;
6790 return pAttrList;
6793 void set_font(GtkLabel* pLabel, const vcl::Font& rFont)
6795 // TODO?, clear old props like set_text_foreground_color does
6796 PangoAttrList* pAttrList = create_attr_list(rFont);
6797 gtk_label_set_attributes(pLabel, pAttrList);
6798 pango_attr_list_unref(pAttrList);
6801 class GtkInstanceButton : public GtkInstanceContainer, public virtual weld::Button
6803 private:
6804 GtkButton* m_pButton;
6805 gulong m_nSignalId;
6806 std::unique_ptr<vcl::Font> m_xFont;
6808 static void signalClicked(GtkButton*, gpointer widget)
6810 GtkInstanceButton* pThis = static_cast<GtkInstanceButton*>(widget);
6811 SolarMutexGuard aGuard;
6812 pThis->signal_clicked();
6815 virtual void ensureMouseEventWidget() override
6817 // The GtkButton is sufficient to get mouse events without an intermediate GtkEventBox
6818 if (!m_pMouseEventBox)
6819 m_pMouseEventBox = m_pWidget;
6822 static GtkWidget* find_label_widget(GtkContainer* pContainer)
6824 GList* pChildren = gtk_container_get_children(pContainer);
6826 GtkWidget* pChild = nullptr;
6827 for (GList* pCandidate = pChildren; pCandidate; pCandidate = pCandidate->next)
6829 if (GTK_IS_LABEL(pCandidate->data))
6831 pChild = GTK_WIDGET(pCandidate->data);
6832 break;
6834 else if (GTK_IS_CONTAINER(pCandidate->data))
6836 pChild = find_label_widget(GTK_CONTAINER(pCandidate->data));
6837 if (pChild)
6838 break;
6841 g_list_free(pChildren);
6843 return pChild;
6846 protected:
6847 GtkWidget* get_label_widget()
6849 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pButton));
6850 if (GTK_IS_ALIGNMENT(pChild))
6851 pChild = gtk_bin_get_child(GTK_BIN(pChild));
6853 if (GTK_IS_CONTAINER(pChild))
6854 pChild = find_label_widget(GTK_CONTAINER(pChild));
6855 else if (!GTK_IS_LABEL(pChild))
6856 pChild = nullptr;
6858 return pChild;
6861 public:
6862 GtkInstanceButton(GtkButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
6863 : GtkInstanceContainer(GTK_CONTAINER(pButton), pBuilder, bTakeOwnership)
6864 , m_pButton(pButton)
6865 , m_nSignalId(g_signal_connect(pButton, "clicked", G_CALLBACK(signalClicked), this))
6867 g_object_set_data(G_OBJECT(m_pButton), "g-lo-GtkInstanceButton", this);
6870 virtual void set_label(const OUString& rText) override
6872 ::set_label(m_pButton, rText);
6875 virtual void set_image(VirtualDevice* pDevice) override
6877 gtk_button_set_always_show_image(m_pButton, true);
6878 gtk_button_set_image_position(m_pButton, GTK_POS_LEFT);
6879 if (pDevice)
6880 gtk_button_set_image(m_pButton, image_new_from_virtual_device(*pDevice));
6881 else
6882 gtk_button_set_image(m_pButton, nullptr);
6885 virtual void set_from_icon_name(const OUString& rIconName) override
6887 GdkPixbuf* pixbuf = load_icon_by_name(rIconName);
6888 if (!pixbuf)
6889 gtk_button_set_image(m_pButton, nullptr);
6890 else
6892 gtk_button_set_image(m_pButton, gtk_image_new_from_pixbuf(pixbuf));
6893 g_object_unref(pixbuf);
6897 virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
6899 GdkPixbuf* pixbuf = getPixbuf(rImage);
6900 if (!pixbuf)
6901 gtk_button_set_image(m_pButton, nullptr);
6902 else
6904 gtk_button_set_image(m_pButton, gtk_image_new_from_pixbuf(pixbuf));
6905 g_object_unref(pixbuf);
6909 virtual OUString get_label() const override
6911 return ::get_label(m_pButton);
6914 virtual void set_label_line_wrap(bool wrap) override
6916 GtkWidget* pChild = get_label_widget();
6917 gtk_label_set_line_wrap(GTK_LABEL(pChild), wrap);
6920 virtual void set_font(const vcl::Font& rFont) override
6922 m_xFont.reset(new vcl::Font(rFont));
6923 GtkWidget* pChild = get_label_widget();
6924 ::set_font(GTK_LABEL(pChild), rFont);
6927 virtual vcl::Font get_font() override
6929 if (m_xFont)
6930 return *m_xFont;
6931 return GtkInstanceWidget::get_font();
6934 // allow us to block buttons with click handlers making dialogs return a response
6935 bool has_click_handler() const
6937 return m_aClickHdl.IsSet();
6940 void clear_click_handler()
6942 m_aClickHdl = Link<Button&, void>();
6945 virtual void disable_notify_events() override
6947 g_signal_handler_block(m_pButton, m_nSignalId);
6948 GtkInstanceContainer::disable_notify_events();
6951 virtual void enable_notify_events() override
6953 GtkInstanceContainer::enable_notify_events();
6954 g_signal_handler_unblock(m_pButton, m_nSignalId);
6957 virtual ~GtkInstanceButton() override
6959 g_object_steal_data(G_OBJECT(m_pButton), "g-lo-GtkInstanceButton");
6960 g_signal_handler_disconnect(m_pButton, m_nSignalId);
6966 void GtkInstanceDialog::asyncresponse(gint ret)
6968 if (ret == GTK_RESPONSE_HELP)
6970 help();
6971 return;
6974 GtkInstanceButton* pClickHandler = has_click_handler(ret);
6975 if (pClickHandler)
6977 // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
6978 if (ret == GTK_RESPONSE_DELETE_EVENT)
6979 close(false);
6980 return;
6983 if (get_modal())
6984 m_aDialogRun.dec_modal_count();
6985 hide();
6987 // move the self pointer, otherwise it might be de-allocated by time we try to reset it
6988 auto xRunAsyncSelf = std::move(m_xRunAsyncSelf);
6989 auto xDialogController = std::move(m_xDialogController);
6990 auto aFunc = std::move(m_aFunc);
6992 auto nResponseSignalId = m_nResponseSignalId;
6993 auto nCancelSignalId = m_nCancelSignalId;
6994 auto nSignalDeleteId = m_nSignalDeleteId;
6995 m_nResponseSignalId = 0;
6996 m_nCancelSignalId = 0;
6997 m_nSignalDeleteId = 0;
6999 if (aFunc)
7000 aFunc(GtkToVcl(ret));
7002 if (nResponseSignalId)
7003 g_signal_handler_disconnect(m_pDialog, nResponseSignalId);
7004 if (nCancelSignalId)
7005 g_signal_handler_disconnect(m_pDialog, nCancelSignalId);
7006 if (nSignalDeleteId)
7007 g_signal_handler_disconnect(m_pDialog, nSignalDeleteId);
7009 xDialogController.reset();
7010 xRunAsyncSelf.reset();
7013 int GtkInstanceDialog::run()
7015 if (GTK_IS_DIALOG(m_pDialog))
7016 sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
7017 int ret;
7018 while (true)
7020 ret = m_aDialogRun.run();
7021 if (ret == GTK_RESPONSE_HELP)
7023 help();
7024 continue;
7026 else if (has_click_handler(ret))
7027 continue;
7028 break;
7030 hide();
7031 return GtkToVcl(ret);
7034 weld::Button* GtkInstanceDialog::weld_widget_for_response(int nVclResponse)
7036 GtkButton* pButton = get_widget_for_response(VclToGtk(nVclResponse));
7037 if (!pButton)
7038 return nullptr;
7039 return new GtkInstanceButton(pButton, m_pBuilder, false);
7042 void GtkInstanceDialog::response(int nResponse)
7044 int nGtkResponse = VclToGtk(nResponse);
7045 //unblock this response now when activated through code
7046 if (GtkButton* pWidget = get_widget_for_response(nGtkResponse))
7048 void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceButton");
7049 GtkInstanceButton* pButton = static_cast<GtkInstanceButton*>(pData);
7050 if (pButton)
7051 pButton->clear_click_handler();
7053 if (GTK_IS_DIALOG(m_pDialog))
7054 gtk_dialog_response(GTK_DIALOG(m_pDialog), nGtkResponse);
7055 else if (GTK_IS_ASSISTANT(m_pDialog))
7057 if (!m_aDialogRun.loop_is_running())
7058 asyncresponse(nGtkResponse);
7059 else
7061 m_aDialogRun.m_nResponseId = nGtkResponse;
7062 m_aDialogRun.loop_quit();
7067 void GtkInstanceDialog::close(bool bCloseSignal)
7069 GtkInstanceButton* pClickHandler = has_click_handler(GTK_RESPONSE_CANCEL);
7070 if (pClickHandler)
7072 if (bCloseSignal)
7073 g_signal_stop_emission_by_name(m_pDialog, "close");
7074 // make esc (bCloseSignal == true) or window-delete (bCloseSignal == false)
7075 // act as if cancel button was pressed
7076 pClickHandler->clicked();
7077 return;
7079 response(RET_CANCEL);
7082 GtkInstanceButton* GtkInstanceDialog::has_click_handler(int nResponse)
7084 GtkInstanceButton* pButton = nullptr;
7085 // e.g. map GTK_RESPONSE_DELETE_EVENT to GTK_RESPONSE_CANCEL
7086 nResponse = VclToGtk(GtkToVcl(nResponse));
7087 if (GtkButton* pWidget = get_widget_for_response(nResponse))
7089 void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceButton");
7090 pButton = static_cast<GtkInstanceButton*>(pData);
7091 if (pButton && !pButton->has_click_handler())
7092 pButton = nullptr;
7094 return pButton;
7097 namespace {
7099 class GtkInstanceToggleButton : public GtkInstanceButton, public virtual weld::ToggleButton
7101 protected:
7102 GtkToggleButton* m_pToggleButton;
7103 private:
7104 gulong m_nSignalId;
7106 static void signalToggled(GtkToggleButton*, gpointer widget)
7108 GtkInstanceToggleButton* pThis = static_cast<GtkInstanceToggleButton*>(widget);
7109 SolarMutexGuard aGuard;
7110 pThis->signal_toggled();
7112 public:
7113 GtkInstanceToggleButton(GtkToggleButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7114 : GtkInstanceButton(GTK_BUTTON(pButton), pBuilder, bTakeOwnership)
7115 , m_pToggleButton(pButton)
7116 , m_nSignalId(g_signal_connect(m_pToggleButton, "toggled", G_CALLBACK(signalToggled), this))
7120 virtual void set_active(bool active) override
7122 disable_notify_events();
7123 gtk_toggle_button_set_inconsistent(m_pToggleButton, false);
7124 gtk_toggle_button_set_active(m_pToggleButton, active);
7125 enable_notify_events();
7128 virtual bool get_active() const override
7130 return gtk_toggle_button_get_active(m_pToggleButton);
7133 virtual void set_inconsistent(bool inconsistent) override
7135 gtk_toggle_button_set_inconsistent(m_pToggleButton, inconsistent);
7138 virtual bool get_inconsistent() const override
7140 return gtk_toggle_button_get_inconsistent(m_pToggleButton);
7143 virtual void disable_notify_events() override
7145 g_signal_handler_block(m_pToggleButton, m_nSignalId);
7146 GtkInstanceButton::disable_notify_events();
7149 virtual void enable_notify_events() override
7151 GtkInstanceButton::enable_notify_events();
7152 g_signal_handler_unblock(m_pToggleButton, m_nSignalId);
7155 virtual ~GtkInstanceToggleButton() override
7157 g_signal_handler_disconnect(m_pToggleButton, m_nSignalId);
7161 void do_grab(GtkWidget* pWidget)
7163 GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
7164 GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
7165 gdk_seat_grab(pSeat, gtk_widget_get_window(pWidget),
7166 GDK_SEAT_CAPABILITY_ALL, true, nullptr, nullptr, nullptr, nullptr);
7169 void do_ungrab(GtkWidget* pWidget)
7171 GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
7172 GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
7173 gdk_seat_ungrab(pSeat);
7176 GtkPositionType show_menu_older_gtk(GtkWidget* pMenuButton, GtkWindow* pMenu)
7178 //place the toplevel just below its launcher button
7179 GtkWidget* pToplevel = gtk_widget_get_toplevel(pMenuButton);
7180 gint x, y, absx, absy;
7181 gtk_widget_translate_coordinates(pMenuButton, pToplevel, 0, 0, &x, &y);
7182 GdkWindow *pWindow = gtk_widget_get_window(pToplevel);
7183 gdk_window_get_position(pWindow, &absx, &absy);
7185 x += absx;
7186 y += absy;
7188 gint nButtonHeight = gtk_widget_get_allocated_height(pMenuButton);
7189 y += nButtonHeight;
7191 gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), pMenu);
7192 gtk_window_set_transient_for(pMenu, GTK_WINDOW(pToplevel));
7194 gint nMenuWidth, nMenuHeight;
7195 gtk_widget_get_size_request(GTK_WIDGET(pMenu), &nMenuWidth, &nMenuHeight);
7197 if (nMenuWidth == -1 || nMenuHeight == -1)
7199 GtkRequisition req;
7200 gtk_widget_get_preferred_size(GTK_WIDGET(pMenu), nullptr, &req);
7201 if (nMenuWidth == -1)
7202 nMenuWidth = req.width;
7203 if (nMenuHeight == -1)
7204 nMenuHeight = req.height;
7207 bool bSwapForRTL = SwapForRTL(pMenuButton);
7208 if (bSwapForRTL)
7210 gint nButtonWidth = gtk_widget_get_allocated_width(pMenuButton);
7211 x += nButtonWidth;
7212 x -= nMenuWidth;
7215 tools::Rectangle aWorkArea(::get_monitor_workarea(pMenuButton));
7217 // shrink it a little, I find it reassuring to see a little margin with a
7218 // long menu to know the menu is fully on screen
7219 aWorkArea.AdjustTop(8);
7220 aWorkArea.AdjustBottom(-8);
7221 gint endx = x + nMenuWidth;
7222 if (endx > aWorkArea.Right())
7223 x -= endx - aWorkArea.Right();
7224 if (x < 0)
7225 x = 0;
7227 GtkPositionType ePosUsed = GTK_POS_BOTTOM;
7229 gint endy = y + nMenuHeight;
7230 gint nMissingBelow = endy - aWorkArea.Bottom();
7231 if (nMissingBelow > 0)
7233 gint nNewY = y - (nButtonHeight + nMenuHeight);
7234 if (nNewY < aWorkArea.Top())
7236 gint nMissingAbove = aWorkArea.Top() - nNewY;
7237 if (nMissingBelow <= nMissingAbove)
7238 nMenuHeight -= nMissingBelow;
7239 else
7241 nMenuHeight -= nMissingAbove;
7242 y = aWorkArea.Top();
7243 ePosUsed = GTK_POS_TOP;
7245 gtk_widget_set_size_request(GTK_WIDGET(pMenu), nMenuWidth, nMenuHeight);
7247 else
7249 y = nNewY;
7250 ePosUsed = GTK_POS_TOP;
7254 gtk_window_move(pMenu, x, y);
7256 return ePosUsed;
7259 bool show_menu_newer_gtk(GtkWidget* pComboBox, GtkWindow* pMenu)
7261 static auto window_move_to_rect = reinterpret_cast<void (*) (GdkWindow*, const GdkRectangle*, GdkGravity,
7262 GdkGravity, GdkAnchorHints, gint, gint)>(
7263 dlsym(nullptr, "gdk_window_move_to_rect"));
7264 if (!window_move_to_rect)
7265 return false;
7267 #if defined(GDK_WINDOWING_X11)
7268 // under wayland gdk_window_move_to_rect works great for me, but in my current
7269 // gtk 3.24 under X it leaves part of long menus outside the work area
7270 GdkDisplay *pDisplay = gtk_widget_get_display(pComboBox);
7271 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
7272 return false;
7273 #endif
7275 //place the toplevel just below its launcher button
7276 GtkWidget* pToplevel = gtk_widget_get_toplevel(pComboBox);
7277 gint x, y;
7278 gtk_widget_translate_coordinates(pComboBox, pToplevel, 0, 0, &x, &y);
7280 gtk_widget_realize(GTK_WIDGET(pMenu));
7281 gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), pMenu);
7282 gtk_window_set_transient_for(pMenu, GTK_WINDOW(pToplevel));
7284 gint nComboWidth = gtk_widget_get_allocated_width(pComboBox);
7285 gint nComboHeight = gtk_widget_get_allocated_height(pComboBox);
7287 bool bSwapForRTL = SwapForRTL(GTK_WIDGET(pComboBox));
7289 GdkGravity rect_anchor = !bSwapForRTL ? GDK_GRAVITY_SOUTH_WEST : GDK_GRAVITY_SOUTH_EAST;
7290 GdkGravity menu_anchor = !bSwapForRTL ? GDK_GRAVITY_NORTH_WEST : GDK_GRAVITY_NORTH_EAST;
7291 GdkRectangle rect {x, y, nComboWidth, nComboHeight };
7292 GdkWindow* toplevel = gtk_widget_get_window(GTK_WIDGET(pMenu));
7294 window_move_to_rect(toplevel, &rect, rect_anchor, menu_anchor,
7295 static_cast<GdkAnchorHints>(GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_RESIZE_Y |
7296 GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_RESIZE_X),
7297 0, 0);
7299 return true;
7302 GtkPositionType show_menu(GtkWidget* pMenuButton, GtkWindow* pMenu)
7304 // we only use ePosUsed in the replacement-for-X-popover case of a
7305 // MenuButton, so we only need it when show_menu_older_gtk is used
7306 GtkPositionType ePosUsed = GTK_POS_BOTTOM;
7308 // tdf#120764 It isn't allowed under wayland to have two visible popups that share
7309 // the same top level parent. The problem is that since gtk 3.24 tooltips are also
7310 // implemented as popups, which means that we cannot show any popup if there is a
7311 // visible tooltip.
7312 GtkWidget* pParent = gtk_widget_get_toplevel(pMenuButton);
7313 GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
7314 if (pFrame)
7316 // hide any current tooltip
7317 pFrame->HideTooltip();
7318 // don't allow any more to appear until menu is dismissed
7319 pFrame->BlockTooltip();
7322 // try with gdk_window_move_to_rect, but if that's not available, try without
7323 if (!show_menu_newer_gtk(pMenuButton, pMenu))
7324 ePosUsed = show_menu_older_gtk(pMenuButton, pMenu);
7325 gtk_widget_show_all(GTK_WIDGET(pMenu));
7326 gtk_widget_grab_focus(GTK_WIDGET(pMenu));
7327 do_grab(GTK_WIDGET(pMenu));
7329 return ePosUsed;
7332 class GtkInstanceMenuButton : public GtkInstanceToggleButton, public MenuHelper, public virtual weld::MenuButton
7334 protected:
7335 GtkMenuButton* m_pMenuButton;
7336 private:
7337 GtkBox* m_pBox;
7338 GtkImage* m_pImage;
7339 GtkWidget* m_pLabel;
7340 //popover cannot escape dialog under X so stick up own window instead
7341 GtkWindow* m_pMenuHack;
7342 //when doing so, if it's a toolbar menubutton align the menu to the full toolitem
7343 GtkWidget* m_pMenuHackAlign;
7344 GtkWidget* m_pPopover;
7345 gulong m_nSignalId;
7347 static void signalToggled(GtkWidget*, gpointer widget)
7349 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
7350 SolarMutexGuard aGuard;
7351 pThis->toggle_menu();
7354 void toggle_menu()
7356 if (!m_pMenuHack)
7357 return;
7358 if (!get_active())
7360 do_ungrab(GTK_WIDGET(m_pMenuHack));
7362 gtk_widget_hide(GTK_WIDGET(m_pMenuHack));
7363 //put contents back from where the came from
7364 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pMenuHack));
7365 g_object_ref(pChild);
7366 gtk_container_remove(GTK_CONTAINER(m_pMenuHack), pChild);
7367 gtk_container_add(GTK_CONTAINER(m_pPopover), pChild);
7368 g_object_unref(pChild);
7370 // so gdk_window_move_to_rect will work again the next time
7371 gtk_widget_unrealize(GTK_WIDGET(m_pMenuHack));
7373 gtk_widget_set_size_request(GTK_WIDGET(m_pMenuHack), -1, -1);
7375 // undo show_menu tooltip blocking
7376 GtkWidget* pParent = gtk_widget_get_toplevel(GTK_WIDGET(m_pMenuButton));
7377 GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
7378 if (pFrame)
7379 pFrame->UnblockTooltip();
7381 else
7383 //set border width
7384 gtk_container_set_border_width(GTK_CONTAINER(m_pMenuHack), gtk_container_get_border_width(GTK_CONTAINER(m_pPopover)));
7386 //steal popover contents and smuggle into toplevel display window
7387 GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pPopover));
7388 g_object_ref(pChild);
7389 gtk_container_remove(GTK_CONTAINER(m_pPopover), pChild);
7390 gtk_container_add(GTK_CONTAINER(m_pMenuHack), pChild);
7391 g_object_unref(pChild);
7393 GtkPositionType ePosUsed = show_menu(m_pMenuHackAlign ? m_pMenuHackAlign : GTK_WIDGET(m_pMenuButton), m_pMenuHack);
7394 // tdf#132540 keep the placeholder popover on this same side as the replacement menu
7395 gtk_popover_set_position(gtk_menu_button_get_popover(m_pMenuButton), ePosUsed);
7399 static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
7401 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
7402 pThis->grab_broken(pEvent);
7405 void grab_broken(const GdkEventGrabBroken *event)
7407 if (event->grab_window == nullptr)
7409 set_active(false);
7411 else
7413 //try and regrab, so when we lose the grab to the menu of the color palette
7414 //combobox we regain it so the color palette doesn't itself disappear on next
7415 //click on the color palette combobox
7416 do_grab(GTK_WIDGET(m_pMenuHack));
7420 static gboolean signalButtonRelease(GtkWidget* pWidget, GdkEventButton* pEvent, gpointer widget)
7422 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
7423 return pThis->button_release(pWidget, pEvent);
7426 bool button_release(GtkWidget* pWidget, GdkEventButton* pEvent)
7428 //we want to pop down if the button was released outside our popup
7429 gdouble x = pEvent->x_root;
7430 gdouble y = pEvent->y_root;
7431 gint xoffset, yoffset;
7432 gdk_window_get_root_origin(gtk_widget_get_window(pWidget), &xoffset, &yoffset);
7434 GtkAllocation alloc;
7435 gtk_widget_get_allocation(pWidget, &alloc);
7436 xoffset += alloc.x;
7437 yoffset += alloc.y;
7439 gtk_widget_get_allocation(GTK_WIDGET(m_pMenuHack), &alloc);
7440 gint x1 = alloc.x + xoffset;
7441 gint y1 = alloc.y + yoffset;
7442 gint x2 = x1 + alloc.width;
7443 gint y2 = y1 + alloc.height;
7445 if (x > x1 && x < x2 && y > y1 && y < y2)
7446 return false;
7448 set_active(false);
7450 return false;
7453 static gboolean keyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
7455 GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
7456 return pThis->key_press(pEvent);
7459 bool key_press(const GdkEventKey* pEvent)
7461 if (pEvent->keyval == GDK_KEY_Escape)
7463 set_active(false);
7464 return true;
7466 return false;
7469 void ensure_image_widget()
7471 if (!m_pImage)
7473 m_pImage = GTK_IMAGE(gtk_image_new());
7474 gtk_box_pack_start(m_pBox, GTK_WIDGET(m_pImage), false, false, 0);
7475 gtk_box_reorder_child(m_pBox, GTK_WIDGET(m_pImage), 0);
7476 gtk_widget_show(GTK_WIDGET(m_pImage));
7480 public:
7481 GtkInstanceMenuButton(GtkMenuButton* pMenuButton, GtkWidget* pMenuAlign, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7482 : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pMenuButton), pBuilder, bTakeOwnership)
7483 , MenuHelper(gtk_menu_button_get_popup(pMenuButton), false)
7484 , m_pMenuButton(pMenuButton)
7485 , m_pImage(nullptr)
7486 , m_pMenuHack(nullptr)
7487 , m_pMenuHackAlign(pMenuAlign)
7488 , m_pPopover(nullptr)
7489 , m_nSignalId(0)
7491 m_pLabel = gtk_bin_get_child(GTK_BIN(m_pMenuButton));
7492 m_pBox = formatMenuButton(m_pLabel);
7495 virtual void set_size_request(int nWidth, int nHeight) override
7497 // tweak the label to get a narrower size to stick
7498 if (GTK_IS_LABEL(m_pLabel))
7499 gtk_label_set_ellipsize(GTK_LABEL(m_pLabel), PANGO_ELLIPSIZE_MIDDLE);
7500 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
7503 virtual void set_label(const OUString& rText) override
7505 ::set_label(GTK_LABEL(m_pLabel), rText);
7508 virtual OUString get_label() const override
7510 return ::get_label(GTK_LABEL(m_pLabel));
7513 virtual void set_image(VirtualDevice* pDevice) override
7515 ensure_image_widget();
7516 if (pDevice)
7517 gtk_image_set_from_surface(m_pImage, get_underlying_cairo_surface(*pDevice));
7518 else
7519 gtk_image_set_from_surface(m_pImage, nullptr);
7522 virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
7524 ensure_image_widget();
7525 GdkPixbuf* pixbuf = getPixbuf(rImage);
7526 if (pixbuf)
7528 gtk_image_set_from_pixbuf(m_pImage, pixbuf);
7529 g_object_unref(pixbuf);
7531 else
7532 gtk_image_set_from_surface(m_pImage, nullptr);
7535 virtual void insert_item(int pos, const OUString& rId, const OUString& rStr,
7536 const OUString* pIconName, VirtualDevice* pImageSurface, TriState eCheckRadioFalse) override
7538 MenuHelper::insert_item(pos, rId, rStr, pIconName, pImageSurface, eCheckRadioFalse);
7541 virtual void insert_separator(int pos, const OUString& rId) override
7543 MenuHelper::insert_separator(pos, rId);
7546 virtual void remove_item(const OString& rId) override
7548 MenuHelper::remove_item(rId);
7551 virtual void clear() override
7553 clear_items();
7556 virtual void set_item_active(const OString& rIdent, bool bActive) override
7558 MenuHelper::set_item_active(rIdent, bActive);
7561 virtual void set_item_sensitive(const OString& rIdent, bool bSensitive) override
7563 MenuHelper::set_item_sensitive(rIdent, bSensitive);
7566 virtual void set_item_label(const OString& rIdent, const OUString& rLabel) override
7568 MenuHelper::set_item_label(rIdent, rLabel);
7571 virtual OUString get_item_label(const OString& rIdent) const override
7573 return MenuHelper::get_item_label(rIdent);
7576 virtual void set_item_visible(const OString& rIdent, bool bVisible) override
7578 MenuHelper::set_item_visible(rIdent, bVisible);
7581 virtual void set_item_help_id(const OString& rIdent, const OString& rHelpId) override
7583 MenuHelper::set_item_help_id(rIdent, rHelpId);
7586 virtual OString get_item_help_id(const OString& rIdent) const override
7588 return MenuHelper::get_item_help_id(rIdent);
7591 virtual void signal_activate(GtkMenuItem* pItem) override
7593 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
7594 signal_selected(OString(pStr, pStr ? strlen(pStr) : 0));
7597 virtual void set_popover(weld::Widget* pPopover) override
7599 GtkInstanceWidget* pPopoverWidget = dynamic_cast<GtkInstanceWidget*>(pPopover);
7600 m_pPopover = pPopoverWidget ? pPopoverWidget->getWidget() : nullptr;
7602 #if defined(GDK_WINDOWING_X11)
7603 if (!m_pMenuHack)
7605 //under wayland a Popover will work to "escape" the parent dialog, not
7606 //so under X, so come up with this hack to use a raw GtkWindow
7607 GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
7608 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
7610 m_pMenuHack = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
7611 gtk_window_set_type_hint(m_pMenuHack, GDK_WINDOW_TYPE_HINT_COMBO);
7612 gtk_window_set_modal(m_pMenuHack, true);
7613 gtk_window_set_resizable(m_pMenuHack, false);
7614 m_nSignalId = g_signal_connect(GTK_TOGGLE_BUTTON(m_pMenuButton), "toggled", G_CALLBACK(signalToggled), this);
7615 g_signal_connect(m_pMenuHack, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
7616 g_signal_connect(m_pMenuHack, "button-release-event", G_CALLBACK(signalButtonRelease), this);
7617 g_signal_connect(m_pMenuHack, "key-press-event", G_CALLBACK(keyPress), this);
7620 #endif
7622 if (m_pMenuHack)
7624 GtkWidget* pPlaceHolder = gtk_popover_new(GTK_WIDGET(m_pMenuButton));
7625 gtk_popover_set_transitions_enabled(GTK_POPOVER(pPlaceHolder), false);
7627 // tdf#132540 theme the unwanted popover into invisibility
7628 GtkStyleContext *pPopoverContext = gtk_widget_get_style_context(pPlaceHolder);
7629 GtkCssProvider *pProvider = gtk_css_provider_new();
7630 static const gchar data[] = "popover { box-shadow: none; padding: 0 0 0 0; margin: 0 0 0 0; border-image: none; border-image-width: 0 0 0 0; background-image: none; background-color: transparent; border-radius: 0 0 0 0; border-width: 0 0 0 0; border-style: none; border-color: transparent; opacity: 0; min-height: 0; min-width: 0; }";
7631 gtk_css_provider_load_from_data(pProvider, data, -1, nullptr);
7632 gtk_style_context_add_provider(pPopoverContext, GTK_STYLE_PROVIDER(pProvider),
7633 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
7635 gtk_menu_button_set_popover(m_pMenuButton, pPlaceHolder);
7637 else
7639 gtk_menu_button_set_popover(m_pMenuButton, m_pPopover);
7640 if (m_pPopover)
7641 gtk_widget_show_all(m_pPopover);
7645 void set_menu(weld::Menu* pMenu);
7647 static GtkBox* formatMenuButton(GtkWidget* pLabel)
7649 // format the GtkMenuButton "manually" so we can have the dropdown image in GtkMenuButtons shown
7650 // on the right at the same time as an image is shown on the left
7651 g_object_ref(pLabel);
7652 GtkWidget* pContainer = gtk_widget_get_parent(pLabel);
7653 gtk_container_remove(GTK_CONTAINER(pContainer), pLabel);
7655 gint nImageSpacing(2);
7656 GtkStyleContext *pContext = gtk_widget_get_style_context(pContainer);
7657 gtk_style_context_get_style(pContext, "image-spacing", &nImageSpacing, nullptr);
7658 GtkBox* pBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, nImageSpacing));
7660 gtk_box_pack_start(pBox, pLabel, true, true, 0);
7661 g_object_unref(pLabel);
7663 if (gtk_toggle_button_get_mode(GTK_TOGGLE_BUTTON(pContainer)))
7664 gtk_box_pack_end(pBox, gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_BUTTON), false, false, 0);
7666 gtk_container_add(GTK_CONTAINER(pContainer), GTK_WIDGET(pBox));
7667 gtk_widget_show_all(GTK_WIDGET(pBox));
7669 return pBox;
7672 virtual ~GtkInstanceMenuButton() override
7674 if (m_pMenuHack)
7676 g_signal_handler_disconnect(m_pMenuButton, m_nSignalId);
7677 gtk_menu_button_set_popover(m_pMenuButton, nullptr);
7678 gtk_widget_destroy(GTK_WIDGET(m_pMenuHack));
7683 class GtkInstanceMenuToggleButton : public GtkInstanceToggleButton, public MenuHelper
7684 , public virtual weld::MenuToggleButton
7686 private:
7687 GtkContainer* m_pContainer;
7688 GtkButton* m_pToggleMenuButton;
7689 gulong m_nMenuBtnClickedId;
7690 gulong m_nToggleStateFlagsChangedId;
7691 gulong m_nMenuBtnStateFlagsChangedId;
7693 static void signalToggleStateFlagsChanged(GtkWidget* pWidget, GtkStateFlags /*eFlags*/, gpointer widget)
7695 GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
7696 // mirror togglebutton state to menubutton
7697 gtk_widget_set_state_flags(GTK_WIDGET(pThis->m_pToggleMenuButton), gtk_widget_get_state_flags(pWidget), true);
7700 static void signalMenuBtnStateFlagsChanged(GtkWidget* pWidget, GtkStateFlags /*eFlags*/, gpointer widget)
7702 GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
7703 // mirror menubutton to togglebutton, keeping depressed state of menubutton
7704 GtkStateFlags eToggleFlags = gtk_widget_get_state_flags(GTK_WIDGET(pThis->m_pToggleButton));
7705 GtkStateFlags eFlags = gtk_widget_get_state_flags(pWidget);
7706 GtkStateFlags eFinalFlags = static_cast<GtkStateFlags>((eFlags & ~GTK_STATE_FLAG_ACTIVE) |
7707 (eToggleFlags & GTK_STATE_FLAG_ACTIVE));
7708 gtk_widget_set_state_flags(GTK_WIDGET(pThis->m_pToggleButton), eFinalFlags, true);
7711 static void signalMenuBtnClicked(GtkButton*, gpointer widget)
7713 GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
7714 pThis->launch_menu();
7717 void launch_menu()
7719 gtk_widget_set_state_flags(GTK_WIDGET(m_pToggleMenuButton), gtk_widget_get_state_flags(GTK_WIDGET(m_pToggleButton)), true);
7720 GtkWidget* pWidget = GTK_WIDGET(m_pToggleButton);
7722 //run in a sub main loop because we need to keep vcl PopupMenu alive to use
7723 //it during DispatchCommand, returning now to the outer loop causes the
7724 //launching PopupMenu to be destroyed, instead run the subloop here
7725 //until the gtk menu is destroyed
7726 GMainLoop* pLoop = g_main_loop_new(nullptr, true);
7727 gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
7729 #if GTK_CHECK_VERSION(3,22,0)
7730 if (gtk_check_version(3, 22, 0) == nullptr)
7732 // Send a keyboard event through gtk_main_do_event to toggle any active tooltip offs
7733 // before trying to launch the menu
7734 // https://gitlab.gnome.org/GNOME/gtk/issues/1785
7735 GdkEvent *pKeyEvent = GtkSalFrame::makeFakeKeyPress(pWidget);
7736 gtk_main_do_event(pKeyEvent);
7738 GdkEvent *pTriggerEvent = gtk_get_current_event();
7739 if (!pTriggerEvent)
7740 pTriggerEvent = pKeyEvent;
7742 gtk_menu_popup_at_widget(m_pMenu, pWidget, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, pTriggerEvent);
7744 gdk_event_free(pKeyEvent);
7746 else
7747 #endif
7749 guint nButton;
7750 guint32 nTime;
7752 //typically there is an event, and we can then distinguish if this was
7753 //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
7754 //doesn't)
7755 GdkEvent *pEvent = gtk_get_current_event();
7756 if (pEvent)
7758 gdk_event_get_button(pEvent, &nButton);
7759 nTime = gdk_event_get_time(pEvent);
7761 else
7763 nButton = 0;
7764 nTime = GtkSalFrame::GetLastInputEventTime();
7767 gtk_menu_popup(m_pMenu, nullptr, nullptr, nullptr, nullptr, nButton, nTime);
7770 if (g_main_loop_is_running(pLoop))
7772 gdk_threads_leave();
7773 g_main_loop_run(pLoop);
7774 gdk_threads_enter();
7776 g_main_loop_unref(pLoop);
7777 g_signal_handler_disconnect(m_pMenu, nSignalId);
7780 static gboolean signalMenuToggleButton(GtkWidget*, gboolean bGroupCycling, gpointer widget)
7782 GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
7783 return gtk_widget_mnemonic_activate(GTK_WIDGET(pThis->m_pToggleButton), bGroupCycling);
7786 public:
7787 GtkInstanceMenuToggleButton(GtkBuilder* pMenuToggleButtonBuilder, GtkMenuButton* pMenuButton,
7788 GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
7789 : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(gtk_builder_get_object(pMenuToggleButtonBuilder, "togglebutton")),
7790 pBuilder, bTakeOwnership)
7791 , MenuHelper(gtk_menu_button_get_popup(pMenuButton), false)
7792 , m_pContainer(GTK_CONTAINER(gtk_builder_get_object(pMenuToggleButtonBuilder, "box")))
7793 , m_pToggleMenuButton(GTK_BUTTON(gtk_builder_get_object(pMenuToggleButtonBuilder, "menubutton")))
7794 , m_nMenuBtnClickedId(g_signal_connect(m_pToggleMenuButton, "clicked", G_CALLBACK(signalMenuBtnClicked), this))
7795 , m_nToggleStateFlagsChangedId(g_signal_connect(m_pToggleButton, "state-flags-changed", G_CALLBACK(signalToggleStateFlagsChanged), this))
7796 , m_nMenuBtnStateFlagsChangedId(g_signal_connect(m_pToggleMenuButton, "state-flags-changed", G_CALLBACK(signalMenuBtnStateFlagsChanged), this))
7798 GtkInstanceMenuButton::formatMenuButton(gtk_bin_get_child(GTK_BIN(pMenuButton)));
7800 insertAsParent(GTK_WIDGET(pMenuButton), GTK_WIDGET(m_pContainer));
7801 gtk_widget_hide(GTK_WIDGET(pMenuButton));
7803 // move the first GtkMenuButton child, as created by GtkInstanceMenuButton ctor, into the GtkToggleButton
7804 // instead, leaving just the indicator behind in the GtkMenuButton
7805 GtkWidget* pButtonBox = gtk_bin_get_child(GTK_BIN(pMenuButton));
7806 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pButtonBox));
7807 int nGroup = 0;
7808 for (GList* pChild = g_list_first(pChildren); pChild && nGroup < 2; pChild = g_list_next(pChild), ++nGroup)
7810 GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
7811 g_object_ref(pWidget);
7812 gtk_container_remove(GTK_CONTAINER(pButtonBox), pWidget);
7813 if (nGroup == 0)
7814 gtk_container_add(GTK_CONTAINER(m_pToggleButton), pWidget);
7815 else
7816 gtk_container_add(GTK_CONTAINER(m_pToggleMenuButton), pWidget);
7817 gtk_widget_show_all(pWidget);
7818 g_object_unref(pWidget);
7820 g_list_free(pChildren);
7822 // match the GtkToggleButton relief to the GtkMenuButton
7823 GtkReliefStyle eStyle = gtk_button_get_relief(GTK_BUTTON(pMenuButton));
7824 gtk_button_set_relief(GTK_BUTTON(m_pToggleButton), eStyle);
7825 gtk_button_set_relief(GTK_BUTTON(m_pToggleMenuButton), eStyle);
7827 // move the GtkMenuButton margins up to the new parent
7828 gtk_widget_set_margin_top(GTK_WIDGET(m_pContainer),
7829 gtk_widget_get_margin_top(GTK_WIDGET(pMenuButton)));
7830 gtk_widget_set_margin_bottom(GTK_WIDGET(m_pContainer),
7831 gtk_widget_get_margin_bottom(GTK_WIDGET(pMenuButton)));
7832 gtk_widget_set_margin_left(GTK_WIDGET(m_pContainer),
7833 gtk_widget_get_margin_left(GTK_WIDGET(pMenuButton)));
7834 gtk_widget_set_margin_right(GTK_WIDGET(m_pContainer),
7835 gtk_widget_get_margin_right(GTK_WIDGET(pMenuButton)));
7837 gtk_menu_detach(m_pMenu);
7838 gtk_menu_attach_to_widget(m_pMenu, GTK_WIDGET(m_pToggleButton), nullptr);
7840 g_signal_connect(m_pContainer, "mnemonic-activate", G_CALLBACK(signalMenuToggleButton), this);
7843 virtual void disable_notify_events() override
7845 g_signal_handler_block(m_pToggleMenuButton, m_nMenuBtnClickedId);
7846 GtkInstanceToggleButton::disable_notify_events();
7849 virtual void enable_notify_events() override
7851 GtkInstanceToggleButton::enable_notify_events();
7852 g_signal_handler_unblock(m_pToggleMenuButton, m_nMenuBtnClickedId);
7855 virtual ~GtkInstanceMenuToggleButton()
7857 g_signal_handler_disconnect(m_pToggleButton, m_nToggleStateFlagsChangedId);
7858 g_signal_handler_disconnect(m_pToggleMenuButton, m_nMenuBtnStateFlagsChangedId);
7859 g_signal_handler_disconnect(m_pToggleMenuButton, m_nMenuBtnClickedId);
7862 virtual void insert_item(int pos, const OUString& rId, const OUString& rStr,
7863 const OUString* pIconName, VirtualDevice* pImageSurface, TriState eCheckRadioFalse) override
7865 MenuHelper::insert_item(pos, rId, rStr, pIconName, pImageSurface, eCheckRadioFalse);
7868 virtual void insert_separator(int pos, const OUString& rId) override
7870 MenuHelper::insert_separator(pos, rId);
7873 virtual void remove_item(const OString& rId) override
7875 MenuHelper::remove_item(rId);
7878 virtual void clear() override
7880 clear_items();
7883 virtual void set_item_active(const OString& rIdent, bool bActive) override
7885 MenuHelper::set_item_active(rIdent, bActive);
7888 virtual void set_item_sensitive(const OString& rIdent, bool bSensitive) override
7890 MenuHelper::set_item_sensitive(rIdent, bSensitive);
7893 virtual void set_item_label(const OString& rIdent, const OUString& rLabel) override
7895 MenuHelper::set_item_label(rIdent, rLabel);
7898 virtual OUString get_item_label(const OString& rIdent) const override
7900 return MenuHelper::get_item_label(rIdent);
7903 virtual void set_item_visible(const OString& rIdent, bool bVisible) override
7905 MenuHelper::set_item_visible(rIdent, bVisible);
7908 virtual void set_item_help_id(const OString& rIdent, const OString& rHelpId) override
7910 MenuHelper::set_item_help_id(rIdent, rHelpId);
7913 virtual OString get_item_help_id(const OString& rIdent) const override
7915 return MenuHelper::get_item_help_id(rIdent);
7918 virtual void signal_activate(GtkMenuItem* pItem) override
7920 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
7921 signal_selected(OString(pStr, pStr ? strlen(pStr) : 0));
7924 virtual void set_popover(weld::Widget* /*pPopover*/) override
7926 assert(false && "not implemented");
7930 class GtkInstanceMenu : public MenuHelper, public virtual weld::Menu
7932 protected:
7933 std::vector<GtkMenuItem*> m_aExtraItems;
7934 OString m_sActivated;
7935 MenuHelper* m_pTopLevelMenuHelper;
7937 private:
7938 virtual void signal_activate(GtkMenuItem* pItem) override
7940 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
7941 m_sActivated = OString(pStr, pStr ? strlen(pStr) : 0);
7942 weld::Menu::signal_activate(m_sActivated);
7945 void clear_extras()
7947 if (m_aExtraItems.empty())
7948 return;
7949 if (m_pTopLevelMenuHelper)
7951 for (auto a : m_aExtraItems)
7952 m_pTopLevelMenuHelper->remove_from_map(a);
7954 m_aExtraItems.clear();
7957 public:
7958 GtkInstanceMenu(GtkMenu* pMenu, bool bTakeOwnership)
7959 : MenuHelper(pMenu, bTakeOwnership)
7960 , m_pTopLevelMenuHelper(nullptr)
7962 g_object_set_data(G_OBJECT(m_pMenu), "g-lo-GtkInstanceMenu", this);
7963 // tdf#122527 if we're welding a submenu of a menu of a MenuButton,
7964 // then find that MenuButton parent so that when adding items to this
7965 // menu we can inform the MenuButton of their addition
7966 GtkMenu* pTopLevelMenu = pMenu;
7967 while (true)
7969 GtkWidget* pAttached = gtk_menu_get_attach_widget(pTopLevelMenu);
7970 if (!pAttached || !GTK_IS_MENU_ITEM(pAttached))
7971 break;
7972 GtkWidget* pParent = gtk_widget_get_parent(pAttached);
7973 if (!pParent || !GTK_IS_MENU(pParent))
7974 break;
7975 pTopLevelMenu = GTK_MENU(pParent);
7977 if (pTopLevelMenu == pMenu)
7978 return;
7980 // maybe the toplevel is a menubutton
7981 GtkWidget* pAttached = gtk_menu_get_attach_widget(pTopLevelMenu);
7982 if (pAttached && GTK_IS_MENU_BUTTON(pAttached))
7984 void* pData = g_object_get_data(G_OBJECT(pAttached), "g-lo-GtkInstanceButton");
7985 m_pTopLevelMenuHelper = dynamic_cast<GtkInstanceMenuButton*>(static_cast<GtkInstanceButton*>(pData));
7987 // or maybe a menu
7988 if (!m_pTopLevelMenuHelper)
7990 void* pData = g_object_get_data(G_OBJECT(pTopLevelMenu), "g-lo-GtkInstanceMenu");
7991 m_pTopLevelMenuHelper = static_cast<GtkInstanceMenu*>(pData);
7995 virtual OString popup_at_rect(weld::Widget* pParent, const tools::Rectangle &rRect) override
7997 m_sActivated.clear();
7999 GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
8000 assert(pGtkWidget);
8002 GtkWidget* pWidget = pGtkWidget->getWidget();
8003 gtk_menu_attach_to_widget(m_pMenu, pWidget, nullptr);
8005 //run in a sub main loop because we need to keep vcl PopupMenu alive to use
8006 //it during DispatchCommand, returning now to the outer loop causes the
8007 //launching PopupMenu to be destroyed, instead run the subloop here
8008 //until the gtk menu is destroyed
8009 GMainLoop* pLoop = g_main_loop_new(nullptr, true);
8010 gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
8012 #if GTK_CHECK_VERSION(3,22,0)
8013 if (gtk_check_version(3, 22, 0) == nullptr)
8015 GdkRectangle aRect{static_cast<int>(rRect.Left()), static_cast<int>(rRect.Top()),
8016 static_cast<int>(rRect.GetWidth()), static_cast<int>(rRect.GetHeight())};
8017 if (SwapForRTL(pWidget))
8018 aRect.x = gtk_widget_get_allocated_width(pWidget) - aRect.width - 1 - aRect.x;
8020 // Send a keyboard event through gtk_main_do_event to toggle any active tooltip offs
8021 // before trying to launch the menu
8022 // https://gitlab.gnome.org/GNOME/gtk/issues/1785
8023 GdkEvent *pKeyEvent = GtkSalFrame::makeFakeKeyPress(pWidget);
8024 gtk_main_do_event(pKeyEvent);
8026 GdkEvent *pTriggerEvent = gtk_get_current_event();
8027 if (!pTriggerEvent)
8028 pTriggerEvent = pKeyEvent;
8030 gtk_menu_popup_at_rect(m_pMenu, gtk_widget_get_window(pWidget), &aRect, GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_NORTH_WEST, pTriggerEvent);
8032 gdk_event_free(pKeyEvent);
8034 else
8035 #else
8036 (void) rRect;
8037 #endif
8039 guint nButton;
8040 guint32 nTime;
8042 //typically there is an event, and we can then distinguish if this was
8043 //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
8044 //doesn't)
8045 GdkEvent *pEvent = gtk_get_current_event();
8046 if (pEvent)
8048 gdk_event_get_button(pEvent, &nButton);
8049 nTime = gdk_event_get_time(pEvent);
8051 else
8053 nButton = 0;
8054 nTime = GtkSalFrame::GetLastInputEventTime();
8057 gtk_menu_popup(m_pMenu, nullptr, nullptr, nullptr, nullptr, nButton, nTime);
8060 if (g_main_loop_is_running(pLoop))
8062 gdk_threads_leave();
8063 g_main_loop_run(pLoop);
8064 gdk_threads_enter();
8066 g_main_loop_unref(pLoop);
8067 g_signal_handler_disconnect(m_pMenu, nSignalId);
8068 gtk_menu_detach(m_pMenu);
8070 return m_sActivated;
8073 virtual void set_sensitive(const OString& rIdent, bool bSensitive) override
8075 set_item_sensitive(rIdent, bSensitive);
8078 virtual void set_active(const OString& rIdent, bool bActive) override
8080 set_item_active(rIdent, bActive);
8083 virtual bool get_active(const OString& rIdent) const override
8085 return get_item_active(rIdent);
8088 virtual void set_visible(const OString& rIdent, bool bShow) override
8090 set_item_visible(rIdent, bShow);
8093 virtual void set_label(const OString& rIdent, const OUString& rLabel) override
8095 set_item_label(rIdent, rLabel);
8098 virtual OUString get_label(const OString& rIdent) const override
8100 return get_item_label(rIdent);
8103 virtual void insert_separator(int pos, const OUString& rId) override
8105 MenuHelper::insert_separator(pos, rId);
8108 virtual void clear() override
8110 clear_extras();
8111 clear_items();
8114 virtual void insert(int pos, const OUString& rId, const OUString& rStr,
8115 const OUString* pIconName, VirtualDevice* pImageSurface,
8116 TriState eCheckRadioFalse) override
8118 GtkWidget* pImage = nullptr;
8119 if (pIconName)
8121 if (GdkPixbuf* pixbuf = load_icon_by_name(*pIconName))
8123 pImage = gtk_image_new_from_pixbuf(pixbuf);
8124 g_object_unref(pixbuf);
8127 else if (pImageSurface)
8129 pImage = image_new_from_virtual_device(*pImageSurface);
8132 GtkWidget *pItem;
8133 if (pImage)
8135 GtkWidget *pBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
8136 GtkWidget *pLabel = gtk_label_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
8137 pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new() : gtk_menu_item_new();
8138 gtk_container_add(GTK_CONTAINER(pBox), pImage);
8139 gtk_container_add(GTK_CONTAINER(pBox), pLabel);
8140 gtk_container_add(GTK_CONTAINER(pItem), pBox);
8141 gtk_widget_show_all(pItem);
8143 else
8145 pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr())
8146 : gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
8149 if (eCheckRadioFalse == TRISTATE_FALSE)
8150 gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(pItem), true);
8152 gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
8153 gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
8154 gtk_widget_show(pItem);
8155 GtkMenuItem* pMenuItem = GTK_MENU_ITEM(pItem);
8156 m_aExtraItems.push_back(pMenuItem);
8157 add_to_map(pMenuItem);
8158 if (m_pTopLevelMenuHelper)
8159 m_pTopLevelMenuHelper->add_to_map(pMenuItem);
8160 if (pos != -1)
8161 gtk_menu_reorder_child(m_pMenu, pItem, pos);
8164 virtual OString get_id(int pos) const override
8166 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
8167 gpointer pMenuItem = g_list_nth_data(pChildren, pos);
8168 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pMenuItem));
8169 OString id(pStr, pStr ? strlen(pStr) : 0);
8170 g_list_free(pChildren);
8171 return id;
8174 virtual int n_children() const override
8176 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
8177 int nLen = g_list_length(pChildren);
8178 g_list_free(pChildren);
8179 return nLen;
8182 void remove(const OString& rIdent) override
8184 if (!m_aExtraItems.empty())
8186 GtkMenuItem* pMenuItem = m_aMap[rIdent];
8187 auto iter = std::find(m_aExtraItems.begin(), m_aExtraItems.end(), pMenuItem);
8188 if (iter != m_aExtraItems.end())
8190 if (m_pTopLevelMenuHelper)
8191 m_pTopLevelMenuHelper->remove_from_map(pMenuItem);
8192 m_aExtraItems.erase(iter);
8195 MenuHelper::remove_item(rIdent);
8198 virtual ~GtkInstanceMenu() override
8200 clear_extras();
8201 g_object_steal_data(G_OBJECT(m_pMenu), "g-lo-GtkInstanceMenu");
8205 vcl::ImageType GtkToVcl(GtkIconSize eSize)
8207 vcl::ImageType eRet;
8208 switch (eSize)
8210 case GTK_ICON_SIZE_MENU:
8211 case GTK_ICON_SIZE_SMALL_TOOLBAR:
8212 case GTK_ICON_SIZE_BUTTON:
8213 eRet = vcl::ImageType::Size16;
8214 break;
8215 case GTK_ICON_SIZE_LARGE_TOOLBAR:
8216 eRet = vcl::ImageType::Size26;
8217 break;
8218 case GTK_ICON_SIZE_DND:
8219 case GTK_ICON_SIZE_DIALOG:
8220 eRet = vcl::ImageType::Size32;
8221 break;
8222 default:
8223 case GTK_ICON_SIZE_INVALID:
8224 eRet = vcl::ImageType::Small;
8225 break;
8227 return eRet;
8230 GtkIconSize VclToGtk(vcl::ImageType eSize)
8232 GtkIconSize eRet;
8233 switch (eSize)
8235 case vcl::ImageType::Size16:
8236 eRet = GTK_ICON_SIZE_SMALL_TOOLBAR;
8237 break;
8238 case vcl::ImageType::Size26:
8239 eRet = GTK_ICON_SIZE_LARGE_TOOLBAR;
8240 break;
8241 case vcl::ImageType::Size32:
8242 eRet = GTK_ICON_SIZE_DIALOG;
8243 break;
8244 default:
8245 O3TL_UNREACHABLE;
8247 return eRet;
8251 void GtkInstanceMenuButton::set_menu(weld::Menu* pMenu)
8253 GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
8254 m_pPopover = nullptr;
8255 GtkWidget* pMenuWidget = GTK_WIDGET(pPopoverWidget ? pPopoverWidget->getMenu() : nullptr);
8256 gtk_menu_button_set_popup(m_pMenuButton, pMenuWidget);
8259 namespace {
8261 class GtkInstanceToolbar : public GtkInstanceWidget, public virtual weld::Toolbar
8263 private:
8264 GtkToolbar* m_pToolbar;
8265 GtkCssProvider *m_pMenuButtonProvider;
8267 std::map<OString, GtkToolItem*> m_aMap;
8268 std::map<OString, std::unique_ptr<GtkInstanceMenuButton>> m_aMenuButtonMap;
8270 // at the time of writing there is no gtk_menu_tool_button_set_popover available
8271 // though there will be in the future
8272 // https://gitlab.gnome.org/GNOME/gtk/commit/03e30431a8af9a947a0c4ccab545f24da16bfe17?w=1
8273 static void find_menu_button(GtkWidget *pWidget, gpointer user_data)
8275 if (g_strcmp0(gtk_widget_get_name(pWidget), "GtkMenuButton") == 0)
8277 GtkWidget **ppToggleButton = static_cast<GtkWidget**>(user_data);
8278 *ppToggleButton = pWidget;
8280 else if (GTK_IS_CONTAINER(pWidget))
8281 gtk_container_forall(GTK_CONTAINER(pWidget), find_menu_button, user_data);
8284 static void find_menupeer_button(GtkWidget *pWidget, gpointer user_data)
8286 if (g_strcmp0(gtk_widget_get_name(pWidget), "GtkButton") == 0)
8288 GtkWidget **ppButton = static_cast<GtkWidget**>(user_data);
8289 *ppButton = pWidget;
8291 else if (GTK_IS_CONTAINER(pWidget))
8292 gtk_container_forall(GTK_CONTAINER(pWidget), find_menupeer_button, user_data);
8295 static void collect(GtkWidget* pItem, gpointer widget)
8297 if (GTK_IS_TOOL_ITEM(pItem))
8299 GtkToolItem* pToolItem = GTK_TOOL_ITEM(pItem);
8300 GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
8302 GtkMenuButton* pMenuButton = nullptr;
8303 if (GTK_IS_MENU_TOOL_BUTTON(pItem))
8304 find_menu_button(pItem, &pMenuButton);
8306 pThis->add_to_map(pToolItem, pMenuButton);
8310 void add_to_map(GtkToolItem* pToolItem, GtkMenuButton* pMenuButton)
8312 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pToolItem));
8313 OString id(pStr, pStr ? strlen(pStr) : 0);
8314 m_aMap[id] = pToolItem;
8315 if (pMenuButton)
8317 m_aMenuButtonMap[id] = std::make_unique<GtkInstanceMenuButton>(pMenuButton, GTK_WIDGET(pToolItem), m_pBuilder, false);
8318 // so that, e.g. with focus initially in writer main document then
8319 // after clicking the heading menu in the writer navigator focus is
8320 // left in the main document and not in the toolbar
8321 gtk_button_set_focus_on_click(GTK_BUTTON(pMenuButton), false);
8322 g_signal_connect(pMenuButton, "toggled", G_CALLBACK(signalItemToggled), this);
8324 if (pMenuButton)
8326 // by default the GtkMenuButton down arrow button is as wide as
8327 // a normal button and LibreOffice's original ones are very
8328 // narrow, that assumption is fairly baked into the toolbar and
8329 // sidebar designs, try and minimize the width of the dropdown
8330 // zone.
8331 GtkStyleContext *pButtonContext = gtk_widget_get_style_context(GTK_WIDGET(pMenuButton));
8333 if (!m_pMenuButtonProvider)
8335 m_pMenuButtonProvider = gtk_css_provider_new();
8336 static const gchar data[] = "* { "
8337 "padding: 0;"
8338 "margin-left: 0px;"
8339 "margin-right: 0px;"
8340 "min-width: 4px;"
8341 "}";
8342 gtk_css_provider_load_from_data(m_pMenuButtonProvider, data, -1, nullptr);
8345 gtk_style_context_add_provider(pButtonContext,
8346 GTK_STYLE_PROVIDER(m_pMenuButtonProvider),
8347 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
8351 if (!GTK_IS_TOOL_BUTTON(pToolItem))
8352 return;
8353 g_signal_connect(pToolItem, "clicked", G_CALLBACK(signalItemClicked), this);
8356 static void signalItemClicked(GtkToolButton* pItem, gpointer widget)
8358 GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
8359 SolarMutexGuard aGuard;
8360 pThis->signal_item_clicked(pItem);
8363 void signal_item_clicked(GtkToolButton* pItem)
8365 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
8366 signal_clicked(OString(pStr, pStr ? strlen(pStr) : 0));
8369 static void signalItemToggled(GtkToggleButton* pItem, gpointer widget)
8371 GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
8372 SolarMutexGuard aGuard;
8373 pThis->signal_item_toggled(pItem);
8376 void signal_item_toggled(GtkToggleButton* pItem)
8378 for (auto& a : m_aMenuButtonMap)
8380 if (a.second->getWidget() == GTK_WIDGET(pItem))
8382 signal_toggle_menu(a.first);
8383 break;
8388 static void set_item_image(GtkToolButton* pItem, const css::uno::Reference<css::graphic::XGraphic>& rIcon)
8390 GtkWidget* pImage = nullptr;
8392 if (GdkPixbuf* pixbuf = getPixbuf(rIcon))
8394 pImage = gtk_image_new_from_pixbuf(pixbuf);
8395 g_object_unref(pixbuf);
8396 gtk_widget_show(pImage);
8399 gtk_tool_button_set_icon_widget(pItem, pImage);
8402 void set_item_image(GtkToolButton* pItem, const VirtualDevice* pDevice)
8404 GtkWidget* pImage = nullptr;
8406 if (pDevice)
8408 pImage = image_new_from_virtual_device(*pDevice);
8409 gtk_widget_show(pImage);
8412 gtk_tool_button_set_icon_widget(pItem, pImage);
8413 gtk_widget_queue_draw(GTK_WIDGET(m_pToolbar));
8416 public:
8417 GtkInstanceToolbar(GtkToolbar* pToolbar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
8418 : GtkInstanceWidget(GTK_WIDGET(pToolbar), pBuilder, bTakeOwnership)
8419 , m_pToolbar(pToolbar)
8420 , m_pMenuButtonProvider(nullptr)
8422 gtk_container_foreach(GTK_CONTAINER(pToolbar), collect, this);
8425 void disable_item_notify_events()
8427 for (auto& a : m_aMap)
8429 g_signal_handlers_block_by_func(a.second, reinterpret_cast<void*>(signalItemClicked), this);
8433 void enable_item_notify_events()
8435 for (auto& a : m_aMap)
8437 g_signal_handlers_unblock_by_func(a.second, reinterpret_cast<void*>(signalItemClicked), this);
8441 virtual void set_item_sensitive(const OString& rIdent, bool bSensitive) override
8443 disable_item_notify_events();
8444 gtk_widget_set_sensitive(GTK_WIDGET(m_aMap[rIdent]), bSensitive);
8445 enable_item_notify_events();
8448 virtual bool get_item_sensitive(const OString& rIdent) const override
8450 return gtk_widget_get_sensitive(GTK_WIDGET(m_aMap.find(rIdent)->second));
8453 virtual void set_item_visible(const OString& rIdent, bool bVisible) override
8455 disable_item_notify_events();
8456 gtk_widget_set_visible(GTK_WIDGET(m_aMap[rIdent]), bVisible);
8457 enable_item_notify_events();
8460 virtual void set_item_help_id(const OString& rIdent, const OString& rHelpId) override
8462 ::set_help_id(GTK_WIDGET(m_aMap[rIdent]), rHelpId);
8465 virtual bool get_item_visible(const OString& rIdent) const override
8467 return gtk_widget_get_visible(GTK_WIDGET(m_aMap.find(rIdent)->second));
8470 virtual void set_item_active(const OString& rIdent, bool bActive) override
8472 disable_item_notify_events();
8474 GtkToolItem* pToolButton = m_aMap.find(rIdent)->second;
8476 if (GTK_IS_TOGGLE_TOOL_BUTTON(pToolButton))
8477 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(pToolButton), bActive);
8478 else
8480 GtkButton* pButton = nullptr;
8481 // there is no GtkMenuToggleToolButton so abuse the CHECKED state of the GtkMenuToolButton button
8482 // to emulate one
8483 find_menupeer_button(GTK_WIDGET(pToolButton), &pButton);
8484 if (pButton)
8486 auto eState = gtk_widget_get_state_flags(GTK_WIDGET(pButton)) & ~GTK_STATE_FLAG_CHECKED;
8487 if (bActive)
8488 eState |= GTK_STATE_FLAG_CHECKED;
8489 gtk_widget_set_state_flags(GTK_WIDGET(pButton), static_cast<GtkStateFlags>(eState), true);
8493 enable_item_notify_events();
8496 virtual bool get_item_active(const OString& rIdent) const override
8498 GtkToolItem* pToolButton = m_aMap.find(rIdent)->second;
8500 if (GTK_IS_TOGGLE_TOOL_BUTTON(pToolButton))
8501 return gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(pToolButton));
8502 else
8504 GtkButton* pButton = nullptr;
8505 // there is no GtkMenuToggleToolButton so abuse the CHECKED state of the GtkMenuToolButton button
8506 // to emulate one
8507 find_menupeer_button(GTK_WIDGET(pToolButton), &pButton);
8508 if (pButton)
8510 return gtk_widget_get_state_flags(GTK_WIDGET(pButton)) & GTK_STATE_FLAG_CHECKED;
8514 return false;
8517 virtual void set_menu_item_active(const OString& rIdent, bool bActive) override
8519 disable_item_notify_events();
8521 auto aFind = m_aMenuButtonMap.find(rIdent);
8522 assert (aFind != m_aMenuButtonMap.end());
8523 aFind->second->set_active(bActive);
8525 enable_item_notify_events();
8528 virtual bool get_menu_item_active(const OString& rIdent) const override
8530 auto aFind = m_aMenuButtonMap.find(rIdent);
8531 assert (aFind != m_aMenuButtonMap.end());
8532 return aFind->second->get_active();
8535 virtual void insert_separator(int pos, const OUString& rId) override
8537 GtkToolItem* pItem = gtk_separator_tool_item_new();
8538 gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
8539 gtk_toolbar_insert(m_pToolbar, pItem, pos);
8540 gtk_widget_show(GTK_WIDGET(pItem));
8543 virtual void set_item_popover(const OString& rIdent, weld::Widget* pPopover) override
8545 m_aMenuButtonMap[rIdent]->set_popover(pPopover);
8548 virtual void set_item_menu(const OString& rIdent, weld::Menu* pMenu) override
8550 m_aMenuButtonMap[rIdent]->set_menu(pMenu);
8553 virtual int get_n_items() const override
8555 return gtk_toolbar_get_n_items(m_pToolbar);
8558 virtual OString get_item_ident(int nIndex) const override
8560 GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
8561 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
8562 return OString(pStr, pStr ? strlen(pStr) : 0);
8565 virtual void set_item_ident(int nIndex, const OString& rIdent) override
8567 OString sOldIdent(get_item_ident(nIndex));
8568 m_aMap.erase(m_aMap.find(sOldIdent));
8570 GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
8571 gtk_buildable_set_name(GTK_BUILDABLE(pItem), rIdent.getStr());
8573 // to keep the ids unique, if the new id is already in use by an item,
8574 // change the id of that item to the now unused old ident of this item
8575 auto aFind = m_aMap.find(rIdent);
8576 if (aFind != m_aMap.end())
8578 GtkToolItem* pDupIdItem = aFind->second;
8579 gtk_buildable_set_name(GTK_BUILDABLE(pDupIdItem), sOldIdent.getStr());
8580 m_aMap[sOldIdent] = pDupIdItem;
8583 m_aMap[rIdent] = pItem;
8586 virtual void set_item_label(int nIndex, const OUString& rLabel) override
8588 GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
8589 if (!GTK_IS_TOOL_BUTTON(pItem))
8590 return;
8591 gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem), MapToGtkAccelerator(rLabel).getStr());
8594 virtual void set_item_label(const OString& rIdent, const OUString& rLabel) override
8596 GtkToolItem* pItem = m_aMap[rIdent];
8597 if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
8598 return;
8599 gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem), MapToGtkAccelerator(rLabel).getStr());
8602 OUString get_item_label(const OString& rIdent) const override
8604 const gchar* pText = gtk_tool_button_get_label(GTK_TOOL_BUTTON(m_aMap.find(rIdent)->second));
8605 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
8608 virtual void set_item_icon_name(const OString& rIdent, const OUString& rIconName) override
8610 GtkToolItem* pItem = m_aMap[rIdent];
8611 if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
8612 return;
8614 GtkWidget* pImage = nullptr;
8616 if (GdkPixbuf* pixbuf = getPixbuf(rIconName))
8618 pImage = gtk_image_new_from_pixbuf(pixbuf);
8619 g_object_unref(pixbuf);
8620 gtk_widget_show(pImage);
8623 gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(pItem), pImage);
8626 virtual void set_item_image(const OString& rIdent, const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
8628 GtkToolItem* pItem = m_aMap[rIdent];
8629 if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
8630 return;
8631 set_item_image(GTK_TOOL_BUTTON(pItem), rIcon);
8634 virtual void set_item_image(const OString& rIdent, VirtualDevice* pDevice) override
8636 GtkToolItem* pItem = m_aMap[rIdent];
8637 if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
8638 return;
8639 set_item_image(GTK_TOOL_BUTTON(pItem), pDevice);
8642 virtual void set_item_image(int nIndex, const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
8644 GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
8645 if (!GTK_IS_TOOL_BUTTON(pItem))
8646 return;
8647 set_item_image(GTK_TOOL_BUTTON(pItem), rIcon);
8650 virtual void set_item_tooltip_text(int nIndex, const OUString& rTip) override
8652 GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
8653 gtk_widget_set_tooltip_text(GTK_WIDGET(pItem), OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
8656 virtual void set_item_tooltip_text(const OString& rIdent, const OUString& rTip) override
8658 GtkToolItem* pItem = m_aMap[rIdent];
8659 gtk_widget_set_tooltip_text(GTK_WIDGET(pItem), OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
8662 virtual OUString get_item_tooltip_text(const OString& rIdent) const override
8664 GtkToolItem* pItem = m_aMap.find(rIdent)->second;
8665 const gchar* pStr = gtk_widget_get_tooltip_text(GTK_WIDGET(pItem));
8666 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
8669 virtual vcl::ImageType get_icon_size() const override
8671 return GtkToVcl(gtk_toolbar_get_icon_size(m_pToolbar));
8674 virtual void set_icon_size(vcl::ImageType eType) override
8676 return gtk_toolbar_set_icon_size(m_pToolbar, VclToGtk(eType));
8679 virtual sal_uInt16 get_modifier_state() const override
8681 GdkKeymap* pKeymap = gdk_keymap_get_default();
8682 guint nState = gdk_keymap_get_modifier_state(pKeymap);
8683 return GtkSalFrame::GetKeyModCode(nState);
8686 int get_drop_index(const Point& rPoint) const override
8688 return gtk_toolbar_get_drop_index(m_pToolbar, rPoint.X(), rPoint.Y());
8691 virtual bool has_focus() const override
8693 if (gtk_widget_has_focus(m_pWidget))
8694 return true;
8695 GtkWidget* pTopLevel = gtk_widget_get_toplevel(m_pWidget);
8696 if (!GTK_IS_WINDOW(pTopLevel))
8697 return false;
8698 GtkWidget* pFocus = gtk_window_get_focus(GTK_WINDOW(pTopLevel));
8699 if (!pFocus)
8700 return false;
8701 return gtk_widget_is_ancestor(pFocus, m_pWidget);
8704 virtual void grab_focus() override
8706 if (has_focus())
8707 return;
8708 gtk_widget_grab_focus(m_pWidget);
8709 if (!gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget)))
8711 GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, 0);
8712 gtk_container_set_focus_child(GTK_CONTAINER(m_pWidget), GTK_WIDGET(pItem));
8714 gtk_widget_child_focus(gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget)), GTK_DIR_TAB_FORWARD);
8717 virtual ~GtkInstanceToolbar() override
8719 for (auto& a : m_aMap)
8720 g_signal_handlers_disconnect_by_data(a.second, this);
8724 class GtkInstanceLinkButton : public GtkInstanceContainer, public virtual weld::LinkButton
8726 private:
8727 GtkLinkButton* m_pButton;
8728 gulong m_nSignalId;
8730 static bool signalActivateLink(GtkButton*, gpointer widget)
8732 GtkInstanceLinkButton* pThis = static_cast<GtkInstanceLinkButton*>(widget);
8733 SolarMutexGuard aGuard;
8734 return pThis->signal_activate_link();
8737 public:
8738 GtkInstanceLinkButton(GtkLinkButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
8739 : GtkInstanceContainer(GTK_CONTAINER(pButton), pBuilder, bTakeOwnership)
8740 , m_pButton(pButton)
8741 , m_nSignalId(g_signal_connect(pButton, "activate-link", G_CALLBACK(signalActivateLink), this))
8745 virtual void set_label(const OUString& rText) override
8747 ::set_label(GTK_BUTTON(m_pButton), rText);
8750 virtual OUString get_label() const override
8752 return ::get_label(GTK_BUTTON(m_pButton));
8755 virtual void set_uri(const OUString& rText) override
8757 gtk_link_button_set_uri(m_pButton, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
8760 virtual OUString get_uri() const override
8762 const gchar* pStr = gtk_link_button_get_uri(m_pButton);
8763 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
8766 virtual void disable_notify_events() override
8768 g_signal_handler_block(m_pButton, m_nSignalId);
8769 GtkInstanceContainer::disable_notify_events();
8772 virtual void enable_notify_events() override
8774 GtkInstanceContainer::enable_notify_events();
8775 g_signal_handler_unblock(m_pButton, m_nSignalId);
8778 virtual ~GtkInstanceLinkButton() override
8780 g_signal_handler_disconnect(m_pButton, m_nSignalId);
8784 class GtkInstanceRadioButton : public GtkInstanceToggleButton, public virtual weld::RadioButton
8786 public:
8787 GtkInstanceRadioButton(GtkRadioButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
8788 : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pButton), pBuilder, bTakeOwnership)
8793 class GtkInstanceCheckButton : public GtkInstanceToggleButton, public virtual weld::CheckButton
8795 public:
8796 GtkInstanceCheckButton(GtkCheckButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
8797 : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pButton), pBuilder, bTakeOwnership)
8802 class GtkInstanceScale : public GtkInstanceWidget, public virtual weld::Scale
8804 private:
8805 GtkScale* m_pScale;
8806 gulong m_nValueChangedSignalId;
8808 static void signalValueChanged(GtkScale*, gpointer widget)
8810 GtkInstanceScale* pThis = static_cast<GtkInstanceScale*>(widget);
8811 SolarMutexGuard aGuard;
8812 pThis->signal_value_changed();
8815 public:
8816 GtkInstanceScale(GtkScale* pScale, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
8817 : GtkInstanceWidget(GTK_WIDGET(pScale), pBuilder, bTakeOwnership)
8818 , m_pScale(pScale)
8819 , m_nValueChangedSignalId(g_signal_connect(m_pScale, "value-changed", G_CALLBACK(signalValueChanged), this))
8823 virtual void disable_notify_events() override
8825 g_signal_handler_block(m_pScale, m_nValueChangedSignalId);
8826 GtkInstanceWidget::disable_notify_events();
8829 virtual void enable_notify_events() override
8831 GtkInstanceWidget::enable_notify_events();
8832 g_signal_handler_unblock(m_pScale, m_nValueChangedSignalId);
8835 virtual void set_value(int value) override
8837 disable_notify_events();
8838 gtk_range_set_value(GTK_RANGE(m_pScale), value);
8839 enable_notify_events();
8842 virtual void set_range(int min, int max) override
8844 disable_notify_events();
8845 gtk_range_set_range(GTK_RANGE(m_pScale), min, max);
8846 enable_notify_events();
8849 virtual void set_increments(int step, int page) override
8851 disable_notify_events();
8852 gtk_range_set_increments(GTK_RANGE(m_pScale), step, page);
8853 enable_notify_events();
8856 virtual void get_increments(int& step, int& page) const override
8858 GtkAdjustment* pAdjustment = gtk_range_get_adjustment(GTK_RANGE(m_pScale));
8859 step = gtk_adjustment_get_step_increment(pAdjustment);
8860 page = gtk_adjustment_get_page_increment(pAdjustment);
8863 virtual int get_value() const override
8865 return gtk_range_get_value(GTK_RANGE(m_pScale));
8868 virtual ~GtkInstanceScale() override
8870 g_signal_handler_disconnect(m_pScale, m_nValueChangedSignalId);
8874 class GtkInstanceProgressBar : public GtkInstanceWidget, public virtual weld::ProgressBar
8876 private:
8877 GtkProgressBar* m_pProgressBar;
8879 public:
8880 GtkInstanceProgressBar(GtkProgressBar* pProgressBar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
8881 : GtkInstanceWidget(GTK_WIDGET(pProgressBar), pBuilder, bTakeOwnership)
8882 , m_pProgressBar(pProgressBar)
8886 virtual void set_percentage(int value) override
8888 gtk_progress_bar_set_fraction(m_pProgressBar, value / 100.0);
8891 virtual OUString get_text() const override
8893 const gchar* pText = gtk_progress_bar_get_text(m_pProgressBar);
8894 OUString sRet(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
8895 return sRet;
8898 virtual void set_text(const OUString& rText) override
8900 gtk_progress_bar_set_text(m_pProgressBar, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
8904 class GtkInstanceSpinner : public GtkInstanceWidget, public virtual weld::Spinner
8906 private:
8907 GtkSpinner* m_pSpinner;
8909 public:
8910 GtkInstanceSpinner(GtkSpinner* pSpinner, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
8911 : GtkInstanceWidget(GTK_WIDGET(pSpinner), pBuilder, bTakeOwnership)
8912 , m_pSpinner(pSpinner)
8916 virtual void start() override
8918 gtk_spinner_start(m_pSpinner);
8921 virtual void stop() override
8923 gtk_spinner_stop(m_pSpinner);
8927 class GtkInstanceImage : public GtkInstanceWidget, public virtual weld::Image
8929 private:
8930 GtkImage* m_pImage;
8932 public:
8933 GtkInstanceImage(GtkImage* pImage, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
8934 : GtkInstanceWidget(GTK_WIDGET(pImage), pBuilder, bTakeOwnership)
8935 , m_pImage(pImage)
8939 virtual void set_from_icon_name(const OUString& rIconName) override
8941 GdkPixbuf* pixbuf = load_icon_by_name(rIconName);
8942 if (!pixbuf)
8943 return;
8944 gtk_image_set_from_pixbuf(m_pImage, pixbuf);
8945 g_object_unref(pixbuf);
8948 virtual void set_image(VirtualDevice* pDevice) override
8950 if (pDevice)
8951 gtk_image_set_from_surface(m_pImage, get_underlying_cairo_surface(*pDevice));
8952 else
8953 gtk_image_set_from_surface(m_pImage, nullptr);
8956 virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
8958 GdkPixbuf* pixbuf = getPixbuf(rImage);
8959 gtk_image_set_from_pixbuf(m_pImage, pixbuf);
8960 if (pixbuf)
8961 g_object_unref(pixbuf);
8965 class GtkInstanceCalendar : public GtkInstanceWidget, public virtual weld::Calendar
8967 private:
8968 GtkCalendar* m_pCalendar;
8969 gulong m_nDaySelectedSignalId;
8970 gulong m_nDaySelectedDoubleClickSignalId;
8971 gulong m_nKeyPressEventSignalId;
8972 gulong m_nButtonPressEventSignalId;
8974 static void signalDaySelected(GtkCalendar*, gpointer widget)
8976 GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
8977 pThis->signal_selected();
8980 static void signalDaySelectedDoubleClick(GtkCalendar*, gpointer widget)
8982 GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
8983 pThis->signal_activated();
8986 bool signal_key_press(GdkEventKey* pEvent)
8988 if (pEvent->keyval == GDK_KEY_Return || pEvent->keyval == GDK_KEY_KP_Enter)
8990 signal_activated();
8991 return true;
8993 return false;
8996 static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
8998 GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
8999 return pThis->signal_key_press(pEvent);
9002 static gboolean signalButton(GtkWidget*, GdkEventButton*, gpointer)
9004 // don't let button press get to parent window, for the case of the
9005 // ImplCFieldFloatWin floating window belonging to CalendarField where
9006 // the click on the calendar continues to the parent GtkWindow and
9007 // closePopup is called by GtkSalFrame::signalButton because the click
9008 // window isn't that of the floating parent GtkWindow
9009 return true;
9012 public:
9013 GtkInstanceCalendar(GtkCalendar* pCalendar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
9014 : GtkInstanceWidget(GTK_WIDGET(pCalendar), pBuilder, bTakeOwnership)
9015 , m_pCalendar(pCalendar)
9016 , m_nDaySelectedSignalId(g_signal_connect(pCalendar, "day-selected", G_CALLBACK(signalDaySelected), this))
9017 , m_nDaySelectedDoubleClickSignalId(g_signal_connect(pCalendar, "day-selected-double-click", G_CALLBACK(signalDaySelectedDoubleClick), this))
9018 , m_nKeyPressEventSignalId(g_signal_connect(pCalendar, "key-press-event", G_CALLBACK(signalKeyPress), this))
9019 , m_nButtonPressEventSignalId(g_signal_connect_after(pCalendar, "button-press-event", G_CALLBACK(signalButton), this))
9023 virtual void set_date(const Date& rDate) override
9025 if (!rDate.IsValidAndGregorian())
9026 return;
9028 disable_notify_events();
9029 gtk_calendar_select_month(m_pCalendar, rDate.GetMonth() - 1, rDate.GetYear());
9030 gtk_calendar_select_day(m_pCalendar, rDate.GetDay());
9031 enable_notify_events();
9034 virtual Date get_date() const override
9036 guint year, month, day;
9037 gtk_calendar_get_date(m_pCalendar, &year, &month, &day);
9038 return Date(day, month + 1, year);
9041 virtual void disable_notify_events() override
9043 g_signal_handler_block(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
9044 g_signal_handler_block(m_pCalendar, m_nDaySelectedSignalId);
9045 GtkInstanceWidget::disable_notify_events();
9048 virtual void enable_notify_events() override
9050 GtkInstanceWidget::enable_notify_events();
9051 g_signal_handler_unblock(m_pCalendar, m_nDaySelectedSignalId);
9052 g_signal_handler_unblock(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
9055 virtual ~GtkInstanceCalendar() override
9057 g_signal_handler_disconnect(m_pCalendar, m_nButtonPressEventSignalId);
9058 g_signal_handler_disconnect(m_pCalendar, m_nKeyPressEventSignalId);
9059 g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
9060 g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedSignalId);
9065 namespace
9067 void set_entry_message_type(GtkEntry* pEntry, weld::EntryMessageType eType)
9069 switch (eType)
9071 case weld::EntryMessageType::Normal:
9072 gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, nullptr);
9073 break;
9074 case weld::EntryMessageType::Warning:
9075 gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning");
9076 break;
9077 case weld::EntryMessageType::Error:
9078 gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-error");
9079 break;
9083 gboolean filter_pango_attrs(PangoAttribute *attr, gpointer data)
9085 PangoAttrType* pFilterAttrs = static_cast<PangoAttrType*>(data);
9086 while (*pFilterAttrs)
9088 if (attr->klass->type == *pFilterAttrs)
9089 return true;
9090 ++pFilterAttrs;
9092 return false;
9095 class GtkInstanceEntry : public GtkInstanceWidget, public virtual weld::Entry
9097 private:
9098 GtkEntry* m_pEntry;
9099 std::unique_ptr<vcl::Font> m_xFont;
9100 gulong m_nChangedSignalId;
9101 gulong m_nInsertTextSignalId;
9102 gulong m_nCursorPosSignalId;
9103 gulong m_nSelectionPosSignalId;
9104 gulong m_nActivateSignalId;
9106 static void signalChanged(GtkEntry*, gpointer widget)
9108 GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
9109 SolarMutexGuard aGuard;
9110 pThis->signal_changed();
9113 static void signalInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
9114 gint* position, gpointer widget)
9116 GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
9117 SolarMutexGuard aGuard;
9118 pThis->signal_insert_text(pEntry, pNewText, nNewTextLength, position);
9121 void signal_insert_text(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength, gint* position)
9123 if (!m_aInsertTextHdl.IsSet())
9124 return;
9125 OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
9126 const bool bContinue = m_aInsertTextHdl.Call(sText);
9127 if (bContinue && !sText.isEmpty())
9129 OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
9130 g_signal_handlers_block_by_func(pEntry, reinterpret_cast<gpointer>(signalInsertText), this);
9131 gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position);
9132 g_signal_handlers_unblock_by_func(pEntry, reinterpret_cast<gpointer>(signalInsertText), this);
9134 g_signal_stop_emission_by_name(pEntry, "insert-text");
9137 static void signalCursorPosition(GtkEntry*, GParamSpec*, gpointer widget)
9139 GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
9140 pThis->signal_cursor_position();
9143 static void signalActivate(GtkEntry*, gpointer widget)
9145 GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
9146 pThis->signal_activate();
9149 virtual void ensureMouseEventWidget() override
9151 // The GtkEntry is sufficient to get mouse events without an intermediate GtkEventBox
9152 if (!m_pMouseEventBox)
9153 m_pMouseEventBox = m_pWidget;
9156 protected:
9158 virtual void signal_activate()
9160 if (m_aActivateHdl.IsSet())
9162 SolarMutexGuard aGuard;
9163 if (m_aActivateHdl.Call(*this))
9164 g_signal_stop_emission_by_name(m_pEntry, "activate");
9168 public:
9169 GtkInstanceEntry(GtkEntry* pEntry, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
9170 : GtkInstanceWidget(GTK_WIDGET(pEntry), pBuilder, bTakeOwnership)
9171 , m_pEntry(pEntry)
9172 , m_nChangedSignalId(g_signal_connect(pEntry, "changed", G_CALLBACK(signalChanged), this))
9173 , m_nInsertTextSignalId(g_signal_connect(pEntry, "insert-text", G_CALLBACK(signalInsertText), this))
9174 , m_nCursorPosSignalId(g_signal_connect(pEntry, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
9175 , m_nSelectionPosSignalId(g_signal_connect(pEntry, "notify::selection-bound", G_CALLBACK(signalCursorPosition), this))
9176 , m_nActivateSignalId(g_signal_connect(pEntry, "activate", G_CALLBACK(signalActivate), this))
9180 virtual void set_text(const OUString& rText) override
9182 disable_notify_events();
9183 gtk_entry_set_text(m_pEntry, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
9184 enable_notify_events();
9187 virtual OUString get_text() const override
9189 const gchar* pText = gtk_entry_get_text(m_pEntry);
9190 OUString sRet(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
9191 return sRet;
9194 virtual void set_width_chars(int nChars) override
9196 disable_notify_events();
9197 gtk_entry_set_width_chars(m_pEntry, nChars);
9198 gtk_entry_set_max_width_chars(m_pEntry, nChars);
9199 enable_notify_events();
9202 virtual int get_width_chars() const override
9204 return gtk_entry_get_width_chars(m_pEntry);
9207 virtual void set_max_length(int nChars) override
9209 disable_notify_events();
9210 gtk_entry_set_max_length(m_pEntry, nChars);
9211 enable_notify_events();
9214 virtual void select_region(int nStartPos, int nEndPos) override
9216 disable_notify_events();
9217 gtk_editable_select_region(GTK_EDITABLE(m_pEntry), nStartPos, nEndPos);
9218 enable_notify_events();
9221 bool get_selection_bounds(int& rStartPos, int& rEndPos) override
9223 return gtk_editable_get_selection_bounds(GTK_EDITABLE(m_pEntry), &rStartPos, &rEndPos);
9226 virtual void replace_selection(const OUString& rText) override
9228 disable_notify_events();
9229 gtk_editable_delete_selection(GTK_EDITABLE(m_pEntry));
9230 OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
9231 gint position = gtk_editable_get_position(GTK_EDITABLE(m_pEntry));
9232 gtk_editable_insert_text(GTK_EDITABLE(m_pEntry), sText.getStr(), sText.getLength(),
9233 &position);
9234 enable_notify_events();
9237 virtual void set_position(int nCursorPos) override
9239 disable_notify_events();
9240 gtk_editable_set_position(GTK_EDITABLE(m_pEntry), nCursorPos);
9241 enable_notify_events();
9244 virtual int get_position() const override
9246 return gtk_editable_get_position(GTK_EDITABLE(m_pEntry));
9249 virtual void set_editable(bool bEditable) override
9251 gtk_editable_set_editable(GTK_EDITABLE(m_pEntry), bEditable);
9254 virtual bool get_editable() const override
9256 return gtk_editable_get_editable(GTK_EDITABLE(m_pEntry));
9259 virtual void set_overwrite_mode(bool bOn) override
9261 gtk_entry_set_overwrite_mode(m_pEntry, bOn);
9264 virtual bool get_overwrite_mode() const override
9266 return gtk_entry_get_overwrite_mode(m_pEntry);
9269 virtual void set_message_type(weld::EntryMessageType eType) override
9271 ::set_entry_message_type(m_pEntry, eType);
9274 virtual void disable_notify_events() override
9276 g_signal_handler_block(m_pEntry, m_nActivateSignalId);
9277 g_signal_handler_block(m_pEntry, m_nSelectionPosSignalId);
9278 g_signal_handler_block(m_pEntry, m_nCursorPosSignalId);
9279 g_signal_handler_block(m_pEntry, m_nInsertTextSignalId);
9280 g_signal_handler_block(m_pEntry, m_nChangedSignalId);
9281 GtkInstanceWidget::disable_notify_events();
9284 virtual void enable_notify_events() override
9286 GtkInstanceWidget::enable_notify_events();
9287 g_signal_handler_unblock(m_pEntry, m_nChangedSignalId);
9288 g_signal_handler_unblock(m_pEntry, m_nInsertTextSignalId);
9289 g_signal_handler_unblock(m_pEntry, m_nCursorPosSignalId);
9290 g_signal_handler_unblock(m_pEntry, m_nSelectionPosSignalId);
9291 g_signal_handler_unblock(m_pEntry, m_nActivateSignalId);
9294 virtual void set_font(const vcl::Font& rFont) override
9296 m_xFont.reset(new vcl::Font(rFont));
9297 PangoAttrList* pAttrList = create_attr_list(rFont);
9298 gtk_entry_set_attributes(m_pEntry, pAttrList);
9299 pango_attr_list_unref(pAttrList);
9302 virtual vcl::Font get_font() override
9304 if (m_xFont)
9305 return *m_xFont;
9306 return GtkInstanceWidget::get_font();
9309 void set_font_color(const Color& rColor) override
9311 PangoAttrList* pOrigList = gtk_entry_get_attributes(m_pEntry);
9312 if (rColor == COL_AUTO && !pOrigList) // nothing to do
9313 return;
9315 PangoAttrType aFilterAttrs[] = {PANGO_ATTR_FOREGROUND, PANGO_ATTR_INVALID};
9317 PangoAttrList* pAttrs = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
9318 PangoAttrList* pRemovedAttrs = pOrigList ? pango_attr_list_filter(pAttrs, filter_pango_attrs, &aFilterAttrs) : nullptr;
9320 if (rColor != COL_AUTO)
9321 pango_attr_list_insert(pAttrs, pango_attr_foreground_new(rColor.GetRed()/255.0, rColor.GetGreen()/255.0, rColor.GetBlue()/255.0));
9323 gtk_entry_set_attributes(m_pEntry, pAttrs);
9324 pango_attr_list_unref(pAttrs);
9325 pango_attr_list_unref(pRemovedAttrs);
9328 void fire_signal_changed()
9330 signal_changed();
9333 virtual void cut_clipboard() override
9335 gtk_editable_cut_clipboard(GTK_EDITABLE(m_pEntry));
9338 virtual void copy_clipboard() override
9340 gtk_editable_copy_clipboard(GTK_EDITABLE(m_pEntry));
9343 virtual void paste_clipboard() override
9345 gtk_editable_paste_clipboard(GTK_EDITABLE(m_pEntry));
9348 virtual void set_placeholder_text(const OUString& rText) override
9350 gtk_entry_set_placeholder_text(m_pEntry, rText.toUtf8().getStr());
9353 virtual void grab_focus() override
9355 if (has_focus())
9356 return;
9357 gtk_entry_grab_focus_without_selecting(m_pEntry);
9360 virtual void set_alignment(TxtAlign eXAlign) override
9362 gfloat xalign = 0;
9363 switch (eXAlign)
9365 case TxtAlign::Left:
9366 xalign = 0.0;
9367 break;
9368 case TxtAlign::Center:
9369 xalign = 0.5;
9370 break;
9371 case TxtAlign::Right:
9372 xalign = 1.0;
9373 break;
9375 gtk_entry_set_alignment(m_pEntry, xalign);
9378 virtual ~GtkInstanceEntry() override
9380 g_signal_handler_disconnect(m_pEntry, m_nActivateSignalId);
9381 g_signal_handler_disconnect(m_pEntry, m_nSelectionPosSignalId);
9382 g_signal_handler_disconnect(m_pEntry, m_nCursorPosSignalId);
9383 g_signal_handler_disconnect(m_pEntry, m_nInsertTextSignalId);
9384 g_signal_handler_disconnect(m_pEntry, m_nChangedSignalId);
9388 struct Search
9390 OString str;
9391 int index;
9392 int col;
9393 Search(const OUString& rText, int nCol)
9394 : str(OUStringToOString(rText, RTL_TEXTENCODING_UTF8))
9395 , index(-1)
9396 , col(nCol)
9401 gboolean foreach_find(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, gpointer data)
9403 Search* search = static_cast<Search*>(data);
9404 gchar *pStr = nullptr;
9405 gtk_tree_model_get(model, iter, search->col, &pStr, -1);
9406 bool found = strcmp(pStr, search->str.getStr()) == 0;
9407 if (found)
9409 gint depth;
9410 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
9411 search->index = indices[depth-1];
9413 g_free(pStr);
9414 return found;
9417 void insert_row(GtkListStore* pListStore, GtkTreeIter& iter, int pos, const OUString* pId, const OUString& rText, const OUString* pIconName, const VirtualDevice* pDevice)
9419 if (!pIconName && !pDevice)
9421 gtk_list_store_insert_with_values(pListStore, &iter, pos,
9422 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
9423 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
9424 -1);
9426 else
9428 if (pIconName)
9430 GdkPixbuf* pixbuf = getPixbuf(*pIconName);
9432 gtk_list_store_insert_with_values(pListStore, &iter, pos,
9433 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
9434 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
9435 2, pixbuf,
9436 -1);
9438 if (pixbuf)
9439 g_object_unref(pixbuf);
9441 else
9443 cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
9445 Size aSize(pDevice->GetOutputSizePixel());
9446 cairo_surface_t* target = cairo_surface_create_similar(surface,
9447 cairo_surface_get_content(surface),
9448 aSize.Width(),
9449 aSize.Height());
9451 cairo_t* cr = cairo_create(target);
9452 cairo_set_source_surface(cr, surface, 0, 0);
9453 cairo_paint(cr);
9454 cairo_destroy(cr);
9456 gtk_list_store_insert_with_values(pListStore, &iter, pos,
9457 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
9458 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
9459 3, target,
9460 -1);
9461 cairo_surface_destroy(target);
9467 namespace
9469 gint default_sort_func(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b, gpointer data)
9471 comphelper::string::NaturalStringSorter* pSorter = static_cast<comphelper::string::NaturalStringSorter*>(data);
9472 gchar* pName1;
9473 gchar* pName2;
9474 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(pModel);
9475 gint sort_column_id(0);
9476 gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr);
9477 gtk_tree_model_get(pModel, a, sort_column_id, &pName1, -1);
9478 gtk_tree_model_get(pModel, b, sort_column_id, &pName2, -1);
9479 gint ret = pSorter->compare(OUString(pName1, pName1 ? strlen(pName1) : 0, RTL_TEXTENCODING_UTF8),
9480 OUString(pName2, pName2 ? strlen(pName2) : 0, RTL_TEXTENCODING_UTF8));
9481 g_free(pName1);
9482 g_free(pName2);
9483 return ret;
9486 int starts_with(GtkTreeModel* pTreeModel, const OUString& rStr, int col, int nStartRow, bool bCaseSensitive)
9488 GtkTreeIter iter;
9489 if (!gtk_tree_model_iter_nth_child(pTreeModel, &iter, nullptr, nStartRow))
9490 return -1;
9492 const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
9493 int nRet = nStartRow;
9496 gchar* pStr;
9497 gtk_tree_model_get(pTreeModel, &iter, col, &pStr, -1);
9498 OUString aStr(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
9499 g_free(pStr);
9500 const bool bMatch = !bCaseSensitive ? rI18nHelper.MatchString(rStr, aStr) : aStr.startsWith(rStr);
9501 if (bMatch)
9502 return nRet;
9503 ++nRet;
9504 } while (gtk_tree_model_iter_next(pTreeModel, &iter));
9506 return -1;
9509 struct GtkInstanceTreeIter : public weld::TreeIter
9511 GtkInstanceTreeIter(const GtkInstanceTreeIter* pOrig)
9513 if (pOrig)
9514 iter = pOrig->iter;
9515 else
9516 memset(&iter, 0, sizeof(iter));
9518 GtkInstanceTreeIter(const GtkTreeIter& rOrig)
9520 memcpy(&iter, &rOrig, sizeof(iter));
9522 virtual bool equal(const TreeIter& rOther) const override
9524 return memcmp(&iter, &static_cast<const GtkInstanceTreeIter&>(rOther).iter, sizeof(GtkTreeIter)) == 0;
9526 GtkTreeIter iter;
9529 class GtkInstanceTreeView;
9533 static GtkInstanceTreeView* g_DragSource;
9535 namespace {
9537 struct CompareGtkTreePath
9539 bool operator()(const GtkTreePath* lhs, const GtkTreePath* rhs) const
9541 return gtk_tree_path_compare(lhs, rhs) < 0;
9545 int get_height_row(GtkTreeView* pTreeView, GList* pColumns)
9547 gint nMaxRowHeight = 0;
9548 for (GList* pEntry = g_list_first(pColumns); pEntry; pEntry = g_list_next(pEntry))
9550 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
9551 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
9552 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
9554 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
9555 gint nRowHeight;
9556 gtk_cell_renderer_get_preferred_height(pCellRenderer, GTK_WIDGET(pTreeView), nullptr, &nRowHeight);
9557 nMaxRowHeight = std::max(nMaxRowHeight, nRowHeight);
9559 g_list_free(pRenderers);
9561 return nMaxRowHeight;
9564 int get_height_row_separator(GtkTreeView* pTreeView)
9566 gint nVerticalSeparator;
9567 gtk_widget_style_get(GTK_WIDGET(pTreeView), "vertical-separator", &nVerticalSeparator, nullptr);
9568 return nVerticalSeparator;
9571 int get_height_rows(GtkTreeView* pTreeView, GList* pColumns, int nRows)
9573 gint nMaxRowHeight = get_height_row(pTreeView, pColumns);
9574 gint nVerticalSeparator = get_height_row_separator(pTreeView);
9575 return (nMaxRowHeight * nRows) + (nVerticalSeparator * (nRows + 1));
9578 int get_height_rows(int nRowHeight, int nSeparatorHeight, int nRows)
9580 return (nRowHeight * nRows) + (nSeparatorHeight * (nRows + 1));
9583 tools::Rectangle get_row_area(GtkTreeView* pTreeView, GList* pColumns, GtkTreePath* pPath)
9585 tools::Rectangle aRet;
9587 GdkRectangle aRect;
9588 for (GList* pEntry = g_list_last(pColumns); pEntry; pEntry = g_list_previous(pEntry))
9590 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
9591 gtk_tree_view_get_cell_area(pTreeView, pPath, pColumn, &aRect);
9592 aRet.Union(tools::Rectangle(aRect.x, aRect.y, aRect.x + aRect.width, aRect.y + aRect.height));
9595 return aRet;
9598 struct GtkTreeRowReferenceDeleter
9600 void operator()(GtkTreeRowReference* p) const
9602 gtk_tree_row_reference_free(p);
9606 bool separator_function(const GtkTreePath* path, const std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>>& rSeparatorRows)
9608 bool bFound = false;
9609 for (auto& a : rSeparatorRows)
9611 GtkTreePath* seppath = gtk_tree_row_reference_get_path(a.get());
9612 if (seppath)
9614 bFound = gtk_tree_path_compare(path, seppath) == 0;
9615 gtk_tree_path_free(seppath);
9617 if (bFound)
9618 break;
9620 return bFound;
9623 void tree_store_set(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, ...)
9625 va_list args;
9627 va_start(args, pIter);
9628 gtk_tree_store_set_valist(GTK_TREE_STORE(pTreeModel), pIter, args);
9629 va_end(args);
9632 void list_store_set(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, ...)
9634 va_list args;
9636 va_start(args, pIter);
9637 gtk_list_store_set_valist(GTK_LIST_STORE(pTreeModel), pIter, args);
9638 va_end(args);
9641 void tree_store_insert_with_values(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPos,
9642 gint nTextCol, const gchar* pText,
9643 gint nIdCol, const gchar* pId)
9645 gtk_tree_store_insert_with_values(GTK_TREE_STORE(pTreeModel), pIter, pParent, nPos,
9646 nTextCol, pText, nIdCol, pId, -1);
9649 void list_store_insert_with_values(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPos,
9650 gint nTextCol, const gchar* pText,
9651 gint nIdCol, const gchar* pId)
9653 assert(!pParent); (void)pParent;
9654 gtk_list_store_insert_with_values(GTK_LIST_STORE(pTreeModel), pIter, nPos,
9655 nTextCol, pText, nIdCol, pId, -1);
9658 void tree_store_prepend(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent)
9660 gtk_tree_store_prepend(GTK_TREE_STORE(pTreeModel), pIter, pParent);
9663 void list_store_prepend(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent)
9665 assert(!pParent); (void)pParent;
9666 gtk_list_store_prepend(GTK_LIST_STORE(pTreeModel), pIter);
9669 void tree_store_insert(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPosition)
9671 gtk_tree_store_insert(GTK_TREE_STORE(pTreeModel), pIter, pParent, nPosition);
9674 void list_store_insert(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPosition)
9676 assert(!pParent); (void)pParent;
9677 gtk_list_store_insert(GTK_LIST_STORE(pTreeModel), pIter, nPosition);
9680 void tree_store_clear(GtkTreeModel* pTreeModel)
9682 gtk_tree_store_clear(GTK_TREE_STORE(pTreeModel));
9685 void list_store_clear(GtkTreeModel* pTreeModel)
9687 gtk_list_store_clear(GTK_LIST_STORE(pTreeModel));
9690 void tree_store_remove(GtkTreeModel* pTreeModel, GtkTreeIter *pIter)
9692 gtk_tree_store_remove(GTK_TREE_STORE(pTreeModel), pIter);
9695 void list_store_remove(GtkTreeModel* pTreeModel, GtkTreeIter *pIter)
9697 gtk_list_store_remove(GTK_LIST_STORE(pTreeModel), pIter);
9700 void tree_store_swap(GtkTreeModel* pTreeModel, GtkTreeIter* pIter1, GtkTreeIter* pIter2)
9702 gtk_tree_store_swap(GTK_TREE_STORE(pTreeModel), pIter1, pIter2);
9705 void list_store_swap(GtkTreeModel* pTreeModel, GtkTreeIter* pIter1, GtkTreeIter* pIter2)
9707 gtk_list_store_swap(GTK_LIST_STORE(pTreeModel), pIter1, pIter2);
9710 void tree_store_set_value(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gint nColumn, GValue* pValue)
9712 gtk_tree_store_set_value(GTK_TREE_STORE(pTreeModel), pIter, nColumn, pValue);
9715 void list_store_set_value(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gint nColumn, GValue* pValue)
9717 gtk_list_store_set_value(GTK_LIST_STORE(pTreeModel), pIter, nColumn, pValue);
9720 int promote_arg(bool bArg)
9722 return static_cast<int>(bArg);
9725 class GtkInstanceTreeView : public GtkInstanceContainer, public virtual weld::TreeView
9727 private:
9728 GtkTreeView* m_pTreeView;
9729 GtkTreeModel* m_pTreeModel;
9731 typedef void(*setterFnc)(GtkTreeModel*, GtkTreeIter*, ...);
9732 setterFnc m_Setter;
9734 typedef void(*insertWithValuesFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*, gint, gint, const gchar*, gint, const gchar*);
9735 insertWithValuesFnc m_InsertWithValues;
9737 typedef void(*insertFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*, gint);
9738 insertFnc m_Insert;
9740 typedef void(*prependFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*);
9741 prependFnc m_Prepend;
9743 typedef void(*clearFnc)(GtkTreeModel*);
9744 clearFnc m_Clear;
9746 typedef void(*removeFnc)(GtkTreeModel*, GtkTreeIter*);
9747 removeFnc m_Remove;
9749 typedef void(*swapFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*);
9750 swapFnc m_Swap;
9752 typedef void(*setValueFnc)(GtkTreeModel*, GtkTreeIter*, gint, GValue*);
9753 setValueFnc m_SetValue;
9755 std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
9756 GList *m_pColumns;
9757 std::vector<gulong> m_aColumnSignalIds;
9758 // map from toggle column to toggle visibility column
9759 std::map<int, int> m_aToggleVisMap;
9760 // map from toggle column to tristate column
9761 std::map<int, int> m_aToggleTriStateMap;
9762 // map from text column to text weight column
9763 std::map<int, int> m_aWeightMap;
9764 // map from text column to sensitive column
9765 std::map<int, int> m_aSensitiveMap;
9766 // map from text column to indent column
9767 std::map<int, int> m_aIndentMap;
9768 // map from text column to text align column
9769 std::map<int, int> m_aAlignMap;
9770 // currently expanding parent that logically, but not currently physically,
9771 // contain placeholders
9772 o3tl::sorted_vector<GtkTreePath*, CompareGtkTreePath> m_aExpandingPlaceHolderParents;
9773 // which rows are separators (rare)
9774 std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>> m_aSeparatorRows;
9775 std::vector<GtkSortType> m_aSavedSortTypes;
9776 std::vector<int> m_aSavedSortColumns;
9777 bool m_bWorkAroundBadDragRegion;
9778 bool m_bInDrag;
9779 gint m_nTextCol;
9780 gint m_nTextView;
9781 gint m_nImageCol;
9782 gint m_nExpanderToggleCol;
9783 gint m_nExpanderImageCol;
9784 gint m_nIdCol;
9785 int m_nPendingVAdjustment;
9786 gulong m_nChangedSignalId;
9787 gulong m_nRowActivatedSignalId;
9788 gulong m_nTestExpandRowSignalId;
9789 gulong m_nTestCollapseRowSignalId;
9790 gulong m_nVAdjustmentChangedSignalId;
9791 gulong m_nRowDeletedSignalId;
9792 gulong m_nRowInsertedSignalId;
9793 gulong m_nPopupMenuSignalId;
9794 gulong m_nKeyPressSignalId;
9795 gulong m_nQueryTooltipSignalId;
9796 GtkAdjustment* m_pVAdjustment;
9797 ImplSVEvent* m_pChangeEvent;
9799 DECL_LINK(async_signal_changed, void*, void);
9801 void launch_signal_changed()
9803 //tdf#117991 selection change is sent before the focus change, and focus change
9804 //is what will cause a spinbutton that currently has the focus to set its contents
9805 //as the spin button value. So any LibreOffice callbacks on
9806 //signal-change would happen before the spinbutton value-change occurs.
9807 //To avoid this, send the signal-change to LibreOffice to occur after focus-change
9808 //has been processed
9809 if (m_pChangeEvent)
9810 Application::RemoveUserEvent(m_pChangeEvent);
9811 m_pChangeEvent = Application::PostUserEvent(LINK(this, GtkInstanceTreeView, async_signal_changed));
9814 static void signalChanged(GtkTreeView*, gpointer widget)
9816 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
9817 pThis->launch_signal_changed();
9820 void handle_row_activated()
9822 if (signal_row_activated())
9823 return;
9824 GtkInstanceTreeIter aIter(nullptr);
9825 if (!get_cursor(&aIter))
9826 return;
9827 if (gtk_tree_model_iter_has_child(m_pTreeModel, &aIter.iter))
9828 get_row_expanded(aIter) ? collapse_row(aIter) : expand_row(aIter);
9831 static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget)
9833 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
9834 SolarMutexGuard aGuard;
9835 pThis->handle_row_activated();
9838 virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
9840 return m_aPopupMenuHdl.Call(rCEvt);
9843 void insert_row(GtkTreeIter& iter, const GtkTreeIter* parent, int pos, const OUString* pId, const OUString* pText,
9844 const OUString* pIconName, const VirtualDevice* pDevice)
9846 m_InsertWithValues(m_pTreeModel, &iter, const_cast<GtkTreeIter*>(parent), pos,
9847 m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
9848 m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr());
9850 if (pIconName)
9852 GdkPixbuf* pixbuf = getPixbuf(*pIconName);
9853 m_Setter(m_pTreeModel, &iter, m_nImageCol, pixbuf, -1);
9854 if (pixbuf)
9855 g_object_unref(pixbuf);
9857 else if (pDevice)
9859 cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
9861 Size aSize(pDevice->GetOutputSizePixel());
9862 cairo_surface_t* target = cairo_surface_create_similar(surface,
9863 cairo_surface_get_content(surface),
9864 aSize.Width(),
9865 aSize.Height());
9867 cairo_t* cr = cairo_create(target);
9868 cairo_set_source_surface(cr, surface, 0, 0);
9869 cairo_paint(cr);
9870 cairo_destroy(cr);
9872 m_Setter(m_pTreeModel, &iter, m_nImageCol, target, -1);
9873 cairo_surface_destroy(target);
9877 bool separator_function(const GtkTreePath* path)
9879 return ::separator_function(path, m_aSeparatorRows);
9882 static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget)
9884 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
9885 GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter);
9886 bool bRet = pThis->separator_function(path);
9887 gtk_tree_path_free(path);
9888 return bRet;
9891 OUString get(const GtkTreeIter& iter, int col) const
9893 gchar* pStr;
9894 gtk_tree_model_get(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, &pStr, -1);
9895 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
9896 g_free(pStr);
9897 return sRet;
9900 OUString get(int pos, int col) const
9902 OUString sRet;
9903 GtkTreeIter iter;
9904 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
9905 sRet = get(iter, col);
9906 return sRet;
9909 gint get_int(const GtkTreeIter& iter, int col) const
9911 gint nRet(-1);
9912 gtk_tree_model_get(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, &nRet, -1);
9913 return nRet;
9916 gint get_int(int pos, int col) const
9918 gint nRet(-1);
9919 GtkTreeIter iter;
9920 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
9921 nRet = get_int(iter, col);
9922 gtk_tree_model_get(m_pTreeModel, &iter, col, &nRet, -1);
9923 return nRet;
9926 bool get_bool(const GtkTreeIter& iter, int col) const
9928 gboolean bRet(false);
9929 gtk_tree_model_get(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, &bRet, -1);
9930 return bRet;
9933 bool get_bool(int pos, int col) const
9935 bool bRet(false);
9936 GtkTreeIter iter;
9937 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
9938 bRet = get_bool(iter, col);
9939 return bRet;
9942 void set_toggle(const GtkTreeIter& iter, TriState eState, int col)
9944 if (col == -1)
9945 col = m_nExpanderToggleCol;
9946 else
9947 col = to_internal_model(col);
9949 if (eState == TRISTATE_INDET)
9951 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter),
9952 m_aToggleVisMap[col], promote_arg(true), // checkbuttons are invisible until toggled on or off
9953 m_aToggleTriStateMap[col], promote_arg(true), // tristate on
9954 -1);
9956 else
9958 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter),
9959 m_aToggleVisMap[col], promote_arg(true), // checkbuttons are invisible until toggled on or off
9960 m_aToggleTriStateMap[col], promote_arg(false), // tristate off
9961 col, promote_arg(eState == TRISTATE_TRUE), // set toggle state
9962 -1);
9966 void set(const GtkTreeIter& iter, int col, const OUString& rText)
9968 OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
9969 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, aStr.getStr(), -1);
9972 void set(int pos, int col, const OUString& rText)
9974 GtkTreeIter iter;
9975 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
9976 set(iter, col, rText);
9979 void set(const GtkTreeIter& iter, int col, bool bOn)
9981 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, promote_arg(bOn), -1);
9984 void set(int pos, int col, bool bOn)
9986 GtkTreeIter iter;
9987 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
9988 set(iter, col, bOn);
9991 void set(const GtkTreeIter& iter, int col, gint bInt)
9993 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, bInt, -1);
9996 void set(int pos, int col, gint bInt)
9998 GtkTreeIter iter;
9999 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
10000 set(iter, col, bInt);
10003 void set(const GtkTreeIter& iter, int col, double fValue)
10005 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, fValue, -1);
10008 void set(int pos, int col, double fValue)
10010 GtkTreeIter iter;
10011 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
10012 set(iter, col, fValue);
10015 static gboolean signalTestExpandRow(GtkTreeView*, GtkTreeIter* iter, GtkTreePath*, gpointer widget)
10017 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
10018 return !pThis->signal_test_expand_row(*iter);
10021 static gboolean signalTestCollapseRow(GtkTreeView*, GtkTreeIter* iter, GtkTreePath*, gpointer widget)
10023 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
10024 return !pThis->signal_test_collapse_row(*iter);
10027 bool child_is_placeholder(GtkInstanceTreeIter& rGtkIter) const
10029 GtkTreePath* pPath = gtk_tree_model_get_path(m_pTreeModel, &rGtkIter.iter);
10030 bool bExpanding = m_aExpandingPlaceHolderParents.count(pPath);
10031 gtk_tree_path_free(pPath);
10032 if (bExpanding)
10033 return true;
10035 bool bPlaceHolder = false;
10036 GtkTreeIter tmp;
10037 if (gtk_tree_model_iter_children(m_pTreeModel, &tmp, &rGtkIter.iter))
10039 rGtkIter.iter = tmp;
10040 if (get_text(rGtkIter, -1) == "<dummy>")
10042 bPlaceHolder = true;
10045 return bPlaceHolder;
10048 bool signal_test_expand_row(GtkTreeIter& iter)
10050 disable_notify_events();
10052 // if there's a preexisting placeholder child, required to make this
10053 // potentially expandable in the first place, now we remove it
10054 GtkInstanceTreeIter aIter(iter);
10055 GtkTreePath* pPlaceHolderPath = nullptr;
10056 bool bPlaceHolder = child_is_placeholder(aIter);
10057 if (bPlaceHolder)
10059 m_Remove(m_pTreeModel, &aIter.iter);
10061 pPlaceHolderPath = gtk_tree_model_get_path(m_pTreeModel, &iter);
10062 m_aExpandingPlaceHolderParents.insert(pPlaceHolderPath);
10065 aIter.iter = iter;
10066 bool bRet = signal_expanding(aIter);
10068 if (bPlaceHolder)
10070 //expand disallowed, restore placeholder
10071 if (!bRet)
10073 GtkTreeIter subiter;
10074 OUString sDummy("<dummy>");
10075 insert_row(subiter, &iter, -1, nullptr, &sDummy, nullptr, nullptr);
10077 m_aExpandingPlaceHolderParents.erase(pPlaceHolderPath);
10078 gtk_tree_path_free(pPlaceHolderPath);
10081 enable_notify_events();
10082 return bRet;
10085 bool signal_test_collapse_row(const GtkTreeIter& iter)
10087 disable_notify_events();
10089 GtkInstanceTreeIter aIter(iter);
10090 bool bRet = signal_collapsing(aIter);
10092 enable_notify_events();
10093 return bRet;
10096 static void signalCellToggled(GtkCellRendererToggle* pCell, const gchar *path, gpointer widget)
10098 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
10099 void* pData = g_object_get_data(G_OBJECT(pCell), "g-lo-CellIndex");
10100 pThis->signal_cell_toggled(path, reinterpret_cast<sal_IntPtr>(pData));
10103 void signal_cell_toggled(const gchar *path, int nCol)
10105 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
10107 // additionally set the cursor into the row the toggled element is in
10108 gtk_tree_view_set_cursor(m_pTreeView, tree_path, nullptr, false);
10110 GtkTreeIter iter;
10111 gtk_tree_model_get_iter(m_pTreeModel, &iter, tree_path);
10113 gboolean bRet(false);
10114 gtk_tree_model_get(m_pTreeModel, &iter, nCol, &bRet, -1);
10115 bRet = !bRet;
10116 m_Setter(m_pTreeModel, &iter, nCol, bRet, -1);
10118 set(iter, m_aToggleTriStateMap[nCol], false);
10120 signal_toggled(iter_col(GtkInstanceTreeIter(iter), to_external_model(nCol)));
10122 gtk_tree_path_free(tree_path);
10125 DECL_LINK(async_stop_cell_editing, void*, void);
10127 static void signalCellEditingStarted(GtkCellRenderer*, GtkCellEditable*, const gchar *path, gpointer widget)
10129 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
10130 if (!pThis->signal_cell_editing_started(path))
10131 Application::PostUserEvent(LINK(pThis, GtkInstanceTreeView, async_stop_cell_editing));
10134 bool signal_cell_editing_started(const gchar *path)
10136 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
10138 GtkInstanceTreeIter aGtkIter(nullptr);
10139 gtk_tree_model_get_iter(m_pTreeModel, &aGtkIter.iter, tree_path);
10140 gtk_tree_path_free(tree_path);
10142 return signal_editing_started(aGtkIter);
10145 static void signalCellEdited(GtkCellRendererText* pCell, const gchar *path, const gchar *pNewText, gpointer widget)
10147 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
10148 pThis->signal_cell_edited(pCell, path, pNewText);
10151 static void restoreNonEditable(GObject* pCell)
10153 if (g_object_get_data(pCell, "g-lo-RestoreNonEditable"))
10155 g_object_set(pCell, "editable", false, "editable-set", false, nullptr);
10156 g_object_set_data(pCell, "g-lo-RestoreNonEditable", reinterpret_cast<gpointer>(false));
10160 void signal_cell_edited(GtkCellRendererText* pCell, const gchar *path, const gchar* pNewText)
10162 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
10164 GtkInstanceTreeIter aGtkIter(nullptr);
10165 gtk_tree_model_get_iter(m_pTreeModel, &aGtkIter.iter, tree_path);
10166 gtk_tree_path_free(tree_path);
10168 OUString sText(pNewText, pNewText ? strlen(pNewText) : 0, RTL_TEXTENCODING_UTF8);
10169 if (signal_editing_done(iter_string(aGtkIter, sText)))
10171 void* pData = g_object_get_data(G_OBJECT(pCell), "g-lo-CellIndex");
10172 set(aGtkIter.iter, reinterpret_cast<sal_IntPtr>(pData), sText);
10175 restoreNonEditable(G_OBJECT(pCell));
10178 static void signalCellEditingCanceled(GtkCellRenderer* pCell, gpointer /*widget*/)
10180 restoreNonEditable(G_OBJECT(pCell));
10183 void signal_column_clicked(GtkTreeViewColumn* pClickedColumn)
10185 int nIndex(0);
10186 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
10188 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
10189 if (pColumn == pClickedColumn)
10191 TreeView::signal_column_clicked(nIndex);
10192 break;
10194 ++nIndex;
10198 static void signalColumnClicked(GtkTreeViewColumn* pColumn, gpointer widget)
10200 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
10201 pThis->signal_column_clicked(pColumn);
10204 static void signalVAdjustmentChanged(GtkAdjustment*, gpointer widget)
10206 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
10207 pThis->signal_visible_range_changed();
10210 // The outside concept of a column maps to a gtk CellRenderer, rather than
10211 // a TreeViewColumn. If the first TreeViewColumn has a leading Toggle Renderer
10212 // and/or a leading Image Renderer, those are considered special expander
10213 // columns and precede index 0 and can be accessed via outside index -1
10214 int to_external_model(int modelcol) const
10216 if (m_nExpanderToggleCol != -1)
10217 --modelcol;
10218 if (m_nExpanderImageCol != -1)
10219 --modelcol;
10220 return modelcol;
10223 int to_internal_model(int modelcol) const
10225 if (m_nExpanderToggleCol != -1)
10226 ++modelcol;
10227 if (m_nExpanderImageCol != -1)
10228 ++modelcol;
10229 return modelcol;
10232 void set_column_editable(int nCol, bool bEditable)
10234 nCol = to_internal_model(nCol);
10236 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
10238 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
10239 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
10240 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
10242 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
10243 void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
10244 if (reinterpret_cast<sal_IntPtr>(pData) == nCol)
10246 g_object_set(G_OBJECT(pCellRenderer), "editable", bEditable, "editable-set", true, nullptr);
10247 break;
10250 g_list_free(pRenderers);
10254 static void signalRowDeleted(GtkTreeModel*, GtkTreePath*, gpointer widget)
10256 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
10257 pThis->signal_model_changed();
10260 static void signalRowInserted(GtkTreeModel*, GtkTreePath*, GtkTreeIter*, gpointer widget)
10262 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
10263 pThis->signal_model_changed();
10266 static gint sortFunc(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b, gpointer widget)
10268 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
10269 return pThis->sort_func(pModel, a, b);
10272 gint sort_func(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b)
10274 if (m_aCustomSort)
10275 return m_aCustomSort(GtkInstanceTreeIter(*a), GtkInstanceTreeIter(*b));
10276 return default_sort_func(pModel, a, b, m_xSorter.get());
10279 bool signal_key_press(GdkEventKey* pEvent)
10281 if (pEvent->keyval != GDK_KEY_Left && pEvent->keyval != GDK_KEY_Right)
10282 return false;
10284 GtkInstanceTreeIter aIter(nullptr);
10285 if (!get_cursor(&aIter))
10286 return false;
10288 bool bHasChild = gtk_tree_model_iter_has_child(m_pTreeModel, &aIter.iter);
10290 if (pEvent->keyval == GDK_KEY_Right)
10292 if (bHasChild && !get_row_expanded(aIter))
10294 expand_row(aIter);
10295 return true;
10297 return false;
10300 if (bHasChild && get_row_expanded(aIter))
10302 collapse_row(aIter);
10303 return true;
10306 if (iter_parent(aIter))
10308 unselect_all();
10309 set_cursor(aIter);
10310 select(aIter);
10311 return true;
10314 return false;
10317 static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
10319 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
10320 return pThis->signal_key_press(pEvent);
10323 static gboolean signalQueryTooltip(GtkWidget* /*pGtkWidget*/, gint x, gint y,
10324 gboolean keyboard_tip, GtkTooltip *tooltip,
10325 gpointer widget)
10327 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
10328 GtkTreeIter iter;
10329 GtkTreeView *pTreeView = pThis->m_pTreeView;
10330 GtkTreeModel *pModel = gtk_tree_view_get_model(pTreeView);
10331 GtkTreePath *pPath = nullptr;
10332 if (!gtk_tree_view_get_tooltip_context(pTreeView, &x, &y, keyboard_tip, &pModel, &pPath, &iter))
10333 return false;
10334 OUString aTooltip = pThis->signal_query_tooltip(GtkInstanceTreeIter(iter));
10335 if (aTooltip.isEmpty())
10336 return false;
10337 gtk_tooltip_set_text(tooltip, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
10338 gtk_tree_view_set_tooltip_row(pTreeView, tooltip, pPath);
10339 gtk_tree_path_free(pPath);
10340 return true;
10343 void last_child(GtkTreeModel* pModel, GtkTreeIter* result, GtkTreeIter* pParent, int nChildren) const
10345 gtk_tree_model_iter_nth_child(pModel, result, pParent, nChildren - 1);
10346 nChildren = gtk_tree_model_iter_n_children(pModel, result);
10347 if (nChildren)
10349 GtkTreeIter newparent(*result);
10350 last_child(pModel, result, &newparent, nChildren);
10354 GtkTreePath* get_path_of_last_entry(GtkTreeModel *pModel)
10356 GtkTreePath *lastpath;
10357 // find the last entry in the model for comparison
10358 int nChildren = gtk_tree_model_iter_n_children(pModel, nullptr);
10359 if (!nChildren)
10360 lastpath = gtk_tree_path_new_from_indices(0, -1);
10361 else
10363 GtkTreeIter iter;
10364 last_child(pModel, &iter, nullptr, nChildren);
10365 lastpath = gtk_tree_model_get_path(pModel, &iter);
10367 return lastpath;
10370 void set_font_color(const GtkTreeIter& iter, const Color& rColor)
10372 if (rColor == COL_AUTO)
10373 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), m_nIdCol + 1, nullptr, -1);
10374 else
10376 GdkRGBA aColor{rColor.GetRed()/255.0, rColor.GetGreen()/255.0, rColor.GetBlue()/255.0, 0};
10377 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), m_nIdCol + 1, &aColor, -1);
10381 int get_expander_size() const
10383 gint nExpanderSize;
10384 gint nHorizontalSeparator;
10386 gtk_widget_style_get(GTK_WIDGET(m_pTreeView),
10387 "expander-size", &nExpanderSize,
10388 "horizontal-separator", &nHorizontalSeparator,
10389 nullptr);
10391 return nExpanderSize + (nHorizontalSeparator/ 2);
10394 void real_vadjustment_set_value(int value)
10396 disable_notify_events();
10397 gtk_adjustment_set_value(m_pVAdjustment, value);
10398 enable_notify_events();
10401 static gboolean setAdjustmentCallback(GtkWidget*, GdkFrameClock*, gpointer widget)
10403 GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
10404 if (pThis->m_nPendingVAdjustment != -1)
10406 pThis->real_vadjustment_set_value(pThis->m_nPendingVAdjustment);
10407 pThis->m_nPendingVAdjustment = -1;
10409 return false;
10412 bool iter_next(weld::TreeIter& rIter, bool bOnlyExpanded) const
10414 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
10415 GtkTreeIter tmp;
10416 GtkTreeIter iter = rGtkIter.iter;
10418 bool ret = gtk_tree_model_iter_children(m_pTreeModel, &tmp, &iter);
10419 if (ret && bOnlyExpanded && !get_row_expanded(rGtkIter))
10420 ret = false;
10421 rGtkIter.iter = tmp;
10422 if (ret)
10424 //on-demand dummy entry doesn't count
10425 if (get_text(rGtkIter, -1) == "<dummy>")
10426 return iter_next(rGtkIter, bOnlyExpanded);
10427 return true;
10430 tmp = iter;
10431 if (gtk_tree_model_iter_next(m_pTreeModel, &tmp))
10433 rGtkIter.iter = tmp;
10434 //on-demand dummy entry doesn't count
10435 if (get_text(rGtkIter, -1) == "<dummy>")
10436 return iter_next(rGtkIter, bOnlyExpanded);
10437 return true;
10439 // Move up level(s) until we find the level where the next node exists.
10440 while (gtk_tree_model_iter_parent(m_pTreeModel, &tmp, &iter))
10442 iter = tmp;
10443 if (gtk_tree_model_iter_next(m_pTreeModel, &tmp))
10445 rGtkIter.iter = tmp;
10446 //on-demand dummy entry doesn't count
10447 if (get_text(rGtkIter, -1) == "<dummy>")
10448 return iter_next(rGtkIter, bOnlyExpanded);
10449 return true;
10452 return false;
10455 public:
10456 GtkInstanceTreeView(GtkTreeView* pTreeView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
10457 : GtkInstanceContainer(GTK_CONTAINER(pTreeView), pBuilder, bTakeOwnership)
10458 , m_pTreeView(pTreeView)
10459 , m_pTreeModel(gtk_tree_view_get_model(m_pTreeView))
10460 , m_bWorkAroundBadDragRegion(false)
10461 , m_bInDrag(false)
10462 , m_nTextCol(-1)
10463 , m_nTextView(-1)
10464 , m_nImageCol(-1)
10465 , m_nExpanderToggleCol(-1)
10466 , m_nExpanderImageCol(-1)
10467 , m_nPendingVAdjustment(-1)
10468 , m_nChangedSignalId(g_signal_connect(gtk_tree_view_get_selection(pTreeView), "changed",
10469 G_CALLBACK(signalChanged), this))
10470 , m_nRowActivatedSignalId(g_signal_connect(pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
10471 , m_nTestExpandRowSignalId(g_signal_connect(pTreeView, "test-expand-row", G_CALLBACK(signalTestExpandRow), this))
10472 , m_nTestCollapseRowSignalId(g_signal_connect(pTreeView, "test-collapse-row", G_CALLBACK(signalTestCollapseRow), this))
10473 , m_nVAdjustmentChangedSignalId(0)
10474 , m_nPopupMenuSignalId(g_signal_connect(pTreeView, "popup-menu", G_CALLBACK(signalPopupMenu), this))
10475 , m_nKeyPressSignalId(g_signal_connect(pTreeView, "key-press-event", G_CALLBACK(signalKeyPress), this))
10476 , m_nQueryTooltipSignalId(0)
10477 , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTreeView)))
10478 , m_pChangeEvent(nullptr)
10480 if (GTK_IS_TREE_STORE(m_pTreeModel))
10482 m_Setter = tree_store_set;
10483 m_InsertWithValues = tree_store_insert_with_values;
10484 m_Insert = tree_store_insert;
10485 m_Prepend = tree_store_prepend;
10486 m_Remove = tree_store_remove;
10487 m_Swap = tree_store_swap;
10488 m_SetValue = tree_store_set_value;
10489 m_Clear = tree_store_clear;
10491 else
10494 tdf#136559 see: https://gitlab.gnome.org/GNOME/gtk/-/issues/2693
10495 If we only need a list and not a tree we can get a performance boost from using a ListStore
10497 assert(!gtk_tree_view_get_show_expanders(m_pTreeView) && "a liststore can only be used if no tree structure is needed");
10498 m_Setter = list_store_set;
10499 m_InsertWithValues = list_store_insert_with_values;
10500 m_Insert = list_store_insert;
10501 m_Prepend = list_store_prepend;
10502 m_Remove = list_store_remove;
10503 m_Swap = list_store_swap;
10504 m_SetValue = list_store_set_value;
10505 m_Clear = list_store_clear;
10508 /* The outside concept of a column maps to a gtk CellRenderer, rather than
10509 a TreeViewColumn. If the first TreeViewColumn has a leading Toggle Renderer
10510 and/or a leading Image Renderer, those are considered special expander
10511 columns and precede index 0 and can be accessed via outside index -1
10513 m_pColumns = gtk_tree_view_get_columns(m_pTreeView);
10514 int nIndex(0);
10515 int nViewColumn(0);
10516 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
10518 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
10519 m_aColumnSignalIds.push_back(g_signal_connect(pColumn, "clicked", G_CALLBACK(signalColumnClicked), this));
10520 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
10521 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
10523 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
10524 if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
10526 if (m_nTextCol == -1)
10528 m_nTextCol = nIndex;
10529 m_nTextView = nViewColumn;
10531 m_aWeightMap[nIndex] = -1;
10532 m_aSensitiveMap[nIndex] = -1;
10533 m_aIndentMap[nIndex] = -1;
10534 m_aAlignMap[nIndex] = -1;
10535 g_signal_connect(G_OBJECT(pCellRenderer), "editing-started", G_CALLBACK(signalCellEditingStarted), this);
10536 g_signal_connect(G_OBJECT(pCellRenderer), "editing-canceled", G_CALLBACK(signalCellEditingCanceled), this);
10537 g_signal_connect(G_OBJECT(pCellRenderer), "edited", G_CALLBACK(signalCellEdited), this);
10539 else if (GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
10541 const bool bExpander = nIndex == 0 || (nIndex == 1 && m_nExpanderImageCol == 0);
10542 if (bExpander)
10543 m_nExpanderToggleCol = nIndex;
10544 g_signal_connect(G_OBJECT(pCellRenderer), "toggled", G_CALLBACK(signalCellToggled), this);
10545 m_aToggleVisMap[nIndex] = -1;
10546 m_aToggleTriStateMap[nIndex] = -1;
10548 else if (GTK_IS_CELL_RENDERER_PIXBUF(pCellRenderer))
10550 const bool bExpander = g_list_next(pRenderer) != nullptr;
10551 if (bExpander && m_nExpanderImageCol == -1)
10552 m_nExpanderImageCol = nIndex;
10553 else if (m_nImageCol == -1)
10554 m_nImageCol = nIndex;
10556 g_object_set_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex", reinterpret_cast<gpointer>(nIndex));
10557 ++nIndex;
10559 g_list_free(pRenderers);
10560 ++nViewColumn;
10563 m_nIdCol = nIndex++;
10565 for (auto& a : m_aToggleVisMap)
10566 a.second = nIndex++;
10567 for (auto& a : m_aToggleTriStateMap)
10568 a.second = nIndex++;
10569 for (auto& a : m_aWeightMap)
10570 a.second = nIndex++;
10571 for (auto& a : m_aSensitiveMap)
10572 a.second = nIndex++;
10573 for (auto& a : m_aIndentMap)
10574 a.second = nIndex++;
10575 for (auto& a : m_aAlignMap)
10576 a.second = nIndex++;
10578 ensure_drag_begin_end();
10580 m_nRowDeletedSignalId = g_signal_connect(m_pTreeModel, "row-deleted", G_CALLBACK(signalRowDeleted), this);
10581 m_nRowInsertedSignalId = g_signal_connect(m_pTreeModel, "row-inserted", G_CALLBACK(signalRowInserted), this);
10584 virtual void connect_query_tooltip(const Link<const weld::TreeIter&, OUString>& rLink) override
10586 weld::TreeView::connect_query_tooltip(rLink);
10587 m_nQueryTooltipSignalId = g_signal_connect(m_pTreeView, "query-tooltip", G_CALLBACK(signalQueryTooltip), this);
10590 virtual void columns_autosize() override
10592 gtk_tree_view_columns_autosize(m_pTreeView);
10595 virtual void set_column_fixed_widths(const std::vector<int>& rWidths) override
10597 GList* pEntry = g_list_first(m_pColumns);
10598 for (auto nWidth : rWidths)
10600 assert(pEntry && "wrong count");
10601 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
10602 gtk_tree_view_column_set_fixed_width(pColumn, nWidth);
10603 pEntry = g_list_next(pEntry);
10607 virtual void set_column_editables(const std::vector<bool>& rEditables) override
10609 size_t nTabCount = rEditables.size();
10610 for (size_t i = 0 ; i < nTabCount; ++i)
10611 set_column_editable(i, rEditables[i]);
10614 virtual void set_centered_column(int nCol) override
10616 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
10618 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
10619 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
10620 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
10622 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
10623 void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
10624 if (reinterpret_cast<sal_IntPtr>(pData) == nCol)
10626 g_object_set(G_OBJECT(pCellRenderer), "xalign", 0.5, nullptr);
10627 break;
10630 g_list_free(pRenderers);
10634 virtual int get_column_width(int nColumn) const override
10636 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
10637 assert(pColumn && "wrong count");
10638 int nWidth = gtk_tree_view_column_get_width(pColumn);
10639 // https://github.com/exaile/exaile/issues/580
10640 // after setting fixed_width on a column and requesting width before
10641 // gtk has a chance to do its layout of the column means that the width
10642 // request hasn't come into effect
10643 if (!nWidth)
10644 nWidth = gtk_tree_view_column_get_fixed_width(pColumn);
10645 return nWidth;
10648 virtual OUString get_column_title(int nColumn) const override
10650 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
10651 assert(pColumn && "wrong count");
10652 const gchar* pTitle = gtk_tree_view_column_get_title(pColumn);
10653 OUString sRet(pTitle, pTitle ? strlen(pTitle) : 0, RTL_TEXTENCODING_UTF8);
10654 return sRet;
10657 virtual void set_column_title(int nColumn, const OUString& rTitle) override
10659 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
10660 assert(pColumn && "wrong count");
10661 gtk_tree_view_column_set_title(pColumn, OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
10664 virtual void set_column_custom_renderer(int nColumn, bool bEnable) override
10666 assert(n_children() == 0 && "tree must be empty");
10667 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
10668 assert(pColumn && "wrong count");
10670 GtkCellRenderer* pExpander = nullptr;
10671 GtkCellRenderer* pToggle = nullptr;
10673 // migrate existing editable setting to the new renderer
10674 gboolean is_editable(false);
10675 void* pEditCellData(nullptr);
10676 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
10677 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
10679 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
10681 void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
10682 auto nCellIndex = reinterpret_cast<sal_IntPtr>(pData);
10684 if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
10686 g_object_get(pCellRenderer, "editable", &is_editable, nullptr);
10687 pEditCellData = pData;
10688 break;
10690 else if (GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
10692 if (nCellIndex == m_nExpanderToggleCol)
10694 pToggle = pCellRenderer;
10695 g_object_ref(pToggle);
10698 else if (GTK_IS_CELL_RENDERER_PIXBUF(pCellRenderer))
10700 if (nCellIndex == m_nExpanderImageCol)
10702 pExpander = pCellRenderer;
10703 g_object_ref(pExpander);
10708 g_list_free(pRenderers);
10710 GtkCellRenderer* pRenderer;
10712 gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn));
10713 if (pExpander)
10715 gtk_tree_view_column_pack_start(pColumn, pExpander, false);
10716 gtk_tree_view_column_add_attribute(pColumn, pExpander, "pixbuf", m_nExpanderImageCol);
10717 g_object_unref(pExpander);
10719 if (pToggle)
10721 gtk_tree_view_column_pack_start(pColumn, pToggle, false);
10722 gtk_tree_view_column_add_attribute(pColumn, pToggle, "active", m_nExpanderToggleCol);
10723 gtk_tree_view_column_add_attribute(pColumn, pToggle, "active", m_nExpanderToggleCol);
10724 gtk_tree_view_column_add_attribute(pColumn, pToggle, "visible", m_aToggleTriStateMap[m_nExpanderToggleCol]);
10725 g_object_unref(pToggle);
10728 if (bEnable)
10730 pRenderer = custom_cell_renderer_surface_new();
10731 GValue value = G_VALUE_INIT;
10732 g_value_init(&value, G_TYPE_POINTER);
10733 g_value_set_pointer(&value, static_cast<gpointer>(this));
10734 g_object_set_property(G_OBJECT(pRenderer), "instance", &value);
10735 gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
10736 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
10737 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "id", m_nIdCol);
10739 else
10741 pRenderer = gtk_cell_renderer_text_new();
10742 gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
10743 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
10746 if (is_editable)
10748 g_object_set(pRenderer, "editable", true, "editable-set", true, nullptr);
10749 g_object_set_data(G_OBJECT(pRenderer), "g-lo-CellIndex", pEditCellData);
10750 g_signal_connect(pRenderer, "editing-started", G_CALLBACK(signalCellEditingStarted), this);
10751 g_signal_connect(pRenderer, "editing-canceled", G_CALLBACK(signalCellEditingCanceled), this);
10752 g_signal_connect(pRenderer, "edited", G_CALLBACK(signalCellEdited), this);
10756 virtual void queue_draw() override
10758 gtk_widget_queue_draw(GTK_WIDGET(m_pTreeView));
10761 virtual void insert(const weld::TreeIter* pParent, int pos, const OUString* pText, const OUString* pId, const OUString* pIconName,
10762 VirtualDevice* pImageSurface,
10763 bool bChildrenOnDemand, weld::TreeIter* pRet) override
10765 disable_notify_events();
10766 GtkTreeIter iter;
10767 const GtkInstanceTreeIter* pGtkIter = static_cast<const GtkInstanceTreeIter*>(pParent);
10768 insert_row(iter, pGtkIter ? &pGtkIter->iter : nullptr, pos, pId, pText, pIconName, pImageSurface);
10769 if (bChildrenOnDemand)
10771 GtkTreeIter subiter;
10772 OUString sDummy("<dummy>");
10773 insert_row(subiter, &iter, -1, nullptr, &sDummy, nullptr, nullptr);
10775 if (pRet)
10777 GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
10778 pGtkRetIter->iter = iter;
10780 enable_notify_events();
10783 virtual void insert_separator(int pos, const OUString& rId) override
10785 disable_notify_events();
10786 GtkTreeIter iter;
10787 if (!gtk_tree_view_get_row_separator_func(m_pTreeView))
10788 gtk_tree_view_set_row_separator_func(m_pTreeView, separatorFunction, this, nullptr);
10789 insert_row(iter, nullptr, pos, &rId, nullptr, nullptr, nullptr);
10790 GtkTreePath* pPath = gtk_tree_model_get_path(m_pTreeModel, &iter);
10791 m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(m_pTreeModel, pPath));
10792 gtk_tree_path_free(pPath);
10793 enable_notify_events();
10796 virtual void set_font_color(int pos, const Color& rColor) override
10798 GtkTreeIter iter;
10799 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
10800 set_font_color(iter, rColor);
10803 virtual void set_font_color(const weld::TreeIter& rIter, const Color& rColor) override
10805 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
10806 set_font_color(rGtkIter.iter, rColor);
10809 virtual void remove(int pos) override
10811 disable_notify_events();
10812 GtkTreeIter iter;
10813 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
10814 m_Remove(m_pTreeModel, &iter);
10815 enable_notify_events();
10818 virtual int find_text(const OUString& rText) const override
10820 Search aSearch(rText, m_nTextCol);
10821 gtk_tree_model_foreach(m_pTreeModel, foreach_find, &aSearch);
10822 return aSearch.index;
10825 virtual int find_id(const OUString& rId) const override
10827 Search aSearch(rId, m_nIdCol);
10828 gtk_tree_model_foreach(m_pTreeModel, foreach_find, &aSearch);
10829 return aSearch.index;
10832 virtual void bulk_insert_for_each(int nSourceCount, const std::function<void(weld::TreeIter&, int nSourceIndex)>& func,
10833 const std::vector<int>* pFixedWidths) override
10835 freeze();
10836 clear();
10837 GtkInstanceTreeIter aGtkIter(nullptr);
10839 if (pFixedWidths)
10840 set_column_fixed_widths(*pFixedWidths);
10842 while (nSourceCount)
10844 // tdf#125241 inserting backwards is massively faster
10845 m_Prepend(m_pTreeModel, &aGtkIter.iter, nullptr);
10846 func(aGtkIter, --nSourceCount);
10849 thaw();
10852 virtual void swap(int pos1, int pos2) override
10854 disable_notify_events();
10856 GtkTreeIter iter1;
10857 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter1, nullptr, pos1);
10859 GtkTreeIter iter2;
10860 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter2, nullptr, pos2);
10862 m_Swap(m_pTreeModel, &iter1, &iter2);
10864 enable_notify_events();
10867 virtual void clear() override
10869 disable_notify_events();
10870 gtk_tree_view_set_row_separator_func(m_pTreeView, nullptr, nullptr, nullptr);
10871 m_aSeparatorRows.clear();
10872 m_Clear(m_pTreeModel);
10873 enable_notify_events();
10876 virtual void make_sorted() override
10878 // thaw wants to restore sort state of freeze
10879 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
10880 m_xSorter.reset(new comphelper::string::NaturalStringSorter(
10881 ::comphelper::getProcessComponentContext(),
10882 Application::GetSettings().GetUILanguageTag().getLocale()));
10883 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
10884 gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, sortFunc, this, nullptr);
10885 gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
10888 virtual void make_unsorted() override
10890 m_xSorter.reset();
10891 int nSortColumn;
10892 GtkSortType eSortType;
10893 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
10894 gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
10895 gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, eSortType);
10898 virtual void set_sort_order(bool bAscending) override
10900 GtkSortType eSortType = bAscending ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
10902 gint sort_column_id(0);
10903 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
10904 gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr);
10905 gtk_tree_sortable_set_sort_column_id(pSortable, sort_column_id, eSortType);
10908 virtual bool get_sort_order() const override
10910 int nSortColumn;
10911 GtkSortType eSortType;
10913 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
10914 gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
10915 return nSortColumn != GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID && eSortType == GTK_SORT_ASCENDING;
10918 virtual void set_sort_indicator(TriState eState, int col) override
10920 assert(col >= 0 && "cannot sort on expander column");
10922 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
10923 assert(pColumn && "wrong count");
10924 if (eState == TRISTATE_INDET)
10925 gtk_tree_view_column_set_sort_indicator(pColumn, false);
10926 else
10928 gtk_tree_view_column_set_sort_indicator(pColumn, true);
10929 GtkSortType eSortType = eState == TRISTATE_TRUE ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
10930 gtk_tree_view_column_set_sort_order(pColumn, eSortType);
10934 virtual TriState get_sort_indicator(int col) const override
10936 assert(col >= 0 && "cannot sort on expander column");
10938 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
10939 if (!gtk_tree_view_column_get_sort_indicator(pColumn))
10940 return TRISTATE_INDET;
10941 return gtk_tree_view_column_get_sort_order(pColumn) == GTK_SORT_ASCENDING ? TRISTATE_TRUE : TRISTATE_FALSE;
10944 virtual int get_sort_column() const override
10946 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
10947 gint sort_column_id(0);
10948 if (!gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr))
10949 return -1;
10950 return to_external_model(sort_column_id);
10953 virtual void set_sort_column(int nColumn) override
10955 if (nColumn == -1)
10957 make_unsorted();
10958 return;
10960 GtkSortType eSortType;
10961 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
10962 gtk_tree_sortable_get_sort_column_id(pSortable, nullptr, &eSortType);
10963 int nSortCol = to_internal_model(nColumn);
10964 gtk_tree_sortable_set_sort_func(pSortable, nSortCol, sortFunc, this, nullptr);
10965 gtk_tree_sortable_set_sort_column_id(pSortable, nSortCol, eSortType);
10968 virtual void set_sort_func(const std::function<int(const weld::TreeIter&, const weld::TreeIter&)>& func) override
10970 weld::TreeView::set_sort_func(func);
10971 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
10972 gtk_tree_sortable_sort_column_changed(pSortable);
10975 virtual int n_children() const override
10977 return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr);
10980 virtual int iter_n_children(const weld::TreeIter& rIter) const override
10982 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
10983 return gtk_tree_model_iter_n_children(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
10986 virtual void select(int pos) override
10988 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
10989 disable_notify_events();
10990 if (pos == -1 || (pos == 0 && n_children() == 0))
10992 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(m_pTreeView));
10994 else
10996 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
10997 gtk_tree_selection_select_path(gtk_tree_view_get_selection(m_pTreeView), path);
10998 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
10999 gtk_tree_path_free(path);
11001 enable_notify_events();
11004 virtual void set_cursor(int pos) override
11006 disable_notify_events();
11007 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
11008 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
11009 gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
11010 gtk_tree_path_free(path);
11011 enable_notify_events();
11014 virtual void scroll_to_row(int pos) override
11016 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
11017 disable_notify_events();
11018 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
11019 gtk_tree_view_expand_to_path(m_pTreeView, path);
11020 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
11021 gtk_tree_path_free(path);
11022 enable_notify_events();
11025 virtual bool is_selected(int pos) const override
11027 GtkTreeIter iter;
11028 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
11029 return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(m_pTreeView), &iter);
11032 virtual void unselect(int pos) override
11034 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
11035 disable_notify_events();
11036 if (pos == -1 || (pos == 0 && n_children() == 0))
11038 gtk_tree_selection_select_all(gtk_tree_view_get_selection(m_pTreeView));
11040 else
11042 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
11043 gtk_tree_selection_unselect_path(gtk_tree_view_get_selection(m_pTreeView), path);
11044 gtk_tree_path_free(path);
11046 enable_notify_events();
11049 virtual std::vector<int> get_selected_rows() const override
11051 std::vector<int> aRows;
11053 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), nullptr);
11054 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
11056 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
11058 gint depth;
11059 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
11060 int nRow = indices[depth-1];
11062 aRows.push_back(nRow);
11064 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
11066 return aRows;
11069 virtual void all_foreach(const std::function<bool(weld::TreeIter&)>& func) override
11071 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
11073 GtkInstanceTreeIter aGtkIter(nullptr);
11074 if (get_iter_first(aGtkIter))
11078 if (func(aGtkIter))
11079 break;
11080 } while (iter_next(aGtkIter));
11083 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
11086 virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
11088 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
11090 GtkInstanceTreeIter aGtkIter(nullptr);
11092 GtkTreeModel* pModel;
11093 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
11094 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
11096 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
11097 gtk_tree_model_get_iter(pModel, &aGtkIter.iter, path);
11098 if (func(aGtkIter))
11099 break;
11101 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
11103 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
11106 virtual void visible_foreach(const std::function<bool(weld::TreeIter&)>& func) override
11108 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
11110 GtkTreePath* start_path;
11111 GtkTreePath* end_path;
11113 if (!gtk_tree_view_get_visible_range(m_pTreeView, &start_path, &end_path))
11114 return;
11116 GtkInstanceTreeIter aGtkIter(nullptr);
11117 gtk_tree_model_get_iter(m_pTreeModel, &aGtkIter.iter, start_path);
11121 if (func(aGtkIter))
11122 break;
11123 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, &aGtkIter.iter);
11124 bool bContinue = gtk_tree_path_compare(path, end_path) != 0;
11125 gtk_tree_path_free(path);
11126 if (!bContinue)
11127 break;
11128 if (!iter_next(aGtkIter))
11129 break;
11130 } while(true);
11132 gtk_tree_path_free(start_path);
11133 gtk_tree_path_free(end_path);
11135 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
11138 virtual void connect_visible_range_changed(const Link<weld::TreeView&, void>& rLink) override
11140 weld::TreeView::connect_visible_range_changed(rLink);
11141 if (!m_nVAdjustmentChangedSignalId)
11143 GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
11144 m_nVAdjustmentChangedSignalId = g_signal_connect(pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustmentChanged), this);
11148 virtual bool is_selected(const weld::TreeIter& rIter) const override
11150 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11151 return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
11154 virtual OUString get_text(int pos, int col) const override
11156 if (col == -1)
11157 col = m_nTextCol;
11158 else
11159 col = to_internal_model(col);
11160 return get(pos, col);
11163 virtual void set_text(int pos, const OUString& rText, int col) override
11165 if (col == -1)
11166 col = m_nTextCol;
11167 else
11168 col = to_internal_model(col);
11169 set(pos, col, rText);
11172 virtual TriState get_toggle(int pos, int col) const override
11174 if (col == -1)
11175 col = m_nExpanderToggleCol;
11176 else
11177 col = to_internal_model(col);
11179 if (get_bool(pos, m_aToggleTriStateMap.find(col)->second))
11180 return TRISTATE_INDET;
11181 return get_bool(pos, col) ? TRISTATE_TRUE : TRISTATE_FALSE;
11184 virtual TriState get_toggle(const weld::TreeIter& rIter, int col) const override
11186 if (col == -1)
11187 col = m_nExpanderToggleCol;
11188 else
11189 col = to_internal_model(col);
11191 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11192 if (get_bool(rGtkIter.iter, m_aToggleTriStateMap.find(col)->second))
11193 return TRISTATE_INDET;
11194 return get_bool(rGtkIter.iter, col) ? TRISTATE_TRUE : TRISTATE_FALSE;
11197 virtual void set_toggle(const weld::TreeIter& rIter, TriState eState, int col) override
11199 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11200 set_toggle(rGtkIter.iter, eState, col);
11203 virtual void set_toggle(int pos, TriState eState, int col) override
11205 GtkTreeIter iter;
11206 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
11207 set_toggle(iter, eState, col);
11210 virtual void enable_toggle_buttons(weld::ColumnToggleType eType) override
11212 for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
11214 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
11215 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
11216 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
11218 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
11219 if (!GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
11220 continue;
11221 GtkCellRendererToggle* pToggle = GTK_CELL_RENDERER_TOGGLE(pCellRenderer);
11222 gtk_cell_renderer_toggle_set_radio(pToggle, eType == weld::ColumnToggleType::Radio);
11224 g_list_free(pRenderers);
11228 virtual void set_extra_row_indent(const weld::TreeIter& rIter, int nIndentLevel) override
11230 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11231 set(rGtkIter.iter, m_aIndentMap[m_nTextCol], nIndentLevel * get_expander_size());
11234 virtual void set_text_emphasis(const weld::TreeIter& rIter, bool bOn, int col) override
11236 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11237 col = to_internal_model(col);
11238 set(rGtkIter.iter, m_aWeightMap[col], bOn ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
11241 virtual void set_text_emphasis(int pos, bool bOn, int col) override
11243 col = to_internal_model(col);
11244 set(pos, m_aWeightMap[col], bOn ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
11247 virtual bool get_text_emphasis(const weld::TreeIter& rIter, int col) const override
11249 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11250 col = to_internal_model(col);
11251 return get_int(rGtkIter.iter, m_aWeightMap.find(col)->second) == PANGO_WEIGHT_BOLD;
11254 virtual bool get_text_emphasis(int pos, int col) const override
11256 col = to_internal_model(col);
11257 return get_int(pos, m_aWeightMap.find(col)->second) == PANGO_WEIGHT_BOLD;
11260 virtual void set_text_align(const weld::TreeIter& rIter, double fAlign, int col) override
11262 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11263 col = to_internal_model(col);
11264 set(rGtkIter.iter, m_aAlignMap[col], fAlign);
11267 virtual void set_text_align(int pos, double fAlign, int col) override
11269 col = to_internal_model(col);
11270 set(pos, m_aAlignMap[col], fAlign);
11273 using GtkInstanceWidget::set_sensitive;
11275 virtual void set_sensitive(int pos, bool bSensitive, int col) override
11277 if (col == -1)
11278 col = m_nTextCol;
11279 else
11280 col = to_internal_model(col);
11281 set(pos, m_aSensitiveMap[col], bSensitive);
11284 virtual void set_sensitive(const weld::TreeIter& rIter, bool bSensitive, int col) override
11286 if (col == -1)
11287 col = m_nTextCol;
11288 else
11289 col = to_internal_model(col);
11290 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11291 set(rGtkIter.iter, m_aSensitiveMap[col], bSensitive);
11294 void set_image(const GtkTreeIter& iter, int col, GdkPixbuf* pixbuf)
11296 if (col == -1)
11297 col = m_nExpanderImageCol;
11298 else
11299 col = to_internal_model(col);
11300 m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, pixbuf, -1);
11301 if (pixbuf)
11302 g_object_unref(pixbuf);
11305 void set_image(int pos, GdkPixbuf* pixbuf, int col)
11307 GtkTreeIter iter;
11308 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
11310 set_image(iter, col, pixbuf);
11314 virtual void set_image(int pos, const css::uno::Reference<css::graphic::XGraphic>& rImage, int col) override
11316 set_image(pos, getPixbuf(rImage), col);
11319 virtual void set_image(int pos, const OUString& rImage, int col) override
11321 set_image(pos, getPixbuf(rImage), col);
11324 virtual void set_image(int pos, VirtualDevice& rImage, int col) override
11326 set_image(pos, getPixbuf(rImage), col);
11329 virtual void set_image(const weld::TreeIter& rIter, const css::uno::Reference<css::graphic::XGraphic>& rImage, int col) override
11331 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11332 set_image(rGtkIter.iter, col, getPixbuf(rImage));
11335 virtual void set_image(const weld::TreeIter& rIter, const OUString& rImage, int col) override
11337 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11338 set_image(rGtkIter.iter, col, getPixbuf(rImage));
11341 virtual void set_image(const weld::TreeIter& rIter, VirtualDevice& rImage, int col) override
11343 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11344 set_image(rGtkIter.iter, col, getPixbuf(rImage));
11347 virtual OUString get_id(int pos) const override
11349 return get(pos, m_nIdCol);
11352 virtual void set_id(int pos, const OUString& rId) override
11354 return set(pos, m_nIdCol, rId);
11357 virtual int get_iter_index_in_parent(const weld::TreeIter& rIter) const override
11359 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11361 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
11363 gint depth;
11364 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
11365 int nRet = indices[depth-1];
11367 gtk_tree_path_free(path);
11369 return nRet;
11372 virtual int iter_compare(const weld::TreeIter& a, const weld::TreeIter& b) const override
11374 const GtkInstanceTreeIter& rGtkIterA = static_cast<const GtkInstanceTreeIter&>(a);
11375 const GtkInstanceTreeIter& rGtkIterB = static_cast<const GtkInstanceTreeIter&>(b);
11377 GtkTreePath* pathA = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIterA.iter));
11378 GtkTreePath* pathB = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIterB.iter));
11380 int nRet = gtk_tree_path_compare(pathA, pathB);
11382 gtk_tree_path_free(pathB);
11383 gtk_tree_path_free(pathA);
11385 return nRet;
11388 // by copy and delete of old copy
11389 void move_subtree(GtkTreeIter& rFromIter, GtkTreeIter* pGtkParentIter, int nIndexInNewParent)
11391 int nCols = gtk_tree_model_get_n_columns(m_pTreeModel);
11392 GValue value;
11394 GtkTreeIter toiter;
11395 m_Insert(m_pTreeModel, &toiter, pGtkParentIter, nIndexInNewParent);
11397 for (int i = 0; i < nCols; ++i)
11399 memset(&value, 0, sizeof(GValue));
11400 gtk_tree_model_get_value(m_pTreeModel, &rFromIter, i, &value);
11401 m_SetValue(m_pTreeModel, &toiter, i, &value);
11402 g_value_unset(&value);
11405 GtkTreeIter tmpfromiter;
11406 if (gtk_tree_model_iter_children(m_pTreeModel, &tmpfromiter, &rFromIter))
11408 int j = 0;
11411 move_subtree(tmpfromiter, &toiter, j++);
11412 } while (gtk_tree_model_iter_next(m_pTreeModel, &tmpfromiter));
11415 m_Remove(m_pTreeModel, &rFromIter);
11418 virtual void move_subtree(weld::TreeIter& rNode, const weld::TreeIter* pNewParent, int nIndexInNewParent) override
11420 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rNode);
11421 const GtkInstanceTreeIter* pGtkParentIter = static_cast<const GtkInstanceTreeIter*>(pNewParent);
11422 move_subtree(rGtkIter.iter, pGtkParentIter ? const_cast<GtkTreeIter*>(&pGtkParentIter->iter) : nullptr, nIndexInNewParent);
11425 virtual int get_selected_index() const override
11427 assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
11428 int nRet = -1;
11429 GtkTreeSelection *selection = gtk_tree_view_get_selection(m_pTreeView);
11430 if (gtk_tree_selection_get_mode(selection) != GTK_SELECTION_MULTIPLE)
11432 GtkTreeIter iter;
11433 GtkTreeModel* pModel;
11434 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(m_pTreeView), &pModel, &iter))
11436 GtkTreePath* path = gtk_tree_model_get_path(pModel, &iter);
11438 gint depth;
11439 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
11440 nRet = indices[depth-1];
11442 gtk_tree_path_free(path);
11445 else
11447 auto vec = get_selected_rows();
11448 return vec.empty() ? -1 : vec[0];
11450 return nRet;
11453 bool get_selected_iterator(GtkTreeIter* pIter) const
11455 assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
11456 bool bRet = false;
11457 GtkTreeSelection *selection = gtk_tree_view_get_selection(m_pTreeView);
11458 if (gtk_tree_selection_get_mode(selection) != GTK_SELECTION_MULTIPLE)
11459 bRet = gtk_tree_selection_get_selected(gtk_tree_view_get_selection(m_pTreeView), nullptr, pIter);
11460 else
11462 GtkTreeModel* pModel;
11463 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
11464 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
11466 if (pIter)
11468 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
11469 gtk_tree_model_get_iter(pModel, pIter, path);
11471 bRet = true;
11472 break;
11474 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
11476 return bRet;
11479 virtual OUString get_selected_text() const override
11481 assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
11482 GtkTreeIter iter;
11483 if (get_selected_iterator(&iter))
11484 return get(iter, m_nTextCol);
11485 return OUString();
11488 virtual OUString get_selected_id() const override
11490 assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
11491 GtkTreeIter iter;
11492 if (get_selected_iterator(&iter))
11493 return get(iter, m_nIdCol);
11494 return OUString();
11497 virtual std::unique_ptr<weld::TreeIter> make_iterator(const weld::TreeIter* pOrig) const override
11499 return std::unique_ptr<weld::TreeIter>(new GtkInstanceTreeIter(static_cast<const GtkInstanceTreeIter*>(pOrig)));
11502 virtual void copy_iterator(const weld::TreeIter& rSource, weld::TreeIter& rDest) const override
11504 const GtkInstanceTreeIter& rGtkSource(static_cast<const GtkInstanceTreeIter&>(rSource));
11505 GtkInstanceTreeIter& rGtkDest(static_cast<GtkInstanceTreeIter&>(rDest));
11506 rGtkDest.iter = rGtkSource.iter;
11509 virtual bool get_selected(weld::TreeIter* pIter) const override
11511 GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
11512 return get_selected_iterator(pGtkIter ? &pGtkIter->iter : nullptr);
11515 virtual bool get_cursor(weld::TreeIter* pIter) const override
11517 GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
11518 GtkTreePath* path;
11519 gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
11520 if (pGtkIter && path)
11522 gtk_tree_model_get_iter(m_pTreeModel, &pGtkIter->iter, path);
11524 if (!path)
11525 return false;
11526 gtk_tree_path_free(path);
11527 return true;
11530 virtual int get_cursor_index() const override
11532 int nRet = -1;
11534 GtkTreePath* path;
11535 gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
11536 if (path)
11538 gint depth;
11539 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
11540 nRet = indices[depth-1];
11541 gtk_tree_path_free(path);
11544 return nRet;
11547 virtual void set_cursor(const weld::TreeIter& rIter) override
11549 disable_notify_events();
11550 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11551 GtkTreeIter Iter;
11552 if (gtk_tree_model_iter_parent(m_pTreeModel, &Iter, const_cast<GtkTreeIter*>(&rGtkIter.iter)))
11554 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, &Iter);
11555 if (!gtk_tree_view_row_expanded(m_pTreeView, path))
11556 gtk_tree_view_expand_to_path(m_pTreeView, path);
11557 gtk_tree_path_free(path);
11559 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
11560 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
11561 gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
11562 gtk_tree_path_free(path);
11563 enable_notify_events();
11566 virtual bool get_iter_first(weld::TreeIter& rIter) const override
11568 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
11569 return gtk_tree_model_get_iter_first(m_pTreeModel, &rGtkIter.iter);
11572 virtual bool iter_next_sibling(weld::TreeIter& rIter) const override
11574 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
11575 return gtk_tree_model_iter_next(m_pTreeModel, &rGtkIter.iter);
11578 virtual bool iter_previous_sibling(weld::TreeIter& rIter) const override
11580 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
11581 return gtk_tree_model_iter_previous(m_pTreeModel, &rGtkIter.iter);
11584 virtual bool iter_next(weld::TreeIter& rIter) const override
11586 return iter_next(rIter, false);
11589 virtual bool iter_previous(weld::TreeIter& rIter) const override
11591 bool ret = false;
11592 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
11593 GtkTreeIter iter = rGtkIter.iter;
11594 GtkTreeIter tmp = iter;
11595 if (gtk_tree_model_iter_previous(m_pTreeModel, &tmp))
11597 // Move down level(s) until we find the level where the last node exists.
11598 int nChildren = gtk_tree_model_iter_n_children(m_pTreeModel, &tmp);
11599 if (!nChildren)
11600 rGtkIter.iter = tmp;
11601 else
11602 last_child(m_pTreeModel, &rGtkIter.iter, &tmp, nChildren);
11603 ret = true;
11605 else
11607 // Move up level
11608 if (gtk_tree_model_iter_parent(m_pTreeModel, &tmp, &iter))
11610 rGtkIter.iter = tmp;
11611 ret = true;
11615 if (ret)
11617 //on-demand dummy entry doesn't count
11618 if (get_text(rGtkIter, -1) == "<dummy>")
11619 return iter_previous(rGtkIter);
11620 return true;
11623 return false;
11626 virtual bool iter_children(weld::TreeIter& rIter) const override
11628 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
11629 GtkTreeIter tmp;
11630 bool ret = gtk_tree_model_iter_children(m_pTreeModel, &tmp, &rGtkIter.iter);
11631 rGtkIter.iter = tmp;
11632 if (ret)
11634 //on-demand dummy entry doesn't count
11635 return get_text(rGtkIter, -1) != "<dummy>";
11637 return ret;
11640 virtual bool iter_parent(weld::TreeIter& rIter) const override
11642 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
11643 GtkTreeIter tmp;
11644 auto ret = gtk_tree_model_iter_parent(m_pTreeModel, &tmp, &rGtkIter.iter);
11645 rGtkIter.iter = tmp;
11646 return ret;
11649 virtual void remove(const weld::TreeIter& rIter) override
11651 disable_notify_events();
11652 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11653 m_Remove(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
11654 enable_notify_events();
11657 virtual void remove_selection() override
11659 disable_notify_events();
11661 std::vector<GtkTreeIter> aIters;
11662 GtkTreeModel* pModel;
11663 GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
11664 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
11666 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
11667 aIters.emplace_back();
11668 gtk_tree_model_get_iter(pModel, &aIters.back(), path);
11670 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
11672 for (auto& iter : aIters)
11673 m_Remove(m_pTreeModel, &iter);
11675 enable_notify_events();
11678 virtual void select(const weld::TreeIter& rIter) override
11680 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
11681 disable_notify_events();
11682 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11683 gtk_tree_selection_select_iter(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
11684 enable_notify_events();
11687 virtual void scroll_to_row(const weld::TreeIter& rIter) override
11689 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
11690 disable_notify_events();
11691 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11692 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
11693 gtk_tree_view_expand_to_path(m_pTreeView, path);
11694 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
11695 gtk_tree_path_free(path);
11696 enable_notify_events();
11699 virtual void unselect(const weld::TreeIter& rIter) override
11701 assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
11702 disable_notify_events();
11703 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11704 gtk_tree_selection_unselect_iter(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
11705 enable_notify_events();
11708 virtual int get_iter_depth(const weld::TreeIter& rIter) const override
11710 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11711 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
11712 int ret = gtk_tree_path_get_depth(path) - 1;
11713 gtk_tree_path_free(path);
11714 return ret;
11717 virtual bool iter_has_child(const weld::TreeIter& rIter) const override
11719 weld::TreeIter& rNonConstIter = const_cast<weld::TreeIter&>(rIter);
11720 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rNonConstIter);
11721 GtkTreeIter restore(rGtkIter.iter);
11722 bool ret = iter_children(rNonConstIter);
11723 rGtkIter.iter = restore;
11724 return ret;
11727 virtual bool get_row_expanded(const weld::TreeIter& rIter) const override
11729 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11730 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
11731 bool ret = gtk_tree_view_row_expanded(m_pTreeView, path);
11732 gtk_tree_path_free(path);
11733 return ret;
11736 virtual bool get_children_on_demand(const weld::TreeIter& rIter) const override
11738 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11739 GtkInstanceTreeIter aIter(&rGtkIter);
11740 return child_is_placeholder(aIter);
11743 virtual void set_children_on_demand(const weld::TreeIter& rIter, bool bChildrenOnDemand) override
11745 disable_notify_events();
11747 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11748 GtkInstanceTreeIter aPlaceHolderIter(&rGtkIter);
11750 bool bPlaceHolder = child_is_placeholder(aPlaceHolderIter);
11752 if (bChildrenOnDemand && !bPlaceHolder)
11754 GtkTreeIter subiter;
11755 OUString sDummy("<dummy>");
11756 insert_row(subiter, &rGtkIter.iter, -1, nullptr, &sDummy, nullptr, nullptr);
11758 else if (!bChildrenOnDemand && bPlaceHolder)
11759 remove(aPlaceHolderIter);
11761 enable_notify_events();
11764 virtual void expand_row(const weld::TreeIter& rIter) override
11766 assert(gtk_tree_view_get_model(m_pTreeView) && "don't expand when frozen");
11768 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11769 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
11770 if (!gtk_tree_view_row_expanded(m_pTreeView, path))
11771 gtk_tree_view_expand_to_path(m_pTreeView, path);
11772 gtk_tree_path_free(path);
11775 virtual void collapse_row(const weld::TreeIter& rIter) override
11777 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11778 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
11779 if (gtk_tree_view_row_expanded(m_pTreeView, path))
11780 gtk_tree_view_collapse_row(m_pTreeView, path);
11781 gtk_tree_path_free(path);
11784 virtual OUString get_text(const weld::TreeIter& rIter, int col) const override
11786 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11787 if (col == -1)
11788 col = m_nTextCol;
11789 else
11790 col = to_internal_model(col);
11791 return get(rGtkIter.iter, col);
11794 virtual void set_text(const weld::TreeIter& rIter, const OUString& rText, int col) override
11796 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11797 if (col == -1)
11798 col = m_nTextCol;
11799 else
11800 col = to_internal_model(col);
11801 set(rGtkIter.iter, col, rText);
11804 virtual OUString get_id(const weld::TreeIter& rIter) const override
11806 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11807 return get(rGtkIter.iter, m_nIdCol);
11810 virtual void set_id(const weld::TreeIter& rIter, const OUString& rId) override
11812 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
11813 set(rGtkIter.iter, m_nIdCol, rId);
11816 virtual void freeze() override
11818 disable_notify_events();
11819 GtkInstanceContainer::freeze();
11820 g_object_ref(m_pTreeModel);
11821 gtk_tree_view_set_model(m_pTreeView, nullptr);
11822 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
11823 if (m_xSorter)
11825 int nSortColumn;
11826 GtkSortType eSortType;
11827 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
11828 gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
11829 gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, eSortType);
11831 m_aSavedSortColumns.push_back(nSortColumn);
11832 m_aSavedSortTypes.push_back(eSortType);
11834 enable_notify_events();
11837 virtual void thaw() override
11839 disable_notify_events();
11840 if (m_xSorter)
11842 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
11843 gtk_tree_sortable_set_sort_column_id(pSortable, m_aSavedSortColumns.back(), m_aSavedSortTypes.back());
11844 m_aSavedSortTypes.pop_back();
11845 m_aSavedSortColumns.pop_back();
11847 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
11848 gtk_tree_view_set_model(m_pTreeView, GTK_TREE_MODEL(m_pTreeModel));
11849 g_object_unref(m_pTreeModel);
11850 GtkInstanceContainer::thaw();
11851 enable_notify_events();
11854 virtual int get_height_rows(int nRows) const override
11856 return ::get_height_rows(m_pTreeView, m_pColumns, nRows);
11859 virtual Size get_size_request() const override
11861 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
11862 if (GTK_IS_SCROLLED_WINDOW(pParent))
11864 return Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
11865 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
11867 int nWidth, nHeight;
11868 gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
11869 return Size(nWidth, nHeight);
11872 virtual Size get_preferred_size() const override
11874 Size aRet(-1, -1);
11875 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
11876 if (GTK_IS_SCROLLED_WINDOW(pParent))
11878 aRet = Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
11879 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
11881 GtkRequisition size;
11882 gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
11883 if (aRet.Width() == -1)
11884 aRet.setWidth(size.width);
11885 if (aRet.Height() == -1)
11886 aRet.setHeight(size.height);
11887 return aRet;
11890 virtual void show() override
11892 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
11893 if (GTK_IS_SCROLLED_WINDOW(pParent))
11894 gtk_widget_show(pParent);
11895 gtk_widget_show(m_pWidget);
11898 virtual void hide() override
11900 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
11901 if (GTK_IS_SCROLLED_WINDOW(pParent))
11902 gtk_widget_hide(pParent);
11903 gtk_widget_hide(m_pWidget);
11906 virtual void enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants) override
11908 do_enable_drag_source(rHelper, eDNDConstants);
11911 virtual void drag_source_set(const std::vector<GtkTargetEntry>& rGtkTargets, GdkDragAction eDragAction) override
11913 if (rGtkTargets.empty() && !eDragAction)
11914 gtk_tree_view_unset_rows_drag_source(m_pTreeView);
11915 else
11916 gtk_tree_view_enable_model_drag_source(m_pTreeView, GDK_BUTTON1_MASK, rGtkTargets.data(), rGtkTargets.size(), eDragAction);
11919 virtual void set_selection_mode(SelectionMode eMode) override
11921 disable_notify_events();
11922 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(m_pTreeView), VclToGtk(eMode));
11923 enable_notify_events();
11926 virtual int count_selected_rows() const override
11928 return gtk_tree_selection_count_selected_rows(gtk_tree_view_get_selection(m_pTreeView));
11931 int starts_with(const OUString& rStr, int nStartRow, bool bCaseSensitive)
11933 return ::starts_with(m_pTreeModel, rStr, m_nTextCol, nStartRow, bCaseSensitive);
11936 virtual void disable_notify_events() override
11938 g_signal_handler_block(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
11939 g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
11941 g_signal_handler_block(m_pTreeModel, m_nRowDeletedSignalId);
11942 g_signal_handler_block(m_pTreeModel, m_nRowInsertedSignalId);
11944 GtkInstanceContainer::disable_notify_events();
11947 virtual void enable_notify_events() override
11949 GtkInstanceContainer::enable_notify_events();
11951 g_signal_handler_unblock(m_pTreeModel, m_nRowDeletedSignalId);
11952 g_signal_handler_unblock(m_pTreeModel, m_nRowInsertedSignalId);
11954 g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
11955 g_signal_handler_unblock(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
11958 virtual void connect_popup_menu(const Link<const CommandEvent&, bool>& rLink) override
11960 ensureButtonPressSignal();
11961 weld::TreeView::connect_popup_menu(rLink);
11964 virtual bool get_dest_row_at_pos(const Point &rPos, weld::TreeIter* pResult, bool bDnDMode) override
11966 if (rPos.X() < 0 || rPos.Y() < 0)
11968 // short-circuit to avoid "gtk_tree_view_get_dest_row_at_pos: assertion 'drag_x >= 0'" g_assert
11969 return false;
11972 const bool bAsTree = gtk_tree_view_get_enable_tree_lines(m_pTreeView);
11974 // to keep it simple we'll default to always drop before the current row
11975 // except for the special edge cases
11976 GtkTreeViewDropPosition pos = bAsTree ? GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : GTK_TREE_VIEW_DROP_BEFORE;
11978 // unhighlight current highlighted row
11979 gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, pos);
11981 if (m_bWorkAroundBadDragRegion)
11982 gtk_drag_unhighlight(GTK_WIDGET(m_pTreeView));
11984 GtkTreePath *path = nullptr;
11985 GtkTreeViewDropPosition gtkpos = bAsTree ? GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : GTK_TREE_VIEW_DROP_BEFORE;
11986 bool ret = gtk_tree_view_get_dest_row_at_pos(m_pTreeView, rPos.X(), rPos.Y(),
11987 &path, &gtkpos);
11989 // find the last entry in the model for comparison
11990 GtkTreePath *lastpath = get_path_of_last_entry(m_pTreeModel);
11992 if (!ret)
11994 // empty space, draw an indicator at the last entry
11995 assert(!path);
11996 path = gtk_tree_path_copy(lastpath);
11997 pos = GTK_TREE_VIEW_DROP_AFTER;
11999 else if (bDnDMode && gtk_tree_path_compare(path, lastpath) == 0)
12001 // if we're on the last entry, see if gtk thinks
12002 // the drop should be before or after it, and if
12003 // its after, treat it like a drop into empty
12004 // space, i.e. append it
12005 if (gtkpos == GTK_TREE_VIEW_DROP_AFTER ||
12006 gtkpos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
12008 ret = false;
12009 pos = bAsTree ? gtkpos : GTK_TREE_VIEW_DROP_AFTER;
12013 if (ret && pResult)
12015 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(*pResult);
12016 gtk_tree_model_get_iter(m_pTreeModel, &rGtkIter.iter, path);
12019 if (m_bInDrag && bDnDMode)
12021 // highlight the row
12022 gtk_tree_view_set_drag_dest_row(m_pTreeView, path, pos);
12025 assert(path);
12026 gtk_tree_path_free(path);
12027 gtk_tree_path_free(lastpath);
12029 // auto scroll if we're close to the edges
12030 GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
12031 double fStep = gtk_adjustment_get_step_increment(pVAdjustment);
12032 if (rPos.Y() < fStep)
12034 double fValue = gtk_adjustment_get_value(pVAdjustment) - fStep;
12035 if (fValue < 0)
12036 fValue = 0.0;
12037 gtk_adjustment_set_value(pVAdjustment, fValue);
12039 else
12041 GdkRectangle aRect;
12042 gtk_tree_view_get_visible_rect(m_pTreeView, &aRect);
12043 if (rPos.Y() > aRect.height - fStep)
12045 double fValue = gtk_adjustment_get_value(pVAdjustment) + fStep;
12046 double fMax = gtk_adjustment_get_upper(pVAdjustment);
12047 if (fValue > fMax)
12048 fValue = fMax;
12049 gtk_adjustment_set_value(pVAdjustment, fValue);
12053 return ret;
12056 virtual void unset_drag_dest_row() override
12058 gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, GTK_TREE_VIEW_DROP_BEFORE);
12061 virtual tools::Rectangle get_row_area(const weld::TreeIter& rIter) const override
12063 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
12064 GtkTreePath* pPath = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
12065 tools::Rectangle aRet = ::get_row_area(m_pTreeView, m_pColumns, pPath);
12066 gtk_tree_path_free(pPath);
12067 return aRet;
12070 virtual void start_editing(const weld::TreeIter& rIter) override
12072 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, m_nTextView));
12073 assert(pColumn && "wrong column");
12075 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
12076 GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
12078 // allow editing of cells which are not usually editable, so we can have double click
12079 // do its usual row-activate but if we explicitly want to edit (remote files dialog)
12080 // we can still do that
12081 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
12082 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
12084 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
12085 if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
12087 gboolean is_editable(false);
12088 g_object_get(pCellRenderer, "editable", &is_editable, nullptr);
12089 if (!is_editable)
12091 g_object_set(pCellRenderer, "editable", true, "editable-set", true, nullptr);
12092 g_object_set_data(G_OBJECT(pCellRenderer), "g-lo-RestoreNonEditable", reinterpret_cast<gpointer>(true));
12093 break;
12097 g_list_free(pRenderers);
12099 gtk_tree_view_scroll_to_cell(m_pTreeView, path, pColumn, false, 0, 0);
12100 gtk_tree_view_set_cursor(m_pTreeView, path, pColumn, true);
12102 gtk_tree_path_free(path);
12105 virtual void end_editing() override
12107 GtkTreeViewColumn *focus_column = nullptr;
12108 gtk_tree_view_get_cursor(m_pTreeView, nullptr, &focus_column);
12109 if (focus_column)
12110 gtk_cell_area_stop_editing(gtk_cell_layout_get_area(GTK_CELL_LAYOUT(focus_column)), true);
12113 virtual TreeView* get_drag_source() const override
12115 return g_DragSource;
12118 virtual bool do_signal_drag_begin(bool& rUnsetDragIcon) override
12120 if (m_aDragBeginHdl.Call(rUnsetDragIcon))
12121 return true;
12122 g_DragSource = this;
12123 return false;
12126 virtual void do_signal_drag_end() override
12128 g_DragSource = nullptr;
12131 // Under gtk 3.24.8 dragging into the TreeView is not highlighting
12132 // entire TreeView widget, just the rectangle which has no entries
12133 // in it, so as a workaround highlight the parent container
12134 // on drag start, and undo it on drag end, and trigger removal
12135 // of the treeview's highlight effort
12136 virtual void drag_started() override
12138 m_bInDrag = true;
12139 GtkWidget* pWidget = GTK_WIDGET(m_pTreeView);
12140 GtkWidget* pParent = gtk_widget_get_parent(pWidget);
12141 if (GTK_IS_SCROLLED_WINDOW(pParent))
12143 gtk_drag_unhighlight(pWidget);
12144 gtk_drag_highlight(pParent);
12145 m_bWorkAroundBadDragRegion = true;
12149 virtual void drag_ended() override
12151 m_bInDrag = false;
12152 if (m_bWorkAroundBadDragRegion)
12154 GtkWidget* pWidget = GTK_WIDGET(m_pTreeView);
12155 GtkWidget* pParent = gtk_widget_get_parent(pWidget);
12156 gtk_drag_unhighlight(pParent);
12157 m_bWorkAroundBadDragRegion = false;
12159 // unhighlight the row
12160 gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, GTK_TREE_VIEW_DROP_BEFORE);
12163 virtual int vadjustment_get_value() const override
12165 if (m_nPendingVAdjustment != -1)
12166 return m_nPendingVAdjustment;
12167 return gtk_adjustment_get_value(m_pVAdjustment);
12170 virtual void vadjustment_set_value(int value) override
12172 disable_notify_events();
12174 /* This rube goldberg device is to remove flicker from setting the
12175 scroll position of a GtkTreeView directly after clearing it and
12176 filling it. As a specific example the writer navigator with ~100
12177 tables, scroll to the end, right click on an entry near the end
12178 and rename it, the tree is cleared and refilled and an attempt
12179 made to set the scroll position of the freshly refilled tree to
12180 the same point as before the clear.
12183 // This forces the tree to recalculate now its preferred size
12184 // after being cleared
12185 GtkRequisition size;
12186 gtk_widget_get_preferred_size(GTK_WIDGET(m_pTreeView), nullptr, &size);
12188 m_nPendingVAdjustment = value;
12190 // The value set here just has to be different to the final value
12191 // set later so that isn't a no-op
12192 gtk_adjustment_set_value(m_pVAdjustment, value - 0.0001);
12194 // This will set the desired m_nPendingVAdjustment value right
12195 // before the tree gets drawn
12196 gtk_widget_add_tick_callback(GTK_WIDGET(m_pTreeView), setAdjustmentCallback, this, nullptr);
12198 enable_notify_events();
12201 void call_signal_custom_render(VirtualDevice& rOutput, const tools::Rectangle& rRect, bool bSelected, const OUString& rId)
12203 signal_custom_render(rOutput, rRect, bSelected, rId);
12206 Size call_signal_custom_get_size(VirtualDevice& rOutput, const OUString& rId)
12208 return signal_custom_get_size(rOutput, rId);
12211 virtual void set_show_expanders(bool bShow) override
12213 gtk_tree_view_set_show_expanders(m_pTreeView, bShow);
12216 virtual ~GtkInstanceTreeView() override
12218 if (m_pChangeEvent)
12219 Application::RemoveUserEvent(m_pChangeEvent);
12220 if (m_nQueryTooltipSignalId)
12221 g_signal_handler_disconnect(m_pTreeView, m_nQueryTooltipSignalId);
12222 g_signal_handler_disconnect(m_pTreeView, m_nKeyPressSignalId);
12223 g_signal_handler_disconnect(m_pTreeView, m_nPopupMenuSignalId);
12224 g_signal_handler_disconnect(m_pTreeModel, m_nRowDeletedSignalId);
12225 g_signal_handler_disconnect(m_pTreeModel, m_nRowInsertedSignalId);
12227 if (m_nVAdjustmentChangedSignalId)
12229 GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
12230 g_signal_handler_disconnect(pVAdjustment, m_nVAdjustmentChangedSignalId);
12233 g_signal_handler_disconnect(m_pTreeView, m_nTestCollapseRowSignalId);
12234 g_signal_handler_disconnect(m_pTreeView, m_nTestExpandRowSignalId);
12235 g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
12236 g_signal_handler_disconnect(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
12238 GValue value = G_VALUE_INIT;
12239 g_value_init(&value, G_TYPE_POINTER);
12240 g_value_set_pointer(&value, static_cast<gpointer>(nullptr));
12242 for (GList* pEntry = g_list_last(m_pColumns); pEntry; pEntry = g_list_previous(pEntry))
12244 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
12245 g_signal_handler_disconnect(pColumn, m_aColumnSignalIds.back());
12246 m_aColumnSignalIds.pop_back();
12248 // unset "instance" to avoid dangling "instance" points in any CustomCellRenderers
12249 GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
12250 for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
12252 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
12253 if (!CUSTOM_IS_CELL_RENDERER_SURFACE(pCellRenderer))
12254 continue;
12255 g_object_set_property(G_OBJECT(pCellRenderer), "instance", &value);
12257 g_list_free(pRenderers);
12259 g_list_free(m_pColumns);
12263 void ensure_device(CustomCellRendererSurface *cellsurface, weld::Widget* pWidget)
12265 if (!cellsurface->device)
12267 cellsurface->device = VclPtr<VirtualDevice>::Create();
12268 cellsurface->device->SetBackground(COL_TRANSPARENT);
12269 // expand the point size of the desired font to the equivalent pixel size
12270 if (vcl::Window* pDefaultDevice = dynamic_cast<vcl::Window*>(Application::GetDefaultDevice()))
12271 pDefaultDevice->SetPointFont(*cellsurface->device, pWidget->get_font());
12277 IMPL_LINK_NOARG(GtkInstanceTreeView, async_signal_changed, void*, void)
12279 m_pChangeEvent = nullptr;
12280 signal_changed();
12283 IMPL_LINK_NOARG(GtkInstanceTreeView, async_stop_cell_editing, void*, void)
12285 end_editing();
12288 namespace {
12290 class GtkInstanceIconView : public GtkInstanceContainer, public virtual weld::IconView
12292 private:
12293 GtkIconView* m_pIconView;
12294 GtkTreeStore* m_pTreeStore;
12295 gint m_nTextCol;
12296 gint m_nImageCol;
12297 gint m_nIdCol;
12298 gulong m_nSelectionChangedSignalId;
12299 gulong m_nItemActivatedSignalId;
12300 ImplSVEvent* m_pSelectionChangeEvent;
12302 DECL_LINK(async_signal_selection_changed, void*, void);
12304 void launch_signal_selection_changed()
12306 //tdf#117991 selection change is sent before the focus change, and focus change
12307 //is what will cause a spinbutton that currently has the focus to set its contents
12308 //as the spin button value. So any LibreOffice callbacks on
12309 //signal-change would happen before the spinbutton value-change occurs.
12310 //To avoid this, send the signal-change to LibreOffice to occur after focus-change
12311 //has been processed
12312 if (m_pSelectionChangeEvent)
12313 Application::RemoveUserEvent(m_pSelectionChangeEvent);
12314 m_pSelectionChangeEvent = Application::PostUserEvent(LINK(this, GtkInstanceIconView, async_signal_selection_changed));
12317 static void signalSelectionChanged(GtkIconView*, gpointer widget)
12319 GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
12320 pThis->launch_signal_selection_changed();
12323 void handle_item_activated()
12325 if (signal_item_activated())
12326 return;
12329 static void signalItemActivated(GtkIconView*, GtkTreePath*, gpointer widget)
12331 GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
12332 SolarMutexGuard aGuard;
12333 pThis->handle_item_activated();
12336 void insert_item(GtkTreeIter& iter, int pos, const OUString* pId, const OUString* pText, const OUString* pIconName)
12338 gtk_tree_store_insert_with_values(m_pTreeStore, &iter, nullptr, pos,
12339 m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
12340 m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
12341 -1);
12342 if (pIconName)
12344 GdkPixbuf* pixbuf = getPixbuf(*pIconName);
12345 gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, pixbuf, -1);
12346 if (pixbuf)
12347 g_object_unref(pixbuf);
12351 OUString get(const GtkTreeIter& iter, int col) const
12353 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
12354 gchar* pStr;
12355 gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &pStr, -1);
12356 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
12357 g_free(pStr);
12358 return sRet;
12361 bool get_selected_iterator(GtkTreeIter* pIter) const
12363 assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
12364 bool bRet = false;
12366 GtkTreeModel* pModel = GTK_TREE_MODEL(m_pTreeStore);
12367 GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
12368 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
12370 if (pIter)
12372 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
12373 gtk_tree_model_get_iter(pModel, pIter, path);
12375 bRet = true;
12376 break;
12378 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
12380 return bRet;
12383 public:
12384 GtkInstanceIconView(GtkIconView* pIconView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12385 : GtkInstanceContainer(GTK_CONTAINER(pIconView), pBuilder, bTakeOwnership)
12386 , m_pIconView(pIconView)
12387 , m_pTreeStore(GTK_TREE_STORE(gtk_icon_view_get_model(m_pIconView)))
12388 , m_nTextCol(gtk_icon_view_get_text_column(m_pIconView))
12389 , m_nImageCol(gtk_icon_view_get_pixbuf_column(m_pIconView))
12390 , m_nSelectionChangedSignalId(g_signal_connect(pIconView, "selection-changed",
12391 G_CALLBACK(signalSelectionChanged), this))
12392 , m_nItemActivatedSignalId(g_signal_connect(pIconView, "item-activated", G_CALLBACK(signalItemActivated), this))
12393 , m_pSelectionChangeEvent(nullptr)
12395 m_nIdCol = m_nTextCol + 1;
12398 virtual void insert(int pos, const OUString* pText, const OUString* pId, const OUString* pIconName, weld::TreeIter* pRet) override
12400 disable_notify_events();
12401 GtkTreeIter iter;
12402 insert_item(iter, pos, pId, pText, pIconName);
12403 if (pRet)
12405 GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
12406 pGtkRetIter->iter = iter;
12408 enable_notify_events();
12411 virtual OUString get_selected_id() const override
12413 assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
12414 GtkTreeIter iter;
12415 if (get_selected_iterator(&iter))
12416 return get(iter, m_nIdCol);
12417 return OUString();
12420 virtual void clear() override
12422 disable_notify_events();
12423 gtk_tree_store_clear(m_pTreeStore);
12424 enable_notify_events();
12427 virtual void freeze() override
12429 disable_notify_events();
12430 GtkInstanceContainer::freeze();
12431 g_object_ref(m_pTreeStore);
12432 gtk_icon_view_set_model(m_pIconView, nullptr);
12433 g_object_freeze_notify(G_OBJECT(m_pTreeStore));
12434 enable_notify_events();
12437 virtual void thaw() override
12439 disable_notify_events();
12440 g_object_thaw_notify(G_OBJECT(m_pTreeStore));
12441 gtk_icon_view_set_model(m_pIconView, GTK_TREE_MODEL(m_pTreeStore));
12442 g_object_unref(m_pTreeStore);
12443 GtkInstanceContainer::thaw();
12444 enable_notify_events();
12447 virtual Size get_size_request() const override
12449 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
12450 if (GTK_IS_SCROLLED_WINDOW(pParent))
12452 return Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
12453 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
12455 int nWidth, nHeight;
12456 gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
12457 return Size(nWidth, nHeight);
12460 virtual Size get_preferred_size() const override
12462 Size aRet(-1, -1);
12463 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
12464 if (GTK_IS_SCROLLED_WINDOW(pParent))
12466 aRet = Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
12467 gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
12469 GtkRequisition size;
12470 gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
12471 if (aRet.Width() == -1)
12472 aRet.setWidth(size.width);
12473 if (aRet.Height() == -1)
12474 aRet.setHeight(size.height);
12475 return aRet;
12478 virtual void show() override
12480 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
12481 if (GTK_IS_SCROLLED_WINDOW(pParent))
12482 gtk_widget_show(pParent);
12483 gtk_widget_show(m_pWidget);
12486 virtual void hide() override
12488 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
12489 if (GTK_IS_SCROLLED_WINDOW(pParent))
12490 gtk_widget_hide(pParent);
12491 gtk_widget_hide(m_pWidget);
12494 virtual OUString get_selected_text() const override
12496 assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
12497 GtkTreeIter iter;
12498 if (get_selected_iterator(&iter))
12499 return get(iter, m_nTextCol);
12500 return OUString();
12503 virtual int count_selected_items() const override
12505 GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
12506 int nRet = g_list_length(pList);
12507 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
12508 return nRet;
12511 virtual void select(int pos) override
12513 assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
12514 disable_notify_events();
12515 if (pos == -1 || (pos == 0 && n_children() == 0))
12517 gtk_icon_view_unselect_all(m_pIconView);
12519 else
12521 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
12522 gtk_icon_view_select_path(m_pIconView, path);
12523 gtk_icon_view_scroll_to_path(m_pIconView, path, false, 0, 0);
12524 gtk_tree_path_free(path);
12526 enable_notify_events();
12529 virtual void unselect(int pos) override
12531 assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
12532 disable_notify_events();
12533 if (pos == -1 || (pos == 0 && n_children() == 0))
12535 gtk_icon_view_select_all(m_pIconView);
12537 else
12539 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
12540 gtk_icon_view_select_path(m_pIconView, path);
12541 gtk_tree_path_free(path);
12543 enable_notify_events();
12546 virtual bool get_selected(weld::TreeIter* pIter) const override
12548 GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
12549 return get_selected_iterator(pGtkIter ? &pGtkIter->iter : nullptr);
12552 virtual bool get_cursor(weld::TreeIter* pIter) const override
12554 GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
12555 GtkTreePath* path;
12556 gtk_icon_view_get_cursor(m_pIconView, &path, nullptr);
12557 if (pGtkIter && path)
12559 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
12560 gtk_tree_model_get_iter(pModel, &pGtkIter->iter, path);
12562 return path != nullptr;
12565 virtual void set_cursor(const weld::TreeIter& rIter) override
12567 disable_notify_events();
12568 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
12569 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
12570 GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
12571 gtk_icon_view_set_cursor(m_pIconView, path, nullptr, false);
12572 gtk_tree_path_free(path);
12573 enable_notify_events();
12576 virtual bool get_iter_first(weld::TreeIter& rIter) const override
12578 GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
12579 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
12580 return gtk_tree_model_get_iter_first(pModel, &rGtkIter.iter);
12583 virtual void scroll_to_item(const weld::TreeIter& rIter) override
12585 assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
12586 disable_notify_events();
12587 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
12588 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
12589 GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
12590 gtk_icon_view_scroll_to_path(m_pIconView, path, false, 0, 0);
12591 gtk_tree_path_free(path);
12592 enable_notify_events();
12595 virtual std::unique_ptr<weld::TreeIter> make_iterator(const weld::TreeIter* pOrig) const override
12597 return std::unique_ptr<weld::TreeIter>(new GtkInstanceTreeIter(static_cast<const GtkInstanceTreeIter*>(pOrig)));
12600 virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
12602 GtkInstanceTreeIter aGtkIter(nullptr);
12604 GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
12605 GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
12606 for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
12608 GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
12609 gtk_tree_model_get_iter(pModel, &aGtkIter.iter, path);
12610 if (func(aGtkIter))
12611 break;
12613 g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
12616 virtual int n_children() const override
12618 return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_pTreeStore), nullptr);
12621 virtual OUString get_id(const weld::TreeIter& rIter) const override
12623 const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
12624 return get(rGtkIter.iter, m_nIdCol);
12627 virtual void disable_notify_events() override
12629 g_signal_handler_block(m_pIconView, m_nSelectionChangedSignalId);
12630 g_signal_handler_block(m_pIconView, m_nItemActivatedSignalId);
12632 GtkInstanceContainer::disable_notify_events();
12635 virtual void enable_notify_events() override
12637 GtkInstanceContainer::enable_notify_events();
12639 g_signal_handler_unblock(m_pIconView, m_nItemActivatedSignalId);
12640 g_signal_handler_unblock(m_pIconView, m_nSelectionChangedSignalId);
12643 virtual ~GtkInstanceIconView() override
12645 if (m_pSelectionChangeEvent)
12646 Application::RemoveUserEvent(m_pSelectionChangeEvent);
12648 g_signal_handler_disconnect(m_pIconView, m_nItemActivatedSignalId);
12649 g_signal_handler_disconnect(m_pIconView, m_nSelectionChangedSignalId);
12655 IMPL_LINK_NOARG(GtkInstanceIconView, async_signal_selection_changed, void*, void)
12657 m_pSelectionChangeEvent = nullptr;
12658 signal_selection_changed();
12661 namespace {
12663 class GtkInstanceSpinButton : public GtkInstanceEntry, public virtual weld::SpinButton
12665 private:
12666 GtkSpinButton* m_pButton;
12667 gulong m_nValueChangedSignalId;
12668 gulong m_nOutputSignalId;
12669 gulong m_nInputSignalId;
12670 bool m_bFormatting;
12671 bool m_bBlockOutput;
12672 bool m_bBlank;
12674 static void signalValueChanged(GtkSpinButton*, gpointer widget)
12676 GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
12677 SolarMutexGuard aGuard;
12678 pThis->m_bBlank = false;
12679 pThis->signal_value_changed();
12682 bool guarded_signal_output()
12684 if (m_bBlockOutput)
12685 return true;
12686 m_bFormatting = true;
12687 bool bRet = signal_output();
12688 m_bFormatting = false;
12689 return bRet;
12692 static gboolean signalOutput(GtkSpinButton*, gpointer widget)
12694 GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
12695 SolarMutexGuard aGuard;
12696 return pThis->guarded_signal_output();
12699 static gint signalInput(GtkSpinButton*, gdouble* new_value, gpointer widget)
12701 GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
12702 SolarMutexGuard aGuard;
12703 int result;
12704 TriState eHandled = pThis->signal_input(&result);
12705 if (eHandled == TRISTATE_INDET)
12706 return 0;
12707 if (eHandled == TRISTATE_TRUE)
12709 *new_value = pThis->toGtk(result);
12710 return 1;
12712 return GTK_INPUT_ERROR;
12715 virtual void signal_activate() override
12717 gtk_spin_button_update(m_pButton);
12718 GtkInstanceEntry::signal_activate();
12721 double toGtk(int nValue) const
12723 return static_cast<double>(nValue) / Power10(get_digits());
12726 int fromGtk(double fValue) const
12728 return FRound(fValue * Power10(get_digits()));
12731 public:
12732 GtkInstanceSpinButton(GtkSpinButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12733 : GtkInstanceEntry(GTK_ENTRY(pButton), pBuilder, bTakeOwnership)
12734 , m_pButton(pButton)
12735 , m_nValueChangedSignalId(g_signal_connect(pButton, "value-changed", G_CALLBACK(signalValueChanged), this))
12736 , m_nOutputSignalId(g_signal_connect(pButton, "output", G_CALLBACK(signalOutput), this))
12737 , m_nInputSignalId(g_signal_connect(pButton, "input", G_CALLBACK(signalInput), this))
12738 , m_bFormatting(false)
12739 , m_bBlockOutput(false)
12740 , m_bBlank(false)
12744 virtual int get_value() const override
12746 return fromGtk(gtk_spin_button_get_value(m_pButton));
12749 virtual void set_value(int value) override
12751 disable_notify_events();
12752 m_bBlank = false;
12753 gtk_spin_button_set_value(m_pButton, toGtk(value));
12754 enable_notify_events();
12757 virtual void set_text(const OUString& rText) override
12759 disable_notify_events();
12760 // tdf#122786 if we're just formatting a value, then we're done,
12761 // however if set_text has been called directly we want to update our
12762 // value from this new text, but don't want to reformat with that value
12763 if (!m_bFormatting)
12765 gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
12767 m_bBlockOutput = true;
12768 gtk_spin_button_update(m_pButton);
12769 m_bBlank = rText.isEmpty();
12770 m_bBlockOutput = false;
12772 else
12774 bool bKeepBlank = m_bBlank && get_value() == 0;
12775 if (!bKeepBlank)
12777 gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
12778 m_bBlank = false;
12781 enable_notify_events();
12784 virtual void set_range(int min, int max) override
12786 disable_notify_events();
12787 gtk_spin_button_set_range(m_pButton, toGtk(min), toGtk(max));
12788 enable_notify_events();
12791 virtual void get_range(int& min, int& max) const override
12793 double gtkmin, gtkmax;
12794 gtk_spin_button_get_range(m_pButton, &gtkmin, &gtkmax);
12795 min = fromGtk(gtkmin);
12796 max = fromGtk(gtkmax);
12799 virtual void set_increments(int step, int page) override
12801 disable_notify_events();
12802 gtk_spin_button_set_increments(m_pButton, toGtk(step), toGtk(page));
12803 enable_notify_events();
12806 virtual void get_increments(int& step, int& page) const override
12808 double gtkstep, gtkpage;
12809 gtk_spin_button_get_increments(m_pButton, &gtkstep, &gtkpage);
12810 step = fromGtk(gtkstep);
12811 page = fromGtk(gtkpage);
12814 virtual void set_digits(unsigned int digits) override
12816 disable_notify_events();
12817 gtk_spin_button_set_digits(m_pButton, digits);
12818 enable_notify_events();
12821 virtual unsigned int get_digits() const override
12823 return gtk_spin_button_get_digits(m_pButton);
12826 virtual void disable_notify_events() override
12828 g_signal_handler_block(m_pButton, m_nValueChangedSignalId);
12829 GtkInstanceEntry::disable_notify_events();
12832 virtual void enable_notify_events() override
12834 GtkInstanceEntry::enable_notify_events();
12835 g_signal_handler_unblock(m_pButton, m_nValueChangedSignalId);
12838 virtual ~GtkInstanceSpinButton() override
12840 g_signal_handler_disconnect(m_pButton, m_nInputSignalId);
12841 g_signal_handler_disconnect(m_pButton, m_nOutputSignalId);
12842 g_signal_handler_disconnect(m_pButton, m_nValueChangedSignalId);
12846 class GtkInstanceFormattedSpinButton : public GtkInstanceEntry, public virtual weld::FormattedSpinButton
12848 private:
12849 GtkSpinButton* m_pButton;
12850 std::unique_ptr<weld::EntryFormatter> m_xOwnFormatter;
12851 weld::EntryFormatter* m_pFormatter;
12852 gulong m_nValueChangedSignalId;
12853 gulong m_nOutputSignalId;
12854 gulong m_nInputSignalId;
12855 bool m_bEmptyField;
12856 bool m_bSyncingValue;
12857 double m_dValueWhenEmpty;
12859 bool signal_output()
12861 double fValue = gtk_spin_button_get_value(m_pButton);
12862 m_bEmptyField &= fValue == m_dValueWhenEmpty;
12863 if (!m_bEmptyField)
12864 GetFormatter().SetValue(fValue);
12865 return true;
12868 static gboolean signalOutput(GtkSpinButton*, gpointer widget)
12870 GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
12871 SolarMutexGuard aGuard;
12872 return pThis->signal_output();
12875 gint signal_input(double* value)
12877 Formatter& rFormatter = GetFormatter();
12878 rFormatter.Modify();
12879 // if the blank-mode is enabled then if the input is empty don't parse
12880 // the input but keep the value as it is. store what the value the
12881 // blank is associated with and until the value is changed, or the text
12882 // is updated from the outside, don't output that value
12883 m_bEmptyField = rFormatter.IsEmptyFieldEnabled() && get_text().isEmpty();
12884 if (m_bEmptyField)
12886 m_dValueWhenEmpty = gtk_spin_button_get_value(m_pButton);
12887 *value = m_dValueWhenEmpty;
12889 else
12890 *value = rFormatter.GetValue();
12891 return 1;
12894 static gint signalInput(GtkSpinButton*, gdouble* new_value, gpointer widget)
12896 GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
12897 SolarMutexGuard aGuard;
12898 return pThis->signal_input(new_value);
12901 static void signalValueChanged(GtkSpinButton*, gpointer widget)
12903 GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
12904 SolarMutexGuard aGuard;
12905 pThis->signal_value_changed();
12908 public:
12909 GtkInstanceFormattedSpinButton(GtkSpinButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
12910 : GtkInstanceEntry(GTK_ENTRY(pButton), pBuilder, bTakeOwnership)
12911 , m_pButton(pButton)
12912 , m_pFormatter(nullptr)
12913 , m_nValueChangedSignalId(g_signal_connect(pButton, "value-changed", G_CALLBACK(signalValueChanged), this))
12914 , m_nOutputSignalId(g_signal_connect(pButton, "output", G_CALLBACK(signalOutput), this))
12915 , m_nInputSignalId(g_signal_connect(pButton, "input", G_CALLBACK(signalInput), this))
12916 , m_bEmptyField(false)
12917 , m_bSyncingValue(false)
12918 , m_dValueWhenEmpty(0.0)
12922 virtual void set_text(const OUString& rText) override
12924 GtkInstanceEntry::set_text(rText);
12925 Formatter& rFormatter = GetFormatter();
12926 m_bEmptyField = rFormatter.IsEmptyFieldEnabled() && rText.isEmpty();
12927 if (m_bEmptyField)
12928 m_dValueWhenEmpty = gtk_spin_button_get_value(m_pButton);
12931 virtual void connect_changed(const Link<weld::Entry&, void>& rLink) override
12933 if (!m_pFormatter) // once a formatter is set, it takes over "changed"
12935 GtkInstanceEntry::connect_changed(rLink);
12936 return;
12938 m_pFormatter->connect_changed(rLink);
12941 virtual void connect_focus_out(const Link<weld::Widget&, void>& rLink) override
12943 if (!m_pFormatter) // once a formatter is set, it takes over "focus-out"
12945 GtkInstanceEntry::connect_focus_out(rLink);
12946 return;
12948 m_pFormatter->connect_focus_out(rLink);
12951 virtual void SetFormatter(weld::EntryFormatter* pFormatter) override
12953 m_xOwnFormatter.reset();
12954 m_pFormatter = pFormatter;
12955 sync_range_from_formatter();
12956 sync_value_from_formatter();
12957 sync_increments_from_formatter();
12960 virtual weld::EntryFormatter& GetFormatter() override
12962 if (!m_pFormatter)
12964 auto aFocusOutHdl = m_aFocusOutHdl;
12965 m_aFocusOutHdl = Link<weld::Widget&, void>();
12966 auto aChangeHdl = m_aChangeHdl;
12967 m_aChangeHdl = Link<weld::Entry&, void>();
12969 double fValue = gtk_spin_button_get_value(m_pButton);
12970 double fMin, fMax;
12971 gtk_spin_button_get_range(m_pButton, &fMin, &fMax);
12972 double fStep;
12973 gtk_spin_button_get_increments(m_pButton, &fStep, nullptr);
12974 m_xOwnFormatter.reset(new weld::EntryFormatter(*this));
12975 m_xOwnFormatter->SetMinValue(fMin);
12976 m_xOwnFormatter->SetMaxValue(fMax);
12977 m_xOwnFormatter->SetSpinSize(fStep);
12978 m_xOwnFormatter->SetValue(fValue);
12980 m_xOwnFormatter->connect_focus_out(aFocusOutHdl);
12981 m_xOwnFormatter->connect_changed(aChangeHdl);
12983 m_pFormatter = m_xOwnFormatter.get();
12985 return *m_pFormatter;
12988 virtual void sync_value_from_formatter() override
12990 if (!m_pFormatter)
12991 return;
12992 // tdf#135317 avoid reenterence
12993 if (m_bSyncingValue)
12994 return;
12995 m_bSyncingValue = true;
12996 disable_notify_events();
12997 // tdf#138519 use gtk_adjustment_set_value instead of gtk_spin_button_set_value because the
12998 // latter doesn't change the value if the new value is less than an EPSILON diff of 1e-10
12999 // from the old value
13000 gtk_adjustment_set_value(gtk_spin_button_get_adjustment(m_pButton), m_pFormatter->GetValue());
13001 enable_notify_events();
13002 m_bSyncingValue = false;
13005 virtual void sync_range_from_formatter() override
13007 if (!m_pFormatter)
13008 return;
13009 disable_notify_events();
13010 double fMin = m_pFormatter->HasMinValue() ? m_pFormatter->GetMinValue() : std::numeric_limits<double>::lowest();
13011 double fMax = m_pFormatter->HasMaxValue() ? m_pFormatter->GetMaxValue() : std::numeric_limits<double>::max();
13012 gtk_spin_button_set_range(m_pButton, fMin, fMax);
13013 enable_notify_events();
13016 virtual void sync_increments_from_formatter() override
13018 if (!m_pFormatter)
13019 return;
13020 disable_notify_events();
13021 double fSpinSize = m_pFormatter->GetSpinSize();
13022 gtk_spin_button_set_increments(m_pButton, fSpinSize, fSpinSize * 10);
13023 enable_notify_events();
13026 virtual void disable_notify_events() override
13028 g_signal_handler_block(m_pButton, m_nValueChangedSignalId);
13029 GtkInstanceEntry::disable_notify_events();
13032 virtual void enable_notify_events() override
13034 GtkInstanceEntry::enable_notify_events();
13035 g_signal_handler_unblock(m_pButton, m_nValueChangedSignalId);
13038 virtual ~GtkInstanceFormattedSpinButton() override
13040 g_signal_handler_disconnect(m_pButton, m_nInputSignalId);
13041 g_signal_handler_disconnect(m_pButton, m_nOutputSignalId);
13042 g_signal_handler_disconnect(m_pButton, m_nValueChangedSignalId);
13044 m_pFormatter = nullptr;
13045 m_xOwnFormatter.reset();
13049 class GtkInstanceLabel : public GtkInstanceWidget, public virtual weld::Label
13051 private:
13052 GtkLabel* m_pLabel;
13054 void set_text_background_color(const Color& rColor)
13056 guint16 nRed = rColor.GetRed() << 8;
13057 guint16 nGreen = rColor.GetRed() << 8;
13058 guint16 nBlue = rColor.GetBlue() << 8;
13060 PangoAttrType aFilterAttrs[] = {PANGO_ATTR_BACKGROUND, PANGO_ATTR_INVALID};
13062 PangoAttrList* pOrigList = gtk_label_get_attributes(m_pLabel);
13063 PangoAttrList* pAttrs = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
13064 PangoAttrList* pRemovedAttrs = pOrigList ? pango_attr_list_filter(pAttrs, filter_pango_attrs, &aFilterAttrs) : nullptr;
13065 pango_attr_list_insert(pAttrs, pango_attr_background_new(nRed, nGreen, nBlue));
13066 gtk_label_set_attributes(m_pLabel, pAttrs);
13067 pango_attr_list_unref(pAttrs);
13068 pango_attr_list_unref(pRemovedAttrs);
13071 void set_text_foreground_color(const Color& rColor, bool bSetBold)
13073 guint16 nRed = rColor.GetRed() << 8;
13074 guint16 nGreen = rColor.GetRed() << 8;
13075 guint16 nBlue = rColor.GetBlue() << 8;
13077 PangoAttrType aFilterAttrs[] = {PANGO_ATTR_FOREGROUND, PANGO_ATTR_WEIGHT, PANGO_ATTR_INVALID};
13079 if (!bSetBold)
13080 aFilterAttrs[1] = PANGO_ATTR_INVALID;
13082 PangoAttrList* pOrigList = gtk_label_get_attributes(m_pLabel);
13083 PangoAttrList* pAttrs = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
13084 PangoAttrList* pRemovedAttrs = pOrigList ? pango_attr_list_filter(pAttrs, filter_pango_attrs, &aFilterAttrs) : nullptr;
13085 if (rColor != COL_AUTO)
13086 pango_attr_list_insert(pAttrs, pango_attr_foreground_new(nRed, nGreen, nBlue));
13087 if (bSetBold)
13088 pango_attr_list_insert(pAttrs, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
13089 gtk_label_set_attributes(m_pLabel, pAttrs);
13090 pango_attr_list_unref(pAttrs);
13091 pango_attr_list_unref(pRemovedAttrs);
13094 public:
13095 GtkInstanceLabel(GtkLabel* pLabel, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
13096 : GtkInstanceWidget(GTK_WIDGET(pLabel), pBuilder, bTakeOwnership)
13097 , m_pLabel(pLabel)
13101 virtual void set_label(const OUString& rText) override
13103 ::set_label(m_pLabel, rText);
13106 virtual OUString get_label() const override
13108 return ::get_label(m_pLabel);
13111 virtual void set_mnemonic_widget(Widget* pTarget) override
13113 assert(!gtk_label_get_selectable(m_pLabel) && "don't use set_mnemonic_widget on selectable labels, for consistency with gen backend");
13114 GtkInstanceWidget* pTargetWidget = dynamic_cast<GtkInstanceWidget*>(pTarget);
13115 gtk_label_set_mnemonic_widget(m_pLabel, pTargetWidget ? pTargetWidget->getWidget() : nullptr);
13118 virtual void set_label_type(weld::LabelType eType) override
13120 switch (eType)
13122 case weld::LabelType::Normal:
13123 gtk_label_set_attributes(m_pLabel, nullptr);
13124 break;
13125 case weld::LabelType::Warning:
13126 set_text_background_color(COL_YELLOW);
13127 break;
13128 case weld::LabelType::Error:
13129 set_text_background_color(Application::GetSettings().GetStyleSettings().GetHighlightColor());
13130 break;
13131 case weld::LabelType::Title:
13132 set_text_foreground_color(Application::GetSettings().GetStyleSettings().GetLightColor(), true);
13133 break;
13137 virtual void set_font(const vcl::Font& rFont) override
13139 ::set_font(m_pLabel, rFont);
13142 virtual void set_font_color(const Color& rColor) override
13144 set_text_foreground_color(rColor, false);
13150 std::unique_ptr<weld::Label> GtkInstanceFrame::weld_label_widget() const
13152 GtkWidget* pLabel = gtk_frame_get_label_widget(m_pFrame);
13153 if (!pLabel || !GTK_IS_LABEL(pLabel))
13154 return nullptr;
13155 return std::make_unique<GtkInstanceLabel>(GTK_LABEL(pLabel), m_pBuilder, false);
13158 namespace {
13160 class GtkInstanceTextView : public GtkInstanceContainer, public virtual weld::TextView
13162 private:
13163 GtkTextView* m_pTextView;
13164 GtkTextBuffer* m_pTextBuffer;
13165 GtkAdjustment* m_pVAdjustment;
13166 GtkCssProvider* m_pFgCssProvider;
13167 int m_nMaxTextLength;
13168 gulong m_nChangedSignalId; // we don't disable/enable this one, it's to implement max-length
13169 gulong m_nInsertTextSignalId;
13170 gulong m_nCursorPosSignalId;
13171 gulong m_nHasSelectionSignalId; // we don't disable/enable this one, it's to implement
13172 // auto-scroll to cursor on losing selection
13173 gulong m_nVAdjustChangedSignalId;
13174 gulong m_nButtonPressEvent; // we don't disable/enable this one, it's to block mouse
13175 // click down from getting to (potential) toplevel
13176 // GtkSalFrame parent, which grabs focus away
13178 static gboolean signalButtonPressEvent(GtkWidget*, GdkEventButton*, gpointer)
13180 // e.g. on clicking on the help TextView in OTableDesignHelpBar the currently displayed text shouldn't disappear
13181 return true;
13184 static void signalChanged(GtkTextBuffer*, gpointer widget)
13186 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
13187 SolarMutexGuard aGuard;
13188 pThis->signal_changed();
13191 static void signalInserText(GtkTextBuffer *pBuffer, GtkTextIter *pLocation, gchar* /*pText*/, gint /*nLen*/, gpointer widget)
13193 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
13194 pThis->insert_text(pBuffer, pLocation);
13197 void insert_text(GtkTextBuffer *pBuffer, GtkTextIter *pLocation)
13199 if (m_nMaxTextLength)
13201 gint nCount = gtk_text_buffer_get_char_count(pBuffer);
13202 if (nCount > m_nMaxTextLength)
13204 GtkTextIter nStart, nEnd;
13205 gtk_text_buffer_get_iter_at_offset(m_pTextBuffer, &nStart, m_nMaxTextLength);
13206 gtk_text_buffer_get_end_iter(m_pTextBuffer, &nEnd);
13207 gtk_text_buffer_delete(m_pTextBuffer, &nStart, &nEnd);
13208 gtk_text_iter_assign(pLocation, &nStart);
13213 static void signalCursorPosition(GtkTextBuffer*, GParamSpec*, gpointer widget)
13215 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
13216 pThis->signal_cursor_position();
13219 static void signalHasSelection(GtkTextBuffer*, GParamSpec*, gpointer widget)
13221 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
13222 pThis->signal_has_selection();
13225 void signal_has_selection()
13228 in the data browser (Data Sources, shift+ctrl+f4), entering a
13229 multiline cell selects all, on cursoring to the right, the selection
13230 is lost and the cursor is at the end but gtk doesn't auto-scroll to
13231 the cursor so if the text needs scrolling to see the cursor it is off
13232 screen, another cursor makes gtk auto-scroll as wanted. So on losing
13233 selection help gtk out and do the initial scroll ourselves here
13235 if (!gtk_text_buffer_get_has_selection(m_pTextBuffer))
13237 GtkTextMark* pMark = gtk_text_buffer_get_insert(m_pTextBuffer);
13238 gtk_text_view_scroll_mark_onscreen(m_pTextView, pMark);
13242 static void signalVAdjustValueChanged(GtkAdjustment*, gpointer widget)
13244 GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
13245 SolarMutexGuard aGuard;
13246 pThis->signal_vadjustment_changed();
13249 public:
13250 GtkInstanceTextView(GtkTextView* pTextView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
13251 : GtkInstanceContainer(GTK_CONTAINER(pTextView), pBuilder, bTakeOwnership)
13252 , m_pTextView(pTextView)
13253 , m_pTextBuffer(gtk_text_view_get_buffer(pTextView))
13254 , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTextView)))
13255 , m_pFgCssProvider(nullptr)
13256 , m_nMaxTextLength(0)
13257 , m_nChangedSignalId(g_signal_connect(m_pTextBuffer, "changed", G_CALLBACK(signalChanged), this))
13258 , m_nInsertTextSignalId(g_signal_connect_after(m_pTextBuffer, "insert-text", G_CALLBACK(signalInserText), this))
13259 , m_nCursorPosSignalId(g_signal_connect(m_pTextBuffer, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
13260 , m_nHasSelectionSignalId(g_signal_connect(m_pTextBuffer, "notify::has-selection", G_CALLBACK(signalHasSelection), this))
13261 , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustValueChanged), this))
13262 , m_nButtonPressEvent(g_signal_connect_after(m_pTextView, "button-press-event", G_CALLBACK(signalButtonPressEvent), this))
13266 virtual void set_size_request(int nWidth, int nHeight) override
13268 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
13269 if (GTK_IS_SCROLLED_WINDOW(pParent))
13271 gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(pParent), nWidth);
13272 gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(pParent), nHeight);
13273 return;
13275 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
13278 virtual void set_text(const OUString& rText) override
13280 disable_notify_events();
13281 OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
13282 gtk_text_buffer_set_text(m_pTextBuffer, sText.getStr(), sText.getLength());
13283 enable_notify_events();
13286 virtual OUString get_text() const override
13288 GtkTextIter start, end;
13289 gtk_text_buffer_get_bounds(m_pTextBuffer, &start, &end);
13290 char* pStr = gtk_text_buffer_get_text(m_pTextBuffer, &start, &end, true);
13291 OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
13292 g_free(pStr);
13293 return sRet;
13296 virtual void replace_selection(const OUString& rText) override
13298 disable_notify_events();
13299 gtk_text_buffer_delete_selection(m_pTextBuffer, false, gtk_text_view_get_editable(m_pTextView));
13300 OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
13301 gtk_text_buffer_insert_at_cursor(m_pTextBuffer, sText.getStr(), sText.getLength());
13302 enable_notify_events();
13305 virtual bool get_selection_bounds(int& rStartPos, int& rEndPos) override
13307 GtkTextIter start, end;
13308 gtk_text_buffer_get_selection_bounds(m_pTextBuffer, &start, &end);
13309 rStartPos = gtk_text_iter_get_offset(&start);
13310 rEndPos = gtk_text_iter_get_offset(&end);
13311 return rStartPos != rEndPos;
13314 virtual void select_region(int nStartPos, int nEndPos) override
13316 disable_notify_events();
13317 GtkTextIter start, end;
13318 gtk_text_buffer_get_iter_at_offset(m_pTextBuffer, &start, nStartPos);
13319 gtk_text_buffer_get_iter_at_offset(m_pTextBuffer, &end, nEndPos);
13320 gtk_text_buffer_select_range(m_pTextBuffer, &start, &end);
13321 GtkTextMark* mark = gtk_text_buffer_create_mark(m_pTextBuffer, "scroll", &end, true);
13322 gtk_text_view_scroll_mark_onscreen(m_pTextView, mark);
13323 enable_notify_events();
13326 virtual void set_editable(bool bEditable) override
13328 gtk_text_view_set_editable(m_pTextView, bEditable);
13331 virtual bool get_editable() const override
13333 return gtk_text_view_get_editable(m_pTextView);
13336 virtual void set_max_length(int nChars) override
13338 m_nMaxTextLength = nChars;
13341 virtual void set_monospace(bool bMonospace) override
13343 gtk_text_view_set_monospace(m_pTextView, bMonospace);
13346 virtual void set_font_color(const Color& rColor) override
13348 const bool bRemoveColor = rColor == COL_AUTO;
13349 if (bRemoveColor && !m_pFgCssProvider)
13350 return;
13351 GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(GTK_WIDGET(m_pTextView));
13352 if (m_pFgCssProvider)
13354 gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFgCssProvider));
13355 m_pFgCssProvider = nullptr;
13357 if (bRemoveColor)
13358 return;
13359 OUString sColor = rColor.AsRGBHexString();
13360 m_pFgCssProvider = gtk_css_provider_new();
13361 OUString aBuffer = "textview text { color: #" + sColor + "; }";
13362 OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
13363 gtk_css_provider_load_from_data(m_pFgCssProvider, aResult.getStr(), aResult.getLength(), nullptr);
13364 gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFgCssProvider),
13365 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
13368 virtual void disable_notify_events() override
13370 g_signal_handler_block(m_pVAdjustment, m_nVAdjustChangedSignalId);
13371 g_signal_handler_block(m_pTextBuffer, m_nCursorPosSignalId);
13372 g_signal_handler_block(m_pTextBuffer, m_nChangedSignalId);
13373 GtkInstanceContainer::disable_notify_events();
13376 virtual void enable_notify_events() override
13378 GtkInstanceContainer::enable_notify_events();
13379 g_signal_handler_unblock(m_pTextBuffer, m_nChangedSignalId);
13380 g_signal_handler_unblock(m_pTextBuffer, m_nCursorPosSignalId);
13381 g_signal_handler_unblock(m_pVAdjustment, m_nVAdjustChangedSignalId);
13384 // in gtk, 'up' when on the first line, will jump to the start of the line
13385 // if not there already
13386 virtual bool can_move_cursor_with_up() const override
13388 GtkTextIter start, end;
13389 gtk_text_buffer_get_selection_bounds(m_pTextBuffer, &start, &end);
13390 return !gtk_text_iter_equal(&start, &end) || !gtk_text_iter_is_start(&start);
13393 // in gtk, 'down' when on the first line, will jump to the end of the line
13394 // if not there already
13395 virtual bool can_move_cursor_with_down() const override
13397 GtkTextIter start, end;
13398 gtk_text_buffer_get_selection_bounds(m_pTextBuffer, &start, &end);
13399 return !gtk_text_iter_equal(&start, &end) || !gtk_text_iter_is_end(&end);
13402 virtual void cut_clipboard() override
13404 GtkClipboard *pClipboard = gtk_widget_get_clipboard(GTK_WIDGET(m_pTextView),
13405 GDK_SELECTION_CLIPBOARD);
13406 gtk_text_buffer_cut_clipboard(m_pTextBuffer, pClipboard, get_editable());
13409 virtual void copy_clipboard() override
13411 GtkClipboard *pClipboard = gtk_widget_get_clipboard(GTK_WIDGET(m_pTextView),
13412 GDK_SELECTION_CLIPBOARD);
13413 gtk_text_buffer_copy_clipboard(m_pTextBuffer, pClipboard);
13416 virtual void paste_clipboard() override
13418 GtkClipboard *pClipboard = gtk_widget_get_clipboard(GTK_WIDGET(m_pTextView),
13419 GDK_SELECTION_CLIPBOARD);
13420 gtk_text_buffer_paste_clipboard(m_pTextBuffer, pClipboard, nullptr, get_editable());
13423 virtual void set_alignment(TxtAlign eXAlign) override
13425 GtkJustification eJust = GTK_JUSTIFY_LEFT;
13426 switch (eXAlign)
13428 case TxtAlign::Left:
13429 eJust = GTK_JUSTIFY_LEFT;
13430 break;
13431 case TxtAlign::Center:
13432 eJust = GTK_JUSTIFY_CENTER;
13433 break;
13434 case TxtAlign::Right:
13435 eJust = GTK_JUSTIFY_RIGHT;
13436 break;
13438 gtk_text_view_set_justification(m_pTextView, eJust);
13441 virtual int vadjustment_get_value() const override
13443 return gtk_adjustment_get_value(m_pVAdjustment);
13446 virtual void vadjustment_set_value(int value) override
13448 disable_notify_events();
13449 gtk_adjustment_set_value(m_pVAdjustment, value);
13450 enable_notify_events();
13453 virtual int vadjustment_get_upper() const override
13455 return gtk_adjustment_get_upper(m_pVAdjustment);
13458 virtual int vadjustment_get_lower() const override
13460 return gtk_adjustment_get_lower(m_pVAdjustment);
13463 virtual int vadjustment_get_page_size() const override
13465 return gtk_adjustment_get_page_size(m_pVAdjustment);
13468 virtual void show() override
13470 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
13471 if (GTK_IS_SCROLLED_WINDOW(pParent))
13472 gtk_widget_show(pParent);
13473 gtk_widget_show(m_pWidget);
13476 virtual void hide() override
13478 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
13479 if (GTK_IS_SCROLLED_WINDOW(pParent))
13480 gtk_widget_hide(pParent);
13481 gtk_widget_hide(m_pWidget);
13484 virtual ~GtkInstanceTextView() override
13486 g_signal_handler_disconnect(m_pTextView, m_nButtonPressEvent);
13487 g_signal_handler_disconnect(m_pVAdjustment, m_nVAdjustChangedSignalId);
13488 g_signal_handler_disconnect(m_pTextBuffer, m_nInsertTextSignalId);
13489 g_signal_handler_disconnect(m_pTextBuffer, m_nChangedSignalId);
13490 g_signal_handler_disconnect(m_pTextBuffer, m_nCursorPosSignalId);
13491 g_signal_handler_disconnect(m_pTextBuffer, m_nHasSelectionSignalId);
13495 // IMHandler
13496 class IMHandler;
13498 AtkObject* (*default_drawing_area_get_accessible)(GtkWidget *widget);
13500 class GtkInstanceDrawingArea : public GtkInstanceWidget, public virtual weld::DrawingArea
13502 private:
13503 GtkDrawingArea* m_pDrawingArea;
13504 a11yref m_xAccessible;
13505 AtkObject *m_pAccessible;
13506 ScopedVclPtrInstance<VirtualDevice> m_xDevice;
13507 std::unique_ptr<IMHandler> m_xIMHandler;
13508 cairo_surface_t* m_pSurface;
13509 gulong m_nDrawSignalId;
13510 gulong m_nQueryTooltip;
13511 gulong m_nPopupMenu;
13512 gulong m_nScrollEvent;
13514 static gboolean signalDraw(GtkWidget*, cairo_t* cr, gpointer widget)
13516 GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
13517 SolarMutexGuard aGuard;
13518 pThis->signal_draw(cr);
13519 return false;
13521 void signal_draw(cairo_t* cr)
13523 GdkRectangle rect;
13524 if (!m_pSurface || !gdk_cairo_get_clip_rectangle(cr, &rect))
13525 return;
13526 tools::Rectangle aRect(Point(rect.x, rect.y), Size(rect.width, rect.height));
13527 aRect = m_xDevice->PixelToLogic(aRect);
13528 m_xDevice->Erase(aRect);
13529 m_aDrawHdl.Call(std::pair<vcl::RenderContext&, const tools::Rectangle&>(*m_xDevice, aRect));
13530 cairo_surface_mark_dirty(m_pSurface);
13532 cairo_set_source_surface(cr, m_pSurface, 0, 0);
13533 cairo_paint(cr);
13535 tools::Rectangle aFocusRect(m_aGetFocusRectHdl.Call(*this));
13536 if (!aFocusRect.IsEmpty())
13538 gtk_render_focus(gtk_widget_get_style_context(GTK_WIDGET(m_pDrawingArea)), cr,
13539 aFocusRect.Left(), aFocusRect.Top(), aFocusRect.GetWidth(), aFocusRect.GetHeight());
13542 virtual void signal_size_allocate(guint nWidth, guint nHeight) override
13544 Size aNewSize(nWidth, nHeight);
13545 if (m_pSurface && aNewSize == m_xDevice->GetOutputSizePixel())
13547 // unchanged
13548 return;
13550 m_xDevice->SetOutputSizePixel(Size(nWidth, nHeight));
13551 m_pSurface = get_underlying_cairo_surface(*m_xDevice);
13552 GtkInstanceWidget::signal_size_allocate(nWidth, nHeight);
13554 void signal_style_updated()
13556 m_aStyleUpdatedHdl.Call(*this);
13558 static gboolean signalQueryTooltip(GtkWidget* pGtkWidget, gint x, gint y,
13559 gboolean /*keyboard_mode*/, GtkTooltip *tooltip,
13560 gpointer widget)
13562 GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
13563 tools::Rectangle aHelpArea(x, y);
13564 OUString aTooltip = pThis->signal_query_tooltip(aHelpArea);
13565 if (aTooltip.isEmpty())
13566 return false;
13567 gtk_tooltip_set_text(tooltip, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
13568 GdkRectangle aGdkHelpArea;
13569 aGdkHelpArea.x = aHelpArea.Left();
13570 aGdkHelpArea.y = aHelpArea.Top();
13571 aGdkHelpArea.width = aHelpArea.GetWidth();
13572 aGdkHelpArea.height = aHelpArea.GetHeight();
13573 if (pThis->SwapForRTL())
13574 aGdkHelpArea.x = gtk_widget_get_allocated_width(pGtkWidget) - aGdkHelpArea.width - 1 - aGdkHelpArea.x;
13575 gtk_tooltip_set_tip_area(tooltip, &aGdkHelpArea);
13576 return true;
13578 virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
13580 return signal_command(rCEvt);
13582 bool signal_scroll(const GdkEventScroll* pEvent)
13584 SalWheelMouseEvent aEvt(GtkSalFrame::GetWheelEvent(*pEvent));
13586 if (SwapForRTL())
13587 aEvt.mnX = gtk_widget_get_allocated_width(m_pWidget) - 1 - aEvt.mnX;
13589 CommandWheelMode nMode;
13590 sal_uInt16 nCode = aEvt.mnCode;
13591 bool bHorz = aEvt.mbHorz;
13592 if (nCode & KEY_MOD1)
13593 nMode = CommandWheelMode::ZOOM;
13594 else if (nCode & KEY_MOD2)
13595 nMode = CommandWheelMode::DATAZOOM;
13596 else
13598 nMode = CommandWheelMode::SCROLL;
13599 // #i85450# interpret shift-wheel as horizontal wheel action
13600 if( (nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)) == KEY_SHIFT )
13601 bHorz = true;
13604 CommandWheelData aWheelData(aEvt.mnDelta, aEvt.mnNotchDelta, aEvt.mnScrollLines,
13605 nMode, nCode, bHorz, aEvt.mbDeltaIsPixel);
13606 CommandEvent aCEvt(Point(aEvt.mnX, aEvt.mnY), CommandEventId::Wheel, true, &aWheelData);
13607 return m_aCommandHdl.Call(aCEvt);
13609 static gboolean signalScroll(GtkWidget*, GdkEventScroll* pEvent, gpointer widget)
13611 GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
13612 return pThis->signal_scroll(pEvent);
13614 DECL_LINK(SettingsChangedHdl, VclWindowEvent&, void);
13615 public:
13616 GtkInstanceDrawingArea(GtkDrawingArea* pDrawingArea, GtkInstanceBuilder* pBuilder, const a11yref& rA11y, bool bTakeOwnership)
13617 : GtkInstanceWidget(GTK_WIDGET(pDrawingArea), pBuilder, bTakeOwnership)
13618 , m_pDrawingArea(pDrawingArea)
13619 , m_xAccessible(rA11y)
13620 , m_pAccessible(nullptr)
13621 , m_xDevice(DeviceFormat::DEFAULT)
13622 , m_pSurface(nullptr)
13623 , m_nDrawSignalId(g_signal_connect(m_pDrawingArea, "draw", G_CALLBACK(signalDraw), this))
13624 , m_nQueryTooltip(g_signal_connect(m_pDrawingArea, "query-tooltip", G_CALLBACK(signalQueryTooltip), this))
13625 , m_nPopupMenu(g_signal_connect(m_pDrawingArea, "popup-menu", G_CALLBACK(signalPopupMenu), this))
13626 , m_nScrollEvent(g_signal_connect(m_pDrawingArea, "scroll-event", G_CALLBACK(signalScroll), this))
13628 gtk_widget_set_has_tooltip(m_pWidget, true);
13629 g_object_set_data(G_OBJECT(m_pDrawingArea), "g-lo-GtkInstanceDrawingArea", this);
13630 m_xDevice->EnableRTL(get_direction());
13632 ImplGetDefaultWindow()->AddEventListener(LINK(this, GtkInstanceDrawingArea, SettingsChangedHdl));
13635 AtkObject* GetAtkObject(AtkObject* pDefaultAccessible)
13637 if (!m_pAccessible && m_xAccessible.is())
13639 GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
13640 m_pAccessible = atk_object_wrapper_new(m_xAccessible, gtk_widget_get_accessible(pParent), pDefaultAccessible);
13641 if (m_pAccessible)
13642 g_object_ref(m_pAccessible);
13644 return m_pAccessible;
13647 virtual void connect_mouse_press(const Link<const MouseEvent&, bool>& rLink) override
13649 if (!(gtk_widget_get_events(m_pWidget) & GDK_BUTTON_PRESS_MASK))
13650 gtk_widget_add_events(m_pWidget, GDK_BUTTON_PRESS_MASK);
13651 GtkInstanceWidget::connect_mouse_press(rLink);
13654 virtual void connect_mouse_release(const Link<const MouseEvent&, bool>& rLink) override
13656 if (!(gtk_widget_get_events(m_pWidget) & GDK_BUTTON_RELEASE_MASK))
13657 gtk_widget_add_events(m_pWidget, GDK_BUTTON_RELEASE_MASK);
13658 GtkInstanceWidget::connect_mouse_release(rLink);
13661 virtual void set_direction(bool bRTL) override
13663 GtkInstanceWidget::set_direction(bRTL);
13664 m_xDevice->EnableRTL(bRTL);
13667 virtual void set_cursor(PointerStyle ePointerStyle) override
13669 GdkCursor *pCursor = GtkSalFrame::getDisplay()->getCursor(ePointerStyle);
13670 if (!gtk_widget_get_realized(GTK_WIDGET(m_pDrawingArea)))
13671 gtk_widget_realize(GTK_WIDGET(m_pDrawingArea));
13672 gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(m_pDrawingArea)), pCursor);
13675 virtual void set_input_context(const InputContext& rInputContext) override;
13677 virtual void im_context_set_cursor_location(const tools::Rectangle& rCursorRect, int nExtTextInputWidth) override;
13679 int im_context_get_surrounding(OUString& rSurroundingText)
13681 return signal_im_context_get_surrounding(rSurroundingText);
13684 bool im_context_delete_surrounding(const Selection& rRange)
13686 return signal_im_context_delete_surrounding(rRange);
13689 virtual void queue_draw() override
13691 gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
13694 virtual void queue_draw_area(int x, int y, int width, int height) override
13696 tools::Rectangle aRect(Point(x, y), Size(width, height));
13697 aRect = m_xDevice->LogicToPixel(aRect);
13698 gtk_widget_queue_draw_area(GTK_WIDGET(m_pDrawingArea), aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight());
13701 virtual void queue_resize() override
13703 gtk_widget_queue_resize(GTK_WIDGET(m_pDrawingArea));
13706 virtual a11yref get_accessible_parent() override
13708 //get_accessible_parent should only be needed for the vcl implementation,
13709 //in the gtk impl the native AtkObject parent set via
13710 //atk_object_wrapper_new(m_xAccessible, gtk_widget_get_accessible(pParent));
13711 //should negate the need.
13712 assert(false && "get_accessible_parent should only be called on a vcl impl");
13713 return uno::Reference<css::accessibility::XAccessible>();
13716 virtual a11yrelationset get_accessible_relation_set() override
13718 //get_accessible_relation_set should only be needed for the vcl implementation,
13719 //in the gtk impl the native equivalent should negate the need.
13720 assert(false && "get_accessible_parent should only be called on a vcl impl");
13721 return uno::Reference<css::accessibility::XAccessibleRelationSet>();
13724 virtual Point get_accessible_location() override
13726 AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
13727 gint x(0), y(0);
13728 if (pAtkObject && ATK_IS_COMPONENT(pAtkObject))
13729 atk_component_get_extents(ATK_COMPONENT(pAtkObject), &x, &y, nullptr, nullptr, ATK_XY_WINDOW);
13730 return Point(x, y);
13733 virtual void set_accessible_name(const OUString& rName) override
13735 AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
13736 if (!pAtkObject)
13737 return;
13738 atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
13741 virtual OUString get_accessible_name() const override
13743 AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
13744 const char* pStr = pAtkObject ? atk_object_get_name(pAtkObject) : nullptr;
13745 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
13748 virtual OUString get_accessible_description() const override
13750 AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
13751 const char* pStr = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
13752 return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
13755 virtual void enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants) override
13757 do_enable_drag_source(rHelper, eDNDConstants);
13760 virtual bool do_signal_drag_begin(bool& rUnsetDragIcon) override
13762 rUnsetDragIcon = false;
13763 if (m_aDragBeginHdl.Call(*this))
13764 return true;
13765 return false;
13768 virtual ~GtkInstanceDrawingArea() override
13770 ImplGetDefaultWindow()->RemoveEventListener(LINK(this, GtkInstanceDrawingArea, SettingsChangedHdl));
13772 g_object_steal_data(G_OBJECT(m_pDrawingArea), "g-lo-GtkInstanceDrawingArea");
13773 if (m_pAccessible)
13774 g_object_unref(m_pAccessible);
13775 css::uno::Reference<css::lang::XComponent> xComp(m_xAccessible, css::uno::UNO_QUERY);
13776 if (xComp.is())
13777 xComp->dispose();
13778 g_signal_handler_disconnect(m_pDrawingArea, m_nScrollEvent);
13779 g_signal_handler_disconnect(m_pDrawingArea, m_nPopupMenu);
13780 g_signal_handler_disconnect(m_pDrawingArea, m_nQueryTooltip);
13781 g_signal_handler_disconnect(m_pDrawingArea, m_nDrawSignalId);
13784 virtual OutputDevice& get_ref_device() override
13786 return *m_xDevice;
13789 bool signal_command(const CommandEvent& rCEvt)
13791 return m_aCommandHdl.Call(rCEvt);
13794 virtual void click(const Point& rPos) override
13796 MouseEvent aEvent(rPos);
13797 m_aMousePressHdl.Call(aEvent);
13798 m_aMouseReleaseHdl.Call(aEvent);
13802 IMPL_LINK(GtkInstanceDrawingArea, SettingsChangedHdl, VclWindowEvent&, rEvent, void)
13804 if (rEvent.GetId() != VclEventId::WindowDataChanged)
13805 return;
13807 DataChangedEvent* pData = static_cast<DataChangedEvent*>(rEvent.GetData());
13808 if (pData->GetType() == DataChangedEventType::SETTINGS)
13809 signal_style_updated();
13812 class IMHandler
13814 private:
13815 GtkInstanceDrawingArea* m_pArea;
13816 GtkIMContext* m_pIMContext;
13817 OUString m_sPreeditText;
13818 gulong m_nFocusInSignalId;
13819 gulong m_nFocusOutSignalId;
13820 bool m_bExtTextInput;
13822 public:
13823 IMHandler(GtkInstanceDrawingArea* pArea)
13824 : m_pArea(pArea)
13825 , m_pIMContext(gtk_im_multicontext_new())
13826 , m_nFocusInSignalId(g_signal_connect(m_pArea->getWidget(), "focus-in-event", G_CALLBACK(signalFocusIn), this))
13827 , m_nFocusOutSignalId(g_signal_connect(m_pArea->getWidget(), "focus-out-event", G_CALLBACK(signalFocusOut), this))
13828 , m_bExtTextInput(false)
13830 g_signal_connect(m_pIMContext, "preedit-start", G_CALLBACK(signalIMPreeditStart), this);
13831 g_signal_connect(m_pIMContext, "preedit-end", G_CALLBACK(signalIMPreeditEnd), this);
13832 g_signal_connect(m_pIMContext, "commit", G_CALLBACK(signalIMCommit), this);
13833 g_signal_connect(m_pIMContext, "preedit-changed", G_CALLBACK(signalIMPreeditChanged), this);
13834 g_signal_connect(m_pIMContext, "retrieve-surrounding", G_CALLBACK(signalIMRetrieveSurrounding), this);
13835 g_signal_connect(m_pIMContext, "delete-surrounding", G_CALLBACK(signalIMDeleteSurrounding), this);
13837 GtkWidget* pWidget = m_pArea->getWidget();
13838 if (!gtk_widget_get_realized(pWidget))
13839 gtk_widget_realize(pWidget);
13840 GdkWindow* pWin = gtk_widget_get_window(pWidget);
13841 gtk_im_context_set_client_window(m_pIMContext, pWin);
13842 if (gtk_widget_has_focus(m_pArea->getWidget()))
13843 gtk_im_context_focus_in(m_pIMContext);
13846 void signalFocus(bool bIn)
13848 if (bIn)
13849 gtk_im_context_focus_in(m_pIMContext);
13850 else
13851 gtk_im_context_focus_out(m_pIMContext);
13854 static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer im_handler)
13856 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
13857 pThis->signalFocus(true);
13858 return false;
13861 static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer im_handler)
13863 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
13864 pThis->signalFocus(false);
13865 return false;
13868 ~IMHandler()
13870 EndExtTextInput();
13872 g_signal_handler_disconnect(m_pArea->getWidget(), m_nFocusOutSignalId);
13873 g_signal_handler_disconnect(m_pArea->getWidget(), m_nFocusInSignalId);
13875 if (gtk_widget_has_focus(m_pArea->getWidget()))
13876 gtk_im_context_focus_out(m_pIMContext);
13878 // first give IC a chance to deinitialize
13879 gtk_im_context_set_client_window(m_pIMContext, nullptr);
13880 // destroy old IC
13881 g_object_unref(m_pIMContext);
13884 void updateIMSpotLocation()
13886 CommandEvent aCEvt(Point(), CommandEventId::CursorPos);
13887 // we expect set_cursor_location to get triggered by this
13888 m_pArea->signal_command(aCEvt);
13891 void set_cursor_location(const tools::Rectangle& rRect)
13893 GdkRectangle aArea{static_cast<int>(rRect.Left()), static_cast<int>(rRect.Top()),
13894 static_cast<int>(rRect.GetWidth()), static_cast<int>(rRect.GetHeight())};
13895 gtk_im_context_set_cursor_location(m_pIMContext, &aArea);
13898 static void signalIMCommit(GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler)
13900 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
13902 SolarMutexGuard aGuard;
13904 // at least editeng expects to have seen a start before accepting a commit
13905 pThis->StartExtTextInput();
13907 OUString sText(pText, strlen(pText), RTL_TEXTENCODING_UTF8);
13908 CommandExtTextInputData aData(sText, nullptr, sText.getLength(), 0, false);
13909 CommandEvent aCEvt(Point(), CommandEventId::ExtTextInput, false, &aData);
13910 pThis->m_pArea->signal_command(aCEvt);
13912 pThis->updateIMSpotLocation();
13914 pThis->EndExtTextInput();
13916 pThis->m_sPreeditText.clear();
13919 static void signalIMPreeditChanged(GtkIMContext* pIMContext, gpointer im_handler)
13921 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
13923 SolarMutexGuard aGuard;
13925 sal_Int32 nCursorPos(0);
13926 sal_uInt8 nCursorFlags(0);
13927 std::vector<ExtTextInputAttr> aInputFlags;
13928 OUString sText = GtkSalFrame::GetPreeditDetails(pIMContext, aInputFlags, nCursorPos, nCursorFlags);
13930 // change from nothing to nothing -> do not start preedit e.g. this
13931 // will activate input into a calc cell without user input
13932 if (sText.isEmpty() && pThis->m_sPreeditText.isEmpty())
13933 return;
13935 pThis->m_sPreeditText = sText;
13937 CommandExtTextInputData aData(sText, aInputFlags.data(), nCursorPos, nCursorFlags, false);
13938 CommandEvent aCEvt(Point(), CommandEventId::ExtTextInput, false, &aData);
13939 pThis->m_pArea->signal_command(aCEvt);
13941 pThis->updateIMSpotLocation();
13944 static gboolean signalIMRetrieveSurrounding(GtkIMContext* pContext, gpointer im_handler)
13946 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
13948 SolarMutexGuard aGuard;
13950 OUString sSurroundingText;
13951 int nCursorIndex = pThis->m_pArea->im_context_get_surrounding(sSurroundingText);
13953 if (nCursorIndex != -1)
13955 OString sUTF = OUStringToOString(sSurroundingText, RTL_TEXTENCODING_UTF8);
13956 OUString sCursorText(sSurroundingText.copy(0, nCursorIndex));
13957 gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
13958 OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
13961 return true;
13964 static gboolean signalIMDeleteSurrounding(GtkIMContext*, gint nOffset, gint nChars,
13965 gpointer im_handler)
13967 bool bRet = false;
13969 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
13971 SolarMutexGuard aGuard;
13973 OUString sSurroundingText;
13974 sal_Int32 nCursorIndex = pThis->m_pArea->im_context_get_surrounding(sSurroundingText);
13976 Selection aSelection = GtkSalFrame::CalcDeleteSurroundingSelection(sSurroundingText, nCursorIndex, nOffset, nChars);
13977 if (aSelection != Selection(SAL_MAX_UINT32, SAL_MAX_UINT32))
13978 bRet = pThis->m_pArea->im_context_delete_surrounding(aSelection);
13979 return bRet;
13982 void StartExtTextInput()
13984 if (m_bExtTextInput)
13985 return;
13986 CommandEvent aCEvt(Point(), CommandEventId::StartExtTextInput);
13987 m_pArea->signal_command(aCEvt);
13988 m_bExtTextInput = true;
13991 static void signalIMPreeditStart(GtkIMContext*, gpointer im_handler)
13993 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
13994 SolarMutexGuard aGuard;
13995 pThis->StartExtTextInput();
13996 pThis->updateIMSpotLocation();
13999 void EndExtTextInput()
14001 if (!m_bExtTextInput)
14002 return;
14003 CommandEvent aCEvt(Point(), CommandEventId::EndExtTextInput);
14004 m_pArea->signal_command(aCEvt);
14005 m_bExtTextInput = false;
14008 static void signalIMPreeditEnd(GtkIMContext*, gpointer im_handler)
14010 IMHandler* pThis = static_cast<IMHandler*>(im_handler);
14011 SolarMutexGuard aGuard;
14012 pThis->updateIMSpotLocation();
14013 pThis->EndExtTextInput();
14017 void GtkInstanceDrawingArea::set_input_context(const InputContext& rInputContext)
14019 bool bUseIm(rInputContext.GetOptions() & InputContextFlags::Text);
14020 if (!bUseIm)
14022 m_xIMHandler.reset();
14023 return;
14025 // create a new im context
14026 if (!m_xIMHandler)
14027 m_xIMHandler.reset(new IMHandler(this));
14030 void GtkInstanceDrawingArea::im_context_set_cursor_location(const tools::Rectangle& rCursorRect, int /*nExtTextInputWidth*/)
14032 if (!m_xIMHandler)
14033 return;
14034 m_xIMHandler->set_cursor_location(rCursorRect);
14039 namespace {
14041 GtkBuilder* makeMenuToggleButtonBuilder()
14043 OUString aUri(AllSettings::GetUIRootDir() + "vcl/ui/menutogglebutton.ui");
14044 OUString aPath;
14045 osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
14046 return gtk_builder_new_from_file(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr());
14049 GtkBuilder* makeComboBoxBuilder()
14051 OUString aUri(AllSettings::GetUIRootDir() + "vcl/ui/combobox.ui");
14052 OUString aPath;
14053 osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
14054 return gtk_builder_new_from_file(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr());
14057 // pop down the toplevel combobox menu when something is activated from a custom
14058 // submenu, i.e. wysiwyg style menu
14059 class CustomRenderMenuButtonHelper : public MenuHelper
14061 private:
14062 GtkToggleButton* m_pComboBox;
14063 public:
14064 CustomRenderMenuButtonHelper(GtkMenu* pMenu, GtkToggleButton* pComboBox)
14065 : MenuHelper(pMenu, false)
14066 , m_pComboBox(pComboBox)
14069 virtual void signal_activate(GtkMenuItem*) override
14071 gtk_toggle_button_set_active(m_pComboBox, false);
14075 class GtkInstanceComboBox : public GtkInstanceContainer, public vcl::ISearchableStringList, public virtual weld::ComboBox
14077 private:
14078 GtkBuilder* m_pComboBuilder;
14079 GtkComboBox* m_pComboBox;
14080 GtkOverlay* m_pOverlay;
14081 GtkTreeView* m_pTreeView;
14082 GtkMenuButton* m_pOverlayButton; // button that the StyleDropdown uses on an active row
14083 GtkWindow* m_pMenuWindow;
14084 GtkTreeModel* m_pTreeModel;
14085 GtkCellRenderer* m_pButtonTextRenderer;
14086 GtkCellRenderer* m_pMenuTextRenderer;
14087 GtkWidget* m_pToggleButton;
14088 GtkWidget* m_pEntry;
14089 GtkCellView* m_pCellView;
14090 std::unique_ptr<CustomRenderMenuButtonHelper> m_xCustomMenuButtonHelper;
14091 std::unique_ptr<vcl::Font> m_xFont;
14092 std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
14093 vcl::QuickSelectionEngine m_aQuickSelectionEngine;
14094 std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>> m_aSeparatorRows;
14095 OUString m_sMenuButtonRow;
14096 bool m_bHoverSelection;
14097 bool m_bMouseInOverlayButton;
14098 bool m_bPopupActive;
14099 bool m_bAutoComplete;
14100 bool m_bAutoCompleteCaseSensitive;
14101 bool m_bChangedByMenu;
14102 bool m_bCustomRenderer;
14103 bool m_bActivateCalled;
14104 gint m_nTextCol;
14105 gint m_nIdCol;
14106 gulong m_nToggleFocusInSignalId;
14107 gulong m_nToggleFocusOutSignalId;
14108 gulong m_nRowActivatedSignalId;
14109 gulong m_nChangedSignalId;
14110 gulong m_nPopupShownSignalId;
14111 gulong m_nKeyPressEventSignalId;
14112 gulong m_nEntryInsertTextSignalId;
14113 gulong m_nEntryActivateSignalId;
14114 gulong m_nEntryFocusInSignalId;
14115 gulong m_nEntryFocusOutSignalId;
14116 gulong m_nEntryKeyPressEventSignalId;
14117 guint m_nAutoCompleteIdleId;
14118 gint m_nNonCustomLineHeight;
14119 gint m_nPrePopupCursorPos;
14120 int m_nMRUCount;
14121 int m_nMaxMRUCount;
14123 static gboolean idleAutoComplete(gpointer widget)
14125 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
14126 pThis->auto_complete();
14127 return false;
14130 void auto_complete()
14132 m_nAutoCompleteIdleId = 0;
14133 OUString aStartText = get_active_text();
14134 int nStartPos, nEndPos;
14135 get_entry_selection_bounds(nStartPos, nEndPos);
14136 int nMaxSelection = std::max(nStartPos, nEndPos);
14137 if (nMaxSelection != aStartText.getLength())
14138 return;
14140 disable_notify_events();
14141 int nActive = get_active();
14142 int nStart = nActive;
14144 if (nStart == -1)
14145 nStart = 0;
14147 int nPos = -1;
14149 int nZeroRow = 0;
14150 if (m_nMRUCount)
14151 nZeroRow += (m_nMRUCount + 1);
14153 if (!m_bAutoCompleteCaseSensitive)
14155 // Try match case insensitive from current position
14156 nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, false);
14157 if (nPos == -1 && nStart != 0)
14159 // Try match case insensitive, but from start
14160 nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, false);
14164 if (nPos == -1)
14166 // Try match case sensitive from current position
14167 nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, true);
14168 if (nPos == -1 && nStart != 0)
14170 // Try match case sensitive, but from start
14171 nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, true);
14175 if (nPos != -1)
14177 OUString aText = get_text_including_mru(nPos);
14178 if (aText != aStartText)
14180 SolarMutexGuard aGuard;
14181 set_active_including_mru(nPos, true);
14183 select_entry_region(aText.getLength(), aStartText.getLength());
14185 enable_notify_events();
14188 static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
14189 gint* position, gpointer widget)
14191 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
14192 SolarMutexGuard aGuard;
14193 pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
14196 void signal_entry_insert_text(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength, gint* position)
14198 // first filter inserted text
14199 if (m_aEntryInsertTextHdl.IsSet())
14201 OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
14202 const bool bContinue = m_aEntryInsertTextHdl.Call(sText);
14203 if (bContinue && !sText.isEmpty())
14205 OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
14206 g_signal_handlers_block_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
14207 gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position);
14208 g_signal_handlers_unblock_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
14210 g_signal_stop_emission_by_name(pEntry, "insert-text");
14212 if (m_bAutoComplete)
14214 // now check for autocompletes
14215 if (m_nAutoCompleteIdleId)
14216 g_source_remove(m_nAutoCompleteIdleId);
14217 m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
14221 static void signalChanged(GtkEntry*, gpointer widget)
14223 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
14224 SolarMutexGuard aGuard;
14225 pThis->fire_signal_changed();
14228 void fire_signal_changed()
14230 signal_changed();
14231 m_bChangedByMenu = false;
14234 static void signalPopupToggled(GtkToggleButton* /*pToggleButton*/, gpointer widget)
14236 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
14237 pThis->signal_popup_toggled();
14240 int get_popup_height(gint& rPopupWidth)
14242 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
14244 int nMaxRows = rSettings.GetListBoxMaximumLineCount();
14245 bool bAddScrollWidth = false;
14246 int nRows = get_count_including_mru();
14247 if (nMaxRows < nRows)
14249 nRows = nMaxRows;
14250 bAddScrollWidth = true;
14253 GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
14254 gint nRowHeight = get_height_row(m_pTreeView, pColumns);
14255 g_list_free(pColumns);
14257 gint nSeparatorHeight = get_height_row_separator(m_pTreeView);
14258 gint nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nRows);
14260 // if we're using a custom renderer, limit the height to the height nMaxRows would be
14261 // for a normal renderer, and then round down to how many custom rows fit in that
14262 // space
14263 if (m_nNonCustomLineHeight != -1 && nRowHeight)
14265 gint nNormalHeight = get_height_rows(m_nNonCustomLineHeight, nSeparatorHeight, nMaxRows);
14266 if (nHeight > nNormalHeight)
14268 gint nRowsOnly = nNormalHeight - get_height_rows(0, nSeparatorHeight, nMaxRows);
14269 gint nCustomRows = (nRowsOnly + (nRowHeight - 1)) / nRowHeight;
14270 nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nCustomRows);
14274 if (bAddScrollWidth)
14275 rPopupWidth += rSettings.GetScrollBarSize();
14277 return nHeight;
14280 void toggle_menu()
14282 if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton)))
14284 if (m_bHoverSelection)
14286 // turn hover selection back off until mouse is moved again
14287 // *after* menu is shown again
14288 gtk_tree_view_set_hover_selection(m_pTreeView, false);
14289 m_bHoverSelection = false;
14292 do_ungrab(GTK_WIDGET(m_pMenuWindow));
14294 gtk_widget_hide(GTK_WIDGET(m_pMenuWindow));
14296 // so gdk_window_move_to_rect will work again the next time
14297 gtk_widget_unrealize(GTK_WIDGET(m_pMenuWindow));
14299 gtk_widget_set_size_request(GTK_WIDGET(m_pMenuWindow), -1, -1);
14301 if (!m_bActivateCalled)
14302 tree_view_set_cursor(m_nPrePopupCursorPos);
14304 // undo show_menu tooltip blocking
14305 GtkWidget* pParent = gtk_widget_get_toplevel(m_pToggleButton);
14306 GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
14307 if (pFrame)
14308 pFrame->UnblockTooltip();
14310 else
14312 GtkWidget* pComboBox = GTK_WIDGET(getContainer());
14314 gint nComboWidth = gtk_widget_get_allocated_width(pComboBox);
14315 GtkRequisition size;
14316 gtk_widget_get_preferred_size(GTK_WIDGET(m_pMenuWindow), nullptr, &size);
14318 gint nPopupWidth = size.width;
14319 gint nPopupHeight = get_popup_height(nPopupWidth);
14320 nPopupWidth = std::max(nPopupWidth, nComboWidth);
14322 gtk_widget_set_size_request(GTK_WIDGET(m_pMenuWindow), nPopupWidth, nPopupHeight);
14324 m_nPrePopupCursorPos = get_active();
14326 m_bActivateCalled = false;
14328 // if we are in mru mode always start with the cursor at the top of the menu
14329 if (m_nMaxMRUCount)
14330 tree_view_set_cursor(0);
14332 show_menu(pComboBox, m_pMenuWindow);
14336 virtual void signal_popup_toggled() override
14338 m_aQuickSelectionEngine.Reset();
14340 toggle_menu();
14342 bool bIsShown = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton));
14343 if (m_bPopupActive != bIsShown)
14345 m_bPopupActive = bIsShown;
14346 ComboBox::signal_popup_toggled();
14347 if (!m_bPopupActive)
14349 //restore focus to the entry view when the popup is gone, which
14350 //is what the vcl case does, to ease the transition a little
14351 grab_focus();
14356 static gboolean signalEntryFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
14358 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
14359 pThis->signal_entry_focus_in();
14360 return false;
14363 void signal_entry_focus_in()
14365 signal_focus_in();
14368 static gboolean signalEntryFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
14370 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
14371 pThis->signal_entry_focus_out();
14372 return false;
14375 void signal_entry_focus_out()
14377 // if we have an untidy selection on losing focus remove the selection
14378 int nStartPos, nEndPos;
14379 if (get_entry_selection_bounds(nStartPos, nEndPos))
14381 int nMin = std::min(nStartPos, nEndPos);
14382 int nMax = std::max(nStartPos, nEndPos);
14383 if (nMin != 0 || nMax != get_active_text().getLength())
14384 select_entry_region(0, 0);
14386 signal_focus_out();
14389 static void signalEntryActivate(GtkEntry*, gpointer widget)
14391 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
14392 pThis->signal_entry_activate();
14395 void signal_entry_activate()
14397 if (m_aEntryActivateHdl.IsSet())
14399 SolarMutexGuard aGuard;
14400 if (m_aEntryActivateHdl.Call(*this))
14401 g_signal_stop_emission_by_name(m_pEntry, "activate");
14403 update_mru();
14406 OUString get(int pos, int col) const
14408 OUString sRet;
14409 GtkTreeIter iter;
14410 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
14412 gchar* pStr;
14413 gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
14414 sRet = OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
14415 g_free(pStr);
14417 return sRet;
14420 void set(int pos, int col, const OUString& rText)
14422 GtkTreeIter iter;
14423 if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
14425 OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
14426 gtk_list_store_set(GTK_LIST_STORE(m_pTreeModel), &iter, col, aStr.getStr(), -1);
14430 int find(const OUString& rStr, int col, bool bSearchMRUArea) const
14432 GtkTreeIter iter;
14433 if (!gtk_tree_model_get_iter_first(m_pTreeModel, &iter))
14434 return -1;
14436 int nRet = 0;
14438 if (!bSearchMRUArea && m_nMRUCount)
14440 if (!gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, m_nMRUCount + 1))
14441 return -1;
14442 nRet += (m_nMRUCount + 1);
14445 OString aStr(OUStringToOString(rStr, RTL_TEXTENCODING_UTF8).getStr());
14448 gchar* pStr;
14449 gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
14450 const bool bEqual = g_strcmp0(pStr, aStr.getStr()) == 0;
14451 g_free(pStr);
14452 if (bEqual)
14453 return nRet;
14454 ++nRet;
14455 } while (gtk_tree_model_iter_next(m_pTreeModel, &iter));
14457 return -1;
14460 bool separator_function(const GtkTreePath* path)
14462 return ::separator_function(path, m_aSeparatorRows);
14465 bool separator_function(int pos)
14467 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
14468 bool bRet = separator_function(path);
14469 gtk_tree_path_free(path);
14470 return bRet;
14473 static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget)
14475 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
14476 GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter);
14477 bool bRet = pThis->separator_function(path);
14478 gtk_tree_path_free(path);
14479 return bRet;
14482 // https://gitlab.gnome.org/GNOME/gtk/issues/310
14484 // in the absence of a built-in solution
14485 // a) support typeahead for the case where there is no entry widget, typing ahead
14486 // into the button itself will select via the vcl selection engine, a matching
14487 // entry
14488 static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
14490 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
14491 return pThis->signal_key_press(pEvent);
14494 // tdf#131076 we want return in a ComboBox to act like return in a
14495 // GtkEntry and activate the default dialog/assistant button
14496 bool combobox_activate()
14498 GtkWidget *pComboBox = GTK_WIDGET(m_pToggleButton);
14499 GtkWidget *pToplevel = gtk_widget_get_toplevel(pComboBox);
14500 GtkWindow *pWindow = GTK_WINDOW(pToplevel);
14501 if (!pWindow)
14502 return false;
14503 if (!GTK_IS_DIALOG(pWindow) && !GTK_IS_ASSISTANT(pWindow))
14504 return false;
14505 bool bDone = false;
14506 GtkWidget *pDefaultWidget = gtk_window_get_default_widget(pWindow);
14507 if (pDefaultWidget && pDefaultWidget != m_pToggleButton && gtk_widget_get_sensitive(pDefaultWidget))
14508 bDone = gtk_widget_activate(pDefaultWidget);
14509 return bDone;
14512 static gboolean signalEntryKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
14514 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
14515 return pThis->signal_entry_key_press(pEvent);
14518 bool signal_entry_key_press(const GdkEventKey* pEvent)
14520 KeyEvent aKEvt(GtkToVcl(*pEvent));
14522 vcl::KeyCode aKeyCode = aKEvt.GetKeyCode();
14524 bool bDone = false;
14526 auto nCode = aKeyCode.GetCode();
14527 switch (nCode)
14529 case KEY_DOWN:
14531 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
14532 if (!nKeyMod)
14534 int nCount = get_count_including_mru();
14535 int nActive = get_active_including_mru() + 1;
14536 while (nActive < nCount && separator_function(nActive))
14537 ++nActive;
14538 if (nActive < nCount)
14539 set_active_including_mru(nActive, true);
14540 bDone = true;
14542 else if (nKeyMod == KEY_MOD2 && !m_bPopupActive)
14544 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), true);
14545 bDone = true;
14547 break;
14549 case KEY_UP:
14551 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
14552 if (!nKeyMod)
14554 int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
14555 int nActive = get_active_including_mru() - 1;
14556 while (nActive >= nStartBound && separator_function(nActive))
14557 --nActive;
14558 if (nActive >= nStartBound)
14559 set_active_including_mru(nActive, true);
14560 bDone = true;
14562 break;
14564 case KEY_PAGEUP:
14566 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
14567 if (!nKeyMod)
14569 int nCount = get_count_including_mru();
14570 int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
14571 int nActive = nStartBound;
14572 while (nActive < nCount && separator_function(nActive))
14573 ++nActive;
14574 if (nActive < nCount)
14575 set_active_including_mru(nActive, true);
14576 bDone = true;
14578 break;
14580 case KEY_PAGEDOWN:
14582 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
14583 if (!nKeyMod)
14585 int nActive = get_count_including_mru() - 1;
14586 int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
14587 while (nActive >= nStartBound && separator_function(nActive))
14588 --nActive;
14589 if (nActive >= nStartBound)
14590 set_active_including_mru(nActive, true);
14591 bDone = true;
14593 break;
14595 default:
14596 break;
14599 return bDone;
14602 bool signal_key_press(const GdkEventKey* pEvent)
14604 if (m_bHoverSelection)
14606 // once a key is pressed, turn off hover selection until mouse is
14607 // moved again otherwise when the treeview scrolls it jumps to the
14608 // position under the mouse.
14609 gtk_tree_view_set_hover_selection(m_pTreeView, false);
14610 m_bHoverSelection = false;
14613 KeyEvent aKEvt(GtkToVcl(*pEvent));
14615 vcl::KeyCode aKeyCode = aKEvt.GetKeyCode();
14617 bool bDone = false;
14619 auto nCode = aKeyCode.GetCode();
14620 switch (nCode)
14622 case KEY_DOWN:
14623 case KEY_UP:
14624 case KEY_PAGEUP:
14625 case KEY_PAGEDOWN:
14626 case KEY_HOME:
14627 case KEY_END:
14628 case KEY_LEFT:
14629 case KEY_RIGHT:
14630 case KEY_RETURN:
14632 m_aQuickSelectionEngine.Reset();
14633 sal_uInt16 nKeyMod = aKeyCode.GetModifier();
14634 // tdf#131076 don't let bare return toggle menu popup active, but do allow deactive
14635 if (nCode == KEY_RETURN && !nKeyMod && !m_bPopupActive)
14636 bDone = combobox_activate();
14637 else if (nCode == KEY_UP && nKeyMod == KEY_MOD2 && m_bPopupActive)
14639 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
14640 bDone = true;
14642 else if (nCode == KEY_DOWN && nKeyMod == KEY_MOD2 && !m_bPopupActive)
14644 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), true);
14645 bDone = true;
14647 break;
14649 case KEY_ESCAPE:
14651 m_aQuickSelectionEngine.Reset();
14652 if (m_bPopupActive)
14654 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
14655 bDone = true;
14657 break;
14659 default:
14660 // tdf#131076 let base space toggle menu popup when it's not already visible
14661 if (nCode == KEY_SPACE && !aKeyCode.GetModifier() && !m_bPopupActive)
14662 bDone = false;
14663 else
14664 bDone = m_aQuickSelectionEngine.HandleKeyEvent(aKEvt);
14665 break;
14668 if (!bDone && !m_pEntry)
14669 bDone = signal_entry_key_press(pEvent);
14671 return bDone;
14674 vcl::StringEntryIdentifier typeahead_getEntry(int nPos, OUString& out_entryText) const
14676 int nEntryCount(get_count_including_mru());
14677 if (nPos >= nEntryCount)
14678 nPos = 0;
14679 out_entryText = get_text_including_mru(nPos);
14681 // vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based
14682 // => normalize
14683 return reinterpret_cast<vcl::StringEntryIdentifier>(nPos + 1);
14686 static int typeahead_getEntryPos(vcl::StringEntryIdentifier entry)
14688 // our pos is 0-based, but StringEntryIdentifier does not allow for a NULL
14689 return reinterpret_cast<sal_Int64>(entry) - 1;
14692 void tree_view_set_cursor(int pos)
14694 if (pos == -1)
14696 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(m_pTreeView));
14697 if (m_pCellView)
14698 gtk_cell_view_set_displayed_row(m_pCellView, nullptr);
14700 else
14702 GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
14703 if (gtk_tree_view_get_model(m_pTreeView))
14704 gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
14705 gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
14706 if (m_pCellView)
14707 gtk_cell_view_set_displayed_row(m_pCellView, path);
14708 gtk_tree_path_free(path);
14712 int tree_view_get_cursor() const
14714 int nRet = -1;
14716 GtkTreePath* path;
14717 gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
14718 if (path)
14720 gint depth;
14721 gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
14722 nRet = indices[depth-1];
14723 gtk_tree_path_free(path);
14726 return nRet;
14729 int get_selected_entry() const
14731 if (m_bPopupActive)
14732 return tree_view_get_cursor();
14733 else
14734 return get_active_including_mru();
14737 void set_typeahead_selected_entry(int nSelect)
14739 if (m_bPopupActive)
14740 tree_view_set_cursor(nSelect);
14741 else
14742 set_active_including_mru(nSelect, true);
14745 virtual vcl::StringEntryIdentifier CurrentEntry(OUString& out_entryText) const override
14747 int nCurrentPos = get_selected_entry();
14748 return typeahead_getEntry((nCurrentPos == -1) ? 0 : nCurrentPos, out_entryText);
14751 virtual vcl::StringEntryIdentifier NextEntry(vcl::StringEntryIdentifier currentEntry, OUString& out_entryText) const override
14753 int nNextPos = typeahead_getEntryPos(currentEntry) + 1;
14754 return typeahead_getEntry(nNextPos, out_entryText);
14757 virtual void SelectEntry(vcl::StringEntryIdentifier entry) override
14759 int nSelect = typeahead_getEntryPos(entry);
14760 if (nSelect == get_selected_entry())
14762 // ignore that. This method is a callback from the QuickSelectionEngine, which means the user attempted
14763 // to select the given entry by typing its starting letters. No need to act.
14764 return;
14767 // normalize
14768 int nCount = get_count_including_mru();
14769 if (nSelect >= nCount)
14770 nSelect = nCount ? nCount-1 : -1;
14772 set_typeahead_selected_entry(nSelect);
14775 static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
14777 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
14778 pThis->grab_broken(pEvent);
14781 void grab_broken(const GdkEventGrabBroken *event)
14783 if (event->grab_window == nullptr)
14785 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
14787 else
14789 //try and regrab, so when we lose the grab to the menu of the color palette
14790 //combobox we regain it so the color palette doesn't itself disappear on next
14791 //click on the color palette combobox
14792 do_grab(GTK_WIDGET(m_pMenuWindow));
14796 static gboolean signalButtonPress(GtkWidget* pWidget, GdkEventButton* pEvent, gpointer widget)
14798 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
14799 return pThis->button_press(pWidget, pEvent);
14802 bool button_press(GtkWidget* pWidget, GdkEventButton* pEvent)
14804 //we want to pop down if the button was pressed outside our popup
14805 gdouble x = pEvent->x_root;
14806 gdouble y = pEvent->y_root;
14807 gint xoffset, yoffset;
14808 gdk_window_get_root_origin(gtk_widget_get_window(pWidget), &xoffset, &yoffset);
14810 GtkAllocation alloc;
14811 gtk_widget_get_allocation(pWidget, &alloc);
14812 xoffset += alloc.x;
14813 yoffset += alloc.y;
14815 gtk_widget_get_allocation(GTK_WIDGET(m_pMenuWindow), &alloc);
14816 gint x1 = alloc.x + xoffset;
14817 gint y1 = alloc.y + yoffset;
14818 gint x2 = x1 + alloc.width;
14819 gint y2 = y1 + alloc.height;
14821 if (x > x1 && x < x2 && y > y1 && y < y2)
14822 return false;
14824 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
14826 return false;
14829 static gboolean signalMotion(GtkWidget*, GdkEventMotion*, gpointer widget)
14831 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
14832 pThis->signal_motion();
14833 return false;
14836 void signal_motion()
14838 // if hover-selection was disabled after pressing a key, then turn it back on again
14839 if (!m_bHoverSelection && !m_bMouseInOverlayButton)
14841 gtk_tree_view_set_hover_selection(m_pTreeView, true);
14842 m_bHoverSelection = true;
14846 static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget)
14848 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
14849 pThis->handle_row_activated();
14852 void handle_row_activated()
14854 m_bActivateCalled = true;
14855 m_bChangedByMenu = true;
14856 disable_notify_events();
14857 int nActive = get_active();
14858 if (m_pEntry)
14859 gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(get_text(nActive), RTL_TEXTENCODING_UTF8).getStr());
14860 else
14861 tree_view_set_cursor(nActive);
14862 enable_notify_events();
14863 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
14864 fire_signal_changed();
14865 update_mru();
14868 void do_clear()
14870 disable_notify_events();
14871 gtk_tree_view_set_row_separator_func(m_pTreeView, nullptr, nullptr, nullptr);
14872 m_aSeparatorRows.clear();
14873 gtk_list_store_clear(GTK_LIST_STORE(m_pTreeModel));
14874 m_nMRUCount = 0;
14875 enable_notify_events();
14878 virtual int get_max_mru_count() const override
14880 return m_nMaxMRUCount;
14883 virtual void set_max_mru_count(int nMaxMRUCount) override
14885 m_nMaxMRUCount = nMaxMRUCount;
14886 update_mru();
14889 void update_mru()
14891 int nMRUCount = m_nMRUCount;
14893 if (m_nMaxMRUCount)
14895 OUString sActiveText = get_active_text();
14896 OUString sActiveId = get_active_id();
14897 insert_including_mru(0, sActiveText, &sActiveId, nullptr, nullptr);
14898 ++m_nMRUCount;
14900 for (int i = 1; i < m_nMRUCount - 1; ++i)
14902 if (get_text_including_mru(i) == sActiveText)
14904 remove_including_mru(i);
14905 --m_nMRUCount;
14906 break;
14911 while (m_nMRUCount > m_nMaxMRUCount)
14913 remove_including_mru(m_nMRUCount - 1);
14914 --m_nMRUCount;
14917 if (m_nMRUCount && !nMRUCount)
14918 insert_separator_including_mru(m_nMRUCount, "separator");
14919 else if (!m_nMRUCount && nMRUCount)
14920 remove_including_mru(m_nMRUCount); // remove separator
14923 int get_count_including_mru() const
14925 return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr);
14928 int get_active_including_mru() const
14930 return tree_view_get_cursor();
14933 void set_active_including_mru(int pos, bool bInteractive)
14935 disable_notify_events();
14937 tree_view_set_cursor(pos);
14939 if (m_pEntry)
14941 if (pos != -1)
14942 gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(get_text_including_mru(pos), RTL_TEXTENCODING_UTF8).getStr());
14943 else
14944 gtk_entry_set_text(GTK_ENTRY(m_pEntry), "");
14947 m_bChangedByMenu = false;
14948 enable_notify_events();
14950 if (bInteractive && !m_bPopupActive)
14951 signal_changed();
14954 int find_text_including_mru(const OUString& rStr, bool bSearchMRU) const
14956 return find(rStr, m_nTextCol, bSearchMRU);
14959 int find_id_including_mru(const OUString& rId, bool bSearchMRU) const
14961 return find(rId, m_nIdCol, bSearchMRU);
14964 OUString get_text_including_mru(int pos) const
14966 return get(pos, m_nTextCol);
14969 OUString get_id_including_mru(int pos) const
14971 return get(pos, m_nIdCol);
14974 void set_id_including_mru(int pos, const OUString& rId)
14976 set(pos, m_nIdCol, rId);
14979 void remove_including_mru(int pos)
14981 disable_notify_events();
14982 GtkTreeIter iter;
14983 gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
14984 if (!m_aSeparatorRows.empty())
14986 bool bFound = false;
14988 GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
14990 for (auto aIter = m_aSeparatorRows.begin(); aIter != m_aSeparatorRows.end(); ++aIter)
14992 GtkTreePath* seppath = gtk_tree_row_reference_get_path(aIter->get());
14993 if (seppath)
14995 if (gtk_tree_path_compare(pPath, seppath) == 0)
14996 bFound = true;
14997 gtk_tree_path_free(seppath);
14999 if (bFound)
15001 m_aSeparatorRows.erase(aIter);
15002 break;
15006 gtk_tree_path_free(pPath);
15008 gtk_list_store_remove(GTK_LIST_STORE(m_pTreeModel), &iter);
15009 enable_notify_events();
15012 void insert_separator_including_mru(int pos, const OUString& rId)
15014 disable_notify_events();
15015 GtkTreeIter iter;
15016 if (!gtk_tree_view_get_row_separator_func(m_pTreeView))
15017 gtk_tree_view_set_row_separator_func(m_pTreeView, separatorFunction, this, nullptr);
15018 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, &rId, "", nullptr, nullptr);
15019 GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
15020 m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(m_pTreeModel, pPath));
15021 gtk_tree_path_free(pPath);
15022 enable_notify_events();
15025 void insert_including_mru(int pos, const OUString& rText, const OUString* pId, const OUString* pIconName, const VirtualDevice* pImageSurface)
15027 disable_notify_events();
15028 GtkTreeIter iter;
15029 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, pId, rText, pIconName, pImageSurface);
15030 enable_notify_events();
15033 static gboolean signalGetChildPosition(GtkOverlay*, GtkWidget*, GdkRectangle* pAllocation, gpointer widget)
15035 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
15036 return pThis->signal_get_child_position(pAllocation);
15039 bool signal_get_child_position(GdkRectangle* pAllocation)
15041 if (!gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton)))
15042 return false;
15043 if (!gtk_widget_get_realized(GTK_WIDGET(m_pTreeView)))
15044 return false;
15045 int nRow = find_id_including_mru(m_sMenuButtonRow, true);
15046 if (nRow == -1)
15047 return false;
15049 gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &pAllocation->width, nullptr);
15051 GtkTreePath* pPath = gtk_tree_path_new_from_indices(nRow, -1);
15052 GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
15053 tools::Rectangle aRect = get_row_area(m_pTreeView, pColumns, pPath);
15054 gtk_tree_path_free(pPath);
15055 g_list_free(pColumns);
15057 pAllocation->x = aRect.Right() - pAllocation->width;
15058 pAllocation->y = aRect.Top();
15059 pAllocation->height = aRect.GetHeight();
15061 return true;
15064 static gboolean signalOverlayButtonCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
15066 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
15067 pThis->signal_overlay_button_crossing(pEvent->type == GDK_ENTER_NOTIFY);
15068 return false;
15071 void signal_overlay_button_crossing(bool bEnter)
15073 m_bMouseInOverlayButton = bEnter;
15074 if (!bEnter)
15075 return;
15077 if (m_bHoverSelection)
15079 // once toggled button is pressed, turn off hover selection until
15080 // mouse leaves the overlay button
15081 gtk_tree_view_set_hover_selection(m_pTreeView, false);
15082 m_bHoverSelection = false;
15084 int nRow = find_id_including_mru(m_sMenuButtonRow, true);
15085 assert(nRow != -1);
15086 tree_view_set_cursor(nRow); // select the buttons row
15089 void signal_combo_mnemonic_activate()
15091 if (m_pEntry)
15092 gtk_widget_grab_focus(m_pEntry);
15093 else
15094 gtk_widget_grab_focus(m_pToggleButton);
15097 static gboolean signalComboMnemonicActivate(GtkWidget*, gboolean, gpointer widget)
15099 GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
15100 pThis->signal_combo_mnemonic_activate();
15101 return true;
15104 int include_mru(int pos)
15106 if (m_nMRUCount && pos != -1)
15107 pos += (m_nMRUCount + 1);
15108 return pos;
15111 public:
15112 GtkInstanceComboBox(GtkBuilder* pComboBuilder, GtkComboBox* pComboBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
15113 : GtkInstanceContainer(GTK_CONTAINER(gtk_builder_get_object(pComboBuilder, "box")), pBuilder, bTakeOwnership)
15114 , m_pComboBuilder(pComboBuilder)
15115 , m_pComboBox(pComboBox)
15116 , m_pOverlay(GTK_OVERLAY(gtk_builder_get_object(pComboBuilder, "overlay")))
15117 , m_pTreeView(GTK_TREE_VIEW(gtk_builder_get_object(pComboBuilder, "treeview")))
15118 , m_pOverlayButton(GTK_MENU_BUTTON(gtk_builder_get_object(pComboBuilder, "overlaybutton")))
15119 , m_pMenuWindow(GTK_WINDOW(gtk_builder_get_object(pComboBuilder, "popup")))
15120 , m_pTreeModel(gtk_combo_box_get_model(pComboBox))
15121 , m_pButtonTextRenderer(nullptr)
15122 , m_pToggleButton(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "button")))
15123 , m_pEntry(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "entry")))
15124 , m_pCellView(nullptr)
15125 , m_aQuickSelectionEngine(*this)
15126 , m_bHoverSelection(false)
15127 , m_bMouseInOverlayButton(false)
15128 , m_bPopupActive(false)
15129 , m_bAutoComplete(false)
15130 , m_bAutoCompleteCaseSensitive(false)
15131 , m_bChangedByMenu(false)
15132 , m_bCustomRenderer(false)
15133 , m_bActivateCalled(false)
15134 , m_nTextCol(gtk_combo_box_get_entry_text_column(pComboBox))
15135 , m_nIdCol(gtk_combo_box_get_id_column(pComboBox))
15136 , m_nToggleFocusInSignalId(0)
15137 , m_nToggleFocusOutSignalId(0)
15138 , m_nRowActivatedSignalId(g_signal_connect(m_pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
15139 , m_nChangedSignalId(g_signal_connect(m_pEntry, "changed", G_CALLBACK(signalChanged), this))
15140 , m_nPopupShownSignalId(g_signal_connect(m_pToggleButton, "toggled", G_CALLBACK(signalPopupToggled), this))
15141 , m_nAutoCompleteIdleId(0)
15142 , m_nNonCustomLineHeight(-1)
15143 , m_nPrePopupCursorPos(-1)
15144 , m_nMRUCount(0)
15145 , m_nMaxMRUCount(0)
15147 int nActive = gtk_combo_box_get_active(m_pComboBox);
15149 if (gtk_style_context_has_class(gtk_widget_get_style_context(GTK_WIDGET(m_pComboBox)), "small-button"))
15150 gtk_style_context_add_class(gtk_widget_get_style_context(GTK_WIDGET(getContainer())), "small-button");
15152 insertAsParent(GTK_WIDGET(m_pComboBox), GTK_WIDGET(getContainer()));
15153 gtk_widget_set_visible(GTK_WIDGET(m_pComboBox), false);
15154 gtk_widget_set_no_show_all(GTK_WIDGET(m_pComboBox), true);
15156 gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
15157 /* tdf#136455 gtk_combo_box_set_model with a null Model should be good
15158 enough. But in practice, while the ComboBox model is unset, GTK
15159 doesn't unset the ComboBox menus model, so that remains listening to
15160 additions to the ListStore and slowing things down massively.
15161 Using a new model does reset the menu to listen to that unused one instead */
15162 gtk_combo_box_set_model(m_pComboBox, GTK_TREE_MODEL(gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING)));
15164 GtkTreeViewColumn* pCol = gtk_tree_view_column_new();
15165 gtk_tree_view_append_column(m_pTreeView, pCol);
15167 bool bPixbufUsedSurface = gtk_tree_model_get_n_columns(m_pTreeModel) == 4;
15169 GList* cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_pComboBox));
15170 // move the cell renderers from the combobox to the replacement treeview
15171 m_pMenuTextRenderer = static_cast<GtkCellRenderer*>(cells->data);
15172 for (GList* pRenderer = g_list_first(cells); pRenderer; pRenderer = g_list_next(pRenderer))
15174 GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
15175 bool bTextRenderer = pCellRenderer == m_pMenuTextRenderer;
15176 gtk_tree_view_column_pack_end(pCol, pCellRenderer, bTextRenderer);
15177 if (!bTextRenderer)
15179 if (bPixbufUsedSurface)
15180 gtk_tree_view_column_set_attributes(pCol, pCellRenderer, "surface", 3, nullptr);
15181 else
15182 gtk_tree_view_column_set_attributes(pCol, pCellRenderer, "pixbuf", 2, nullptr);
15186 gtk_tree_view_column_set_attributes(pCol, m_pMenuTextRenderer, "text", m_nTextCol, nullptr);
15188 if (gtk_combo_box_get_has_entry(m_pComboBox))
15190 m_bAutoComplete = true;
15191 m_nEntryInsertTextSignalId = g_signal_connect(m_pEntry, "insert-text", G_CALLBACK(signalEntryInsertText), this);
15192 m_nEntryActivateSignalId = g_signal_connect(m_pEntry, "activate", G_CALLBACK(signalEntryActivate), this);
15193 m_nEntryFocusInSignalId = g_signal_connect(m_pEntry, "focus-in-event", G_CALLBACK(signalEntryFocusIn), this);
15194 m_nEntryFocusOutSignalId = g_signal_connect(m_pEntry, "focus-out-event", G_CALLBACK(signalEntryFocusOut), this);
15195 m_nEntryKeyPressEventSignalId = g_signal_connect(m_pEntry, "key-press-event", G_CALLBACK(signalEntryKeyPress), this);
15196 m_nKeyPressEventSignalId = 0;
15198 else
15200 gtk_widget_set_visible(m_pEntry, false);
15201 m_pEntry = nullptr;
15203 GtkWidget* pArrow = GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "arrow"));
15204 gtk_container_child_set(getContainer(), m_pToggleButton, "expand", true, nullptr);
15206 auto m_pCellArea = gtk_cell_area_box_new();
15207 m_pCellView = GTK_CELL_VIEW(gtk_cell_view_new_with_context(m_pCellArea, nullptr));
15208 gtk_widget_set_hexpand(GTK_WIDGET(m_pCellView), true);
15209 GtkBox* pBox = GTK_BOX(gtk_widget_get_parent(pArrow));
15211 gint nImageSpacing(2);
15212 GtkStyleContext *pContext = gtk_widget_get_style_context(GTK_WIDGET(m_pToggleButton));
15213 gtk_style_context_get_style(pContext, "image-spacing", &nImageSpacing, nullptr);
15214 gtk_box_set_spacing(pBox, nImageSpacing);
15216 gtk_box_pack_start(pBox, GTK_WIDGET(m_pCellView), false, true, 0);
15218 gtk_cell_view_set_fit_model(m_pCellView, true);
15219 gtk_cell_view_set_model(m_pCellView, m_pTreeModel);
15221 m_pButtonTextRenderer = gtk_cell_renderer_text_new();
15222 gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pCellView), m_pButtonTextRenderer, true);
15223 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), m_pButtonTextRenderer, "text", m_nTextCol, nullptr);
15224 if (g_list_length(cells) > 1)
15226 GtkCellRenderer* pCellRenderer = gtk_cell_renderer_pixbuf_new();
15227 gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, false);
15228 if (bPixbufUsedSurface)
15229 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, "surface", 3, nullptr);
15230 else
15231 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, "pixbuf", 2, nullptr);
15234 gtk_widget_show_all(GTK_WIDGET(m_pCellView));
15236 m_nEntryInsertTextSignalId = 0;
15237 m_nEntryActivateSignalId = 0;
15238 m_nEntryFocusInSignalId = 0;
15239 m_nEntryFocusOutSignalId = 0;
15240 m_nEntryKeyPressEventSignalId = 0;
15241 m_nKeyPressEventSignalId = g_signal_connect(m_pToggleButton, "key-press-event", G_CALLBACK(signalKeyPress), this);
15244 g_list_free(cells);
15246 if (nActive != -1)
15247 tree_view_set_cursor(nActive);
15249 g_signal_connect(getContainer(), "mnemonic-activate", G_CALLBACK(signalComboMnemonicActivate), this);
15251 g_signal_connect(m_pMenuWindow, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
15252 g_signal_connect(m_pMenuWindow, "button-press-event", G_CALLBACK(signalButtonPress), this);
15253 g_signal_connect(m_pMenuWindow, "motion-notify-event", G_CALLBACK(signalMotion), this);
15254 // support typeahead for the menu itself, typing into the menu will
15255 // select via the vcl selection engine, a matching entry.
15256 g_signal_connect(m_pMenuWindow, "key-press-event", G_CALLBACK(signalKeyPress), this);
15258 g_signal_connect(m_pOverlay, "get-child-position", G_CALLBACK(signalGetChildPosition), this);
15259 gtk_overlay_add_overlay(m_pOverlay, GTK_WIDGET(m_pOverlayButton));
15260 g_signal_connect(m_pOverlayButton, "leave-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
15261 g_signal_connect(m_pOverlayButton, "enter-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
15264 virtual int get_active() const override
15266 int nActive = get_active_including_mru();
15267 if (nActive == -1)
15268 return -1;
15270 if (m_nMRUCount)
15272 if (nActive < m_nMRUCount)
15273 nActive = find_text(get_text_including_mru(nActive));
15274 else
15275 nActive -= (m_nMRUCount + 1);
15278 return nActive;
15281 virtual OUString get_active_id() const override
15283 int nActive = get_active();
15284 return nActive != -1 ? get_id(nActive) : OUString();
15287 virtual void set_active_id(const OUString& rStr) override
15289 set_active(find_id(rStr));
15290 m_bChangedByMenu = false;
15293 virtual void set_size_request(int nWidth, int nHeight) override
15295 if (m_pButtonTextRenderer)
15297 // tweak the cell render to get a narrower size to stick
15298 if (nWidth != -1)
15300 // this bit isn't great, I really want to be able to ellipse the text in the comboboxtext itself and let
15301 // the popup menu render them in full, in the interim ellipse both of them
15302 g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_MIDDLE, nullptr);
15304 // to find out how much of the width of the combobox belongs to the cell, set
15305 // the cell and widget to the min cell width and see what the difference is
15306 int min;
15307 gtk_cell_renderer_get_preferred_width(m_pButtonTextRenderer, m_pWidget, &min, nullptr);
15308 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, min, -1);
15309 gtk_widget_set_size_request(m_pWidget, min, -1);
15310 int nNonCellWidth = get_preferred_size().Width() - min;
15312 int nCellWidth = nWidth - nNonCellWidth;
15313 if (nCellWidth >= 0)
15315 // now set the cell to the max width which it can be within the
15316 // requested widget width
15317 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, nWidth - nNonCellWidth, -1);
15320 else
15322 g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_NONE, nullptr);
15323 gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, -1, -1);
15327 gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
15330 virtual void set_active(int pos) override
15332 set_active_including_mru(include_mru(pos), false);
15335 virtual OUString get_active_text() const override
15337 if (m_pEntry)
15339 const gchar* pText = gtk_entry_get_text(GTK_ENTRY(m_pEntry));
15340 return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
15343 int nActive = get_active();
15344 if (nActive == -1)
15345 return OUString();
15347 return get_text(nActive);
15350 virtual OUString get_text(int pos) const override
15352 if (m_nMRUCount)
15353 pos += (m_nMRUCount + 1);
15354 return get_text_including_mru(pos);
15357 virtual OUString get_id(int pos) const override
15359 if (m_nMRUCount)
15360 pos += (m_nMRUCount + 1);
15361 return get_id_including_mru(pos);
15364 virtual void set_id(int pos, const OUString& rId) override
15366 if (m_nMRUCount)
15367 pos += (m_nMRUCount + 1);
15368 set_id_including_mru(pos, rId);
15371 virtual void insert_vector(const std::vector<weld::ComboBoxEntry>& rItems, bool bKeepExisting) override
15373 freeze();
15375 int nInsertionPoint;
15376 if (!bKeepExisting)
15378 clear();
15379 nInsertionPoint = 0;
15381 else
15382 nInsertionPoint = get_count();
15384 GtkTreeIter iter;
15385 // tdf#125241 inserting backwards is faster
15386 for (auto aI = rItems.rbegin(); aI != rItems.rend(); ++aI)
15388 const auto& rItem = *aI;
15389 insert_row(GTK_LIST_STORE(m_pTreeModel), iter, nInsertionPoint, rItem.sId.isEmpty() ? nullptr : &rItem.sId,
15390 rItem.sString, rItem.sImage.isEmpty() ? nullptr : &rItem.sImage, nullptr);
15393 thaw();
15396 virtual void remove(int pos) override
15398 if (m_nMRUCount)
15399 pos += (m_nMRUCount + 1);
15400 remove_including_mru(pos);
15403 virtual void insert(int pos, const OUString& rText, const OUString* pId, const OUString* pIconName, VirtualDevice* pImageSurface) override
15405 insert_including_mru(include_mru(pos), rText, pId, pIconName, pImageSurface);
15408 virtual void insert_separator(int pos, const OUString& rId) override
15410 pos = pos == -1 ? get_count() : pos;
15411 if (m_nMRUCount)
15412 pos += (m_nMRUCount + 1);
15413 insert_separator_including_mru(pos, rId);
15416 virtual int get_count() const override
15418 int nCount = get_count_including_mru();
15419 if (m_nMRUCount)
15420 nCount -= (m_nMRUCount + 1);
15421 return nCount;
15424 virtual int find_text(const OUString& rStr) const override
15426 int nPos = find_text_including_mru(rStr, false);
15427 if (nPos != -1 && m_nMRUCount)
15428 nPos -= (m_nMRUCount + 1);
15429 return nPos;
15432 virtual int find_id(const OUString& rId) const override
15434 int nPos = find_id_including_mru(rId, false);
15435 if (nPos != -1 && m_nMRUCount)
15436 nPos -= (m_nMRUCount + 1);
15437 return nPos;
15440 virtual void clear() override
15442 do_clear();
15445 virtual void make_sorted() override
15447 m_xSorter.reset(new comphelper::string::NaturalStringSorter(
15448 ::comphelper::getProcessComponentContext(),
15449 Application::GetSettings().GetUILanguageTag().getLocale()));
15450 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
15451 gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
15452 gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, default_sort_func, m_xSorter.get(), nullptr);
15455 virtual bool has_entry() const override
15457 return gtk_combo_box_get_has_entry(m_pComboBox);
15460 virtual void set_entry_message_type(weld::EntryMessageType eType) override
15462 assert(m_pEntry);
15463 ::set_entry_message_type(GTK_ENTRY(m_pEntry), eType);
15466 virtual void set_entry_text(const OUString& rText) override
15468 assert(m_pEntry);
15469 disable_notify_events();
15470 gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
15471 enable_notify_events();
15474 virtual void set_entry_width_chars(int nChars) override
15476 assert(m_pEntry);
15477 disable_notify_events();
15478 gtk_entry_set_width_chars(GTK_ENTRY(m_pEntry), nChars);
15479 gtk_entry_set_max_width_chars(GTK_ENTRY(m_pEntry), nChars);
15480 enable_notify_events();
15483 virtual void set_entry_max_length(int nChars) override
15485 assert(m_pEntry);
15486 disable_notify_events();
15487 gtk_entry_set_max_length(GTK_ENTRY(m_pEntry), nChars);
15488 enable_notify_events();
15491 virtual void select_entry_region(int nStartPos, int nEndPos) override
15493 assert(m_pEntry);
15494 disable_notify_events();
15495 gtk_editable_select_region(GTK_EDITABLE(m_pEntry), nStartPos, nEndPos);
15496 enable_notify_events();
15499 virtual bool get_entry_selection_bounds(int& rStartPos, int &rEndPos) override
15501 assert(m_pEntry);
15502 return gtk_editable_get_selection_bounds(GTK_EDITABLE(m_pEntry), &rStartPos, &rEndPos);
15505 virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
15507 m_bAutoComplete = bEnable;
15508 m_bAutoCompleteCaseSensitive = bCaseSensitive;
15511 virtual void set_entry_placeholder_text(const OUString& rText) override
15513 assert(m_pEntry);
15514 gtk_entry_set_placeholder_text(GTK_ENTRY(m_pEntry), rText.toUtf8().getStr());
15517 virtual void set_entry_editable(bool bEditable) override
15519 assert(m_pEntry);
15520 gtk_editable_set_editable(GTK_EDITABLE(m_pEntry), bEditable);
15523 virtual void cut_entry_clipboard() override
15525 assert(m_pEntry);
15526 gtk_editable_cut_clipboard(GTK_EDITABLE(m_pEntry));
15529 virtual void copy_entry_clipboard() override
15531 assert(m_pEntry);
15532 gtk_editable_copy_clipboard(GTK_EDITABLE(m_pEntry));
15535 virtual void paste_entry_clipboard() override
15537 assert(m_pEntry);
15538 gtk_editable_paste_clipboard(GTK_EDITABLE(m_pEntry));
15541 virtual void set_entry_font(const vcl::Font& rFont) override
15543 m_xFont.reset(new vcl::Font(rFont));
15544 PangoAttrList* pAttrList = create_attr_list(rFont);
15545 assert(m_pEntry);
15546 gtk_entry_set_attributes(GTK_ENTRY(m_pEntry), pAttrList);
15547 pango_attr_list_unref(pAttrList);
15550 virtual vcl::Font get_entry_font() override
15552 if (m_xFont)
15553 return *m_xFont;
15554 assert(m_pEntry);
15555 PangoContext* pContext = gtk_widget_get_pango_context(m_pEntry);
15556 return pango_to_vcl(pango_context_get_font_description(pContext),
15557 Application::GetSettings().GetUILanguageTag().getLocale());
15560 virtual void disable_notify_events() override
15562 if (m_pEntry)
15564 g_signal_handler_block(m_pEntry, m_nEntryInsertTextSignalId);
15565 g_signal_handler_block(m_pEntry, m_nEntryActivateSignalId);
15566 g_signal_handler_block(m_pEntry, m_nEntryFocusInSignalId);
15567 g_signal_handler_block(m_pEntry, m_nEntryFocusOutSignalId);
15568 g_signal_handler_block(m_pEntry, m_nEntryKeyPressEventSignalId);
15569 g_signal_handler_block(m_pEntry, m_nChangedSignalId);
15571 else
15572 g_signal_handler_block(m_pToggleButton, m_nKeyPressEventSignalId);
15573 if (m_nToggleFocusInSignalId)
15574 g_signal_handler_block(m_pToggleButton, m_nToggleFocusInSignalId);
15575 if (m_nToggleFocusOutSignalId)
15576 g_signal_handler_block(m_pToggleButton, m_nToggleFocusOutSignalId);
15577 g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
15578 g_signal_handler_block(m_pToggleButton, m_nPopupShownSignalId);
15579 GtkInstanceContainer::disable_notify_events();
15582 virtual void enable_notify_events() override
15584 GtkInstanceContainer::enable_notify_events();
15585 g_signal_handler_unblock(m_pToggleButton, m_nPopupShownSignalId);
15586 g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
15587 if (m_nToggleFocusInSignalId)
15588 g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusInSignalId);
15589 if (m_nToggleFocusOutSignalId)
15590 g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusOutSignalId);
15591 if (m_pEntry)
15593 g_signal_handler_unblock(m_pEntry, m_nChangedSignalId);
15594 g_signal_handler_unblock(m_pEntry, m_nEntryActivateSignalId);
15595 g_signal_handler_unblock(m_pEntry, m_nEntryFocusInSignalId);
15596 g_signal_handler_unblock(m_pEntry, m_nEntryFocusOutSignalId);
15597 g_signal_handler_unblock(m_pEntry, m_nEntryKeyPressEventSignalId);
15598 g_signal_handler_unblock(m_pEntry, m_nEntryInsertTextSignalId);
15600 else
15601 g_signal_handler_unblock(m_pToggleButton, m_nKeyPressEventSignalId);
15604 virtual void freeze() override
15606 disable_notify_events();
15607 GtkInstanceContainer::freeze();
15608 g_object_ref(m_pTreeModel);
15609 gtk_tree_view_set_model(m_pTreeView, nullptr);
15610 g_object_freeze_notify(G_OBJECT(m_pTreeModel));
15611 if (m_xSorter)
15613 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
15614 gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
15616 enable_notify_events();
15619 virtual void thaw() override
15621 disable_notify_events();
15622 if (m_xSorter)
15624 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
15625 gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
15627 g_object_thaw_notify(G_OBJECT(m_pTreeModel));
15628 gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
15629 g_object_unref(m_pTreeModel);
15631 GtkInstanceContainer::thaw();
15632 enable_notify_events();
15635 virtual bool get_popup_shown() const override
15637 return m_bPopupActive;
15640 virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
15642 if (!m_nToggleFocusInSignalId)
15643 m_nToggleFocusInSignalId = g_signal_connect_after(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this);
15644 GtkInstanceContainer::connect_focus_in(rLink);
15647 virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
15649 if (!m_nToggleFocusOutSignalId)
15650 m_nToggleFocusOutSignalId = g_signal_connect_after(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this);
15651 GtkInstanceContainer::connect_focus_out(rLink);
15654 virtual void grab_focus() override
15656 if (has_focus())
15657 return;
15658 if (m_pEntry)
15659 gtk_widget_grab_focus(m_pEntry);
15660 else
15661 gtk_widget_grab_focus(m_pToggleButton);
15664 virtual bool has_focus() const override
15666 if (m_pEntry && gtk_widget_has_focus(m_pEntry))
15667 return true;
15669 if (gtk_widget_has_focus(m_pToggleButton))
15670 return true;
15672 if (gtk_widget_get_visible(GTK_WIDGET(m_pMenuWindow)))
15674 if (gtk_widget_has_focus(GTK_WIDGET(m_pOverlayButton)) || gtk_widget_has_focus(GTK_WIDGET(m_pTreeView)))
15675 return true;
15678 return GtkInstanceWidget::has_focus();
15681 virtual bool changed_by_direct_pick() const override
15683 return m_bChangedByMenu;
15686 virtual void set_custom_renderer(bool bOn) override
15688 if (bOn == m_bCustomRenderer)
15689 return;
15690 GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
15691 // keep the original height around for optimal popup height calculation
15692 m_nNonCustomLineHeight = bOn ? ::get_height_row(m_pTreeView, pColumns) : -1;
15693 GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pColumns->data);
15694 gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn));
15695 if (bOn)
15697 GtkCellRenderer *pRenderer = custom_cell_renderer_surface_new();
15698 GValue value = G_VALUE_INIT;
15699 g_value_init(&value, G_TYPE_POINTER);
15700 g_value_set_pointer(&value, static_cast<gpointer>(this));
15701 g_object_set_property(G_OBJECT(pRenderer), "instance", &value);
15702 gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
15703 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
15704 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "id", m_nIdCol);
15706 else
15708 GtkCellRenderer *pRenderer = gtk_cell_renderer_text_new();
15709 gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
15710 gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
15712 g_list_free(pColumns);
15713 m_bCustomRenderer = bOn;
15716 void call_signal_custom_render(VirtualDevice& rOutput, const tools::Rectangle& rRect, bool bSelected, const OUString& rId)
15718 signal_custom_render(rOutput, rRect, bSelected, rId);
15721 Size call_signal_custom_get_size(VirtualDevice& rOutput)
15723 return signal_custom_get_size(rOutput);
15726 VclPtr<VirtualDevice> create_render_virtual_device() const override
15728 return create_virtual_device();
15731 virtual void set_item_menu(const OString& rIdent, weld::Menu* pMenu) override
15733 m_xCustomMenuButtonHelper.reset();
15734 GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
15735 GtkWidget* pMenuWidget = GTK_WIDGET(pPopoverWidget ? pPopoverWidget->getMenu() : nullptr);
15736 gtk_menu_button_set_popup(m_pOverlayButton, pMenuWidget);
15737 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), pMenuWidget != nullptr);
15738 gtk_widget_queue_resize_no_redraw(GTK_WIDGET(m_pOverlayButton)); // force location recalc
15739 if (pMenuWidget)
15740 m_xCustomMenuButtonHelper.reset(new CustomRenderMenuButtonHelper(GTK_MENU(pMenuWidget), GTK_TOGGLE_BUTTON(m_pToggleButton)));
15741 m_sMenuButtonRow = OUString::fromUtf8(rIdent);
15744 OUString get_mru_entries() const override
15746 const sal_Unicode cSep = ';';
15748 OUStringBuffer aEntries;
15749 for (sal_Int32 n = 0; n < m_nMRUCount; n++)
15751 aEntries.append(get_text_including_mru(n));
15752 if (n < m_nMRUCount - 1)
15753 aEntries.append(cSep);
15755 return aEntries.makeStringAndClear();
15758 virtual void set_mru_entries(const OUString& rEntries) override
15760 const sal_Unicode cSep = ';';
15762 // Remove old MRU entries
15763 for (sal_Int32 n = m_nMRUCount; n;)
15764 remove_including_mru(--n);
15766 sal_Int32 nMRUCount = 0;
15767 sal_Int32 nIndex = 0;
15770 OUString aEntry = rEntries.getToken(0, cSep, nIndex);
15771 // Accept only existing entries
15772 int nPos = find_text(aEntry);
15773 if (nPos != -1)
15775 OUString sId = get_id(nPos);
15776 insert_including_mru(0, aEntry, &sId, nullptr, nullptr);
15777 ++nMRUCount;
15780 while (nIndex >= 0);
15782 if (nMRUCount && !m_nMRUCount)
15783 insert_separator_including_mru(nMRUCount, "separator");
15784 else if (!nMRUCount && m_nMRUCount)
15785 remove_including_mru(m_nMRUCount); // remove separator
15787 m_nMRUCount = nMRUCount;
15790 int get_menu_button_width() const override
15792 bool bVisible = gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton));
15793 if (!bVisible)
15794 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), true);
15795 gint nWidth;
15796 gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &nWidth, nullptr);
15797 if (!bVisible)
15798 gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), false);
15799 return nWidth;
15802 virtual ~GtkInstanceComboBox() override
15804 m_xCustomMenuButtonHelper.reset();
15805 do_clear();
15806 if (m_nAutoCompleteIdleId)
15807 g_source_remove(m_nAutoCompleteIdleId);
15808 if (m_pEntry)
15810 g_signal_handler_disconnect(m_pEntry, m_nChangedSignalId);
15811 g_signal_handler_disconnect(m_pEntry, m_nEntryInsertTextSignalId);
15812 g_signal_handler_disconnect(m_pEntry, m_nEntryActivateSignalId);
15813 g_signal_handler_disconnect(m_pEntry, m_nEntryFocusInSignalId);
15814 g_signal_handler_disconnect(m_pEntry, m_nEntryFocusOutSignalId);
15815 g_signal_handler_disconnect(m_pEntry, m_nEntryKeyPressEventSignalId);
15817 else
15818 g_signal_handler_disconnect(m_pToggleButton, m_nKeyPressEventSignalId);
15819 if (m_nToggleFocusInSignalId)
15820 g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusInSignalId);
15821 if (m_nToggleFocusOutSignalId)
15822 g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusOutSignalId);
15823 g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
15824 g_signal_handler_disconnect(m_pToggleButton, m_nPopupShownSignalId);
15826 gtk_combo_box_set_model(m_pComboBox, m_pTreeModel);
15827 gtk_tree_view_set_model(m_pTreeView, nullptr);
15829 // restore original hierarchy in dtor so a new GtkInstanceComboBox will
15830 // result in the same layout each time
15832 g_object_ref(m_pComboBox);
15834 GtkContainer* pContainer = getContainer();
15836 gtk_container_remove(pContainer, GTK_WIDGET(m_pComboBox));
15838 replaceWidget(GTK_WIDGET(pContainer), GTK_WIDGET(m_pComboBox));
15840 g_object_unref(m_pComboBox);
15843 g_object_unref(m_pComboBuilder);
15849 bool custom_cell_renderer_surface_get_preferred_size(GtkCellRenderer *cell,
15850 GtkOrientation orientation,
15851 gint *minimum_size,
15852 gint *natural_size)
15854 GValue value = G_VALUE_INIT;
15855 g_value_init(&value, G_TYPE_STRING);
15856 g_object_get_property(G_OBJECT(cell), "id", &value);
15858 const char* pStr = g_value_get_string(&value);
15860 OUString sId(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
15862 value = G_VALUE_INIT;
15863 g_value_init(&value, G_TYPE_POINTER);
15864 g_object_get_property(G_OBJECT(cell), "instance", &value);
15866 CustomCellRendererSurface *cellsurface = CUSTOM_CELL_RENDERER_SURFACE(cell);
15868 GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(g_value_get_pointer(&value));
15870 Size aSize;
15872 if (pWidget)
15874 ensure_device(cellsurface, pWidget);
15875 if (GtkInstanceTreeView* pTreeView = dynamic_cast<GtkInstanceTreeView*>(pWidget))
15876 aSize = pTreeView->call_signal_custom_get_size(*cellsurface->device, sId);
15877 else if (GtkInstanceComboBox* pComboBox = dynamic_cast<GtkInstanceComboBox*>(pWidget))
15878 aSize = pComboBox->call_signal_custom_get_size(*cellsurface->device);
15881 if (orientation == GTK_ORIENTATION_HORIZONTAL)
15883 if (minimum_size)
15884 *minimum_size = aSize.Width();
15886 if (natural_size)
15887 *natural_size = aSize.Width();
15889 else
15891 if (minimum_size)
15892 *minimum_size = aSize.Height();
15894 if (natural_size)
15895 *natural_size = aSize.Height();
15898 return true;
15901 void custom_cell_renderer_surface_render(GtkCellRenderer* cell,
15902 cairo_t* cr,
15903 GtkWidget* /*widget*/,
15904 const GdkRectangle* /*background_area*/,
15905 const GdkRectangle* cell_area,
15906 GtkCellRendererState flags)
15908 GValue value = G_VALUE_INIT;
15909 g_value_init(&value, G_TYPE_STRING);
15910 g_object_get_property(G_OBJECT(cell), "id", &value);
15912 const char* pStr = g_value_get_string(&value);
15913 OUString sId(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
15915 value = G_VALUE_INIT;
15916 g_value_init(&value, G_TYPE_POINTER);
15917 g_object_get_property(G_OBJECT(cell), "instance", &value);
15919 CustomCellRendererSurface *cellsurface = CUSTOM_CELL_RENDERER_SURFACE(cell);
15921 GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(g_value_get_pointer(&value));
15923 if (!pWidget)
15924 return;
15926 ensure_device(cellsurface, pWidget);
15928 Size aSize(cell_area->width, cell_area->height);
15929 // false to not bother setting the bg on resize as we'll do that
15930 // ourself via cairo
15931 cellsurface->device->SetOutputSizePixel(aSize, false);
15933 cairo_surface_t* pSurface = get_underlying_cairo_surface(*cellsurface->device);
15935 // fill surface as transparent so it can be blended with the potentially
15936 // selected background
15937 cairo_t* tempcr = cairo_create(pSurface);
15938 cairo_set_source_rgba(tempcr, 0, 0, 0, 0);
15939 cairo_set_operator(tempcr, CAIRO_OPERATOR_SOURCE);
15940 cairo_paint(tempcr);
15941 cairo_destroy(tempcr);
15942 cairo_surface_flush(pSurface);
15944 if (GtkInstanceTreeView* pTreeView = dynamic_cast<GtkInstanceTreeView*>(pWidget))
15945 pTreeView->call_signal_custom_render(*cellsurface->device, tools::Rectangle(Point(0, 0), aSize), flags & GTK_CELL_RENDERER_SELECTED, sId);
15946 else if (GtkInstanceComboBox* pComboBox = dynamic_cast<GtkInstanceComboBox*>(pWidget))
15947 pComboBox->call_signal_custom_render(*cellsurface->device, tools::Rectangle(Point(0, 0), aSize), flags & GTK_CELL_RENDERER_SELECTED, sId);
15948 cairo_surface_mark_dirty(pSurface);
15950 cairo_set_source_surface(cr, pSurface, cell_area->x, cell_area->y);
15951 cairo_paint(cr);
15954 namespace {
15956 class GtkInstanceEntryTreeView : public GtkInstanceContainer, public virtual weld::EntryTreeView
15958 private:
15959 GtkInstanceEntry* m_pEntry;
15960 GtkInstanceTreeView* m_pTreeView;
15961 gulong m_nKeyPressSignalId;
15962 gulong m_nEntryInsertTextSignalId;
15963 guint m_nAutoCompleteIdleId;
15964 bool m_bAutoCompleteCaseSensitive;
15965 bool m_bTreeChange;
15967 bool signal_key_press(GdkEventKey* pEvent)
15969 if (GtkSalFrame::GetMouseModCode(pEvent->state)) // only with no modifiers held
15970 return false;
15972 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 ||
15973 pEvent->keyval == GDK_KEY_KP_Down || pEvent->keyval == GDK_KEY_Down || pEvent->keyval == GDK_KEY_KP_Page_Down || pEvent->keyval == GDK_KEY_Page_Down)
15975 gboolean ret;
15976 disable_notify_events();
15977 GtkWidget* pWidget = m_pTreeView->getWidget();
15978 if (m_pTreeView->get_selected_index() == -1)
15980 m_pTreeView->set_cursor(0);
15981 m_pTreeView->select(0);
15982 m_xEntry->set_text(m_xTreeView->get_selected_text());
15984 else
15986 gtk_widget_grab_focus(pWidget);
15987 g_signal_emit_by_name(pWidget, "key-press-event", pEvent, &ret);
15988 m_xEntry->set_text(m_xTreeView->get_selected_text());
15989 gtk_widget_grab_focus(m_pEntry->getWidget());
15991 m_xEntry->select_region(0, -1);
15992 enable_notify_events();
15993 m_bTreeChange = true;
15994 m_pEntry->fire_signal_changed();
15995 m_bTreeChange = false;
15996 return true;
15998 return false;
16001 static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
16003 GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
16004 return pThis->signal_key_press(pEvent);
16007 static gboolean idleAutoComplete(gpointer widget)
16009 GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
16010 pThis->auto_complete();
16011 return false;
16014 void auto_complete()
16016 m_nAutoCompleteIdleId = 0;
16017 OUString aStartText = get_active_text();
16018 int nStartPos, nEndPos;
16019 get_entry_selection_bounds(nStartPos, nEndPos);
16020 int nMaxSelection = std::max(nStartPos, nEndPos);
16021 if (nMaxSelection != aStartText.getLength())
16022 return;
16024 disable_notify_events();
16025 int nActive = get_active();
16026 int nStart = nActive;
16028 if (nStart == -1)
16029 nStart = 0;
16031 // Try match case sensitive from current position
16032 int nPos = m_pTreeView->starts_with(aStartText, nStart, true);
16033 if (nPos == -1 && nStart != 0)
16035 // Try match case insensitive, but from start
16036 nPos = m_pTreeView->starts_with(aStartText, 0, true);
16039 if (!m_bAutoCompleteCaseSensitive)
16041 // Try match case insensitive from current position
16042 nPos = m_pTreeView->starts_with(aStartText, nStart, false);
16043 if (nPos == -1 && nStart != 0)
16045 // Try match case insensitive, but from start
16046 nPos = m_pTreeView->starts_with(aStartText, 0, false);
16050 if (nPos == -1)
16052 // Try match case sensitive from current position
16053 nPos = m_pTreeView->starts_with(aStartText, nStart, true);
16054 if (nPos == -1 && nStart != 0)
16056 // Try match case sensitive, but from start
16057 nPos = m_pTreeView->starts_with(aStartText, 0, true);
16061 if (nPos != -1)
16063 OUString aText = get_text(nPos);
16064 if (aText != aStartText)
16065 set_active_text(aText);
16066 select_entry_region(aText.getLength(), aStartText.getLength());
16068 enable_notify_events();
16071 void signal_entry_insert_text(GtkEntry*, const gchar*, gint, gint*)
16073 // now check for autocompletes
16074 if (m_nAutoCompleteIdleId)
16075 g_source_remove(m_nAutoCompleteIdleId);
16076 m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
16079 static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
16080 gint* position, gpointer widget)
16082 GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
16083 pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
16087 public:
16088 GtkInstanceEntryTreeView(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership,
16089 std::unique_ptr<weld::Entry> xEntry, std::unique_ptr<weld::TreeView> xTreeView)
16090 : EntryTreeView(std::move(xEntry), std::move(xTreeView))
16091 , GtkInstanceContainer(pContainer, pBuilder, bTakeOwnership)
16092 , m_pEntry(dynamic_cast<GtkInstanceEntry*>(m_xEntry.get()))
16093 , m_pTreeView(dynamic_cast<GtkInstanceTreeView*>(m_xTreeView.get()))
16094 , m_nAutoCompleteIdleId(0)
16095 , m_bAutoCompleteCaseSensitive(false)
16096 , m_bTreeChange(false)
16098 assert(m_pEntry);
16099 GtkWidget* pWidget = m_pEntry->getWidget();
16100 m_nKeyPressSignalId = g_signal_connect(pWidget, "key-press-event", G_CALLBACK(signalKeyPress), this);
16101 m_nEntryInsertTextSignalId = g_signal_connect(pWidget, "insert-text", G_CALLBACK(signalEntryInsertText), this);
16104 virtual void insert_separator(int /*pos*/, const OUString& /*rId*/) override
16106 assert(false);
16109 virtual void make_sorted() override
16111 GtkWidget* pTreeView = m_pTreeView->getWidget();
16112 GtkTreeModel* pModel = gtk_tree_view_get_model(GTK_TREE_VIEW(pTreeView));
16113 GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(pModel);
16114 gtk_tree_sortable_set_sort_column_id(pSortable, 1, GTK_SORT_ASCENDING);
16117 virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
16119 assert(!bEnable && "not implemented yet"); (void)bEnable;
16120 m_bAutoCompleteCaseSensitive = bCaseSensitive;
16123 virtual void set_entry_placeholder_text(const OUString& rText) override
16125 m_xEntry->set_placeholder_text(rText);
16128 virtual void set_entry_editable(bool bEditable) override
16130 m_xEntry->set_editable(bEditable);
16133 virtual void cut_entry_clipboard() override
16135 m_xEntry->cut_clipboard();
16138 virtual void copy_entry_clipboard() override
16140 m_xEntry->copy_clipboard();
16143 virtual void paste_entry_clipboard() override
16145 m_xEntry->paste_clipboard();
16148 virtual void set_entry_font(const vcl::Font& rFont) override
16150 m_xEntry->set_font(rFont);
16153 virtual vcl::Font get_entry_font() override
16155 return m_xEntry->get_font();
16158 virtual void grab_focus() override { m_xEntry->grab_focus(); }
16160 virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
16162 m_xEntry->connect_focus_in(rLink);
16165 virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
16167 m_xEntry->connect_focus_out(rLink);
16170 virtual void disable_notify_events() override
16172 GtkWidget* pWidget = m_pEntry->getWidget();
16173 g_signal_handler_block(pWidget, m_nEntryInsertTextSignalId);
16174 g_signal_handler_block(pWidget, m_nKeyPressSignalId);
16175 m_pTreeView->disable_notify_events();
16176 GtkInstanceContainer::disable_notify_events();
16179 virtual void enable_notify_events() override
16181 GtkWidget* pWidget = m_pEntry->getWidget();
16182 g_signal_handler_unblock(pWidget, m_nKeyPressSignalId);
16183 g_signal_handler_unblock(pWidget, m_nEntryInsertTextSignalId);
16184 m_pTreeView->enable_notify_events();
16185 GtkInstanceContainer::disable_notify_events();
16188 virtual bool changed_by_direct_pick() const override
16190 return m_bTreeChange;
16193 virtual void set_custom_renderer(bool /*bOn*/) override
16195 assert(false && "not implemented");
16198 virtual int get_max_mru_count() const override
16200 assert(false && "not implemented");
16201 return 0;
16204 virtual void set_max_mru_count(int) override
16206 assert(false && "not implemented");
16209 virtual OUString get_mru_entries() const override
16211 assert(false && "not implemented");
16212 return OUString();
16215 virtual void set_mru_entries(const OUString&) override
16217 assert(false && "not implemented");
16220 virtual void set_item_menu(const OString&, weld::Menu*) override
16222 assert(false && "not implemented");
16225 VclPtr<VirtualDevice> create_render_virtual_device() const override
16227 return create_virtual_device();
16230 int get_menu_button_width() const override
16232 assert(false && "not implemented");
16233 return 0;
16236 virtual ~GtkInstanceEntryTreeView() override
16238 if (m_nAutoCompleteIdleId)
16239 g_source_remove(m_nAutoCompleteIdleId);
16240 GtkWidget* pWidget = m_pEntry->getWidget();
16241 g_signal_handler_disconnect(pWidget, m_nKeyPressSignalId);
16242 g_signal_handler_disconnect(pWidget, m_nEntryInsertTextSignalId);
16246 class GtkInstanceExpander : public GtkInstanceContainer, public virtual weld::Expander
16248 private:
16249 GtkExpander* m_pExpander;
16250 gulong m_nSignalId;
16251 gulong m_nButtonPressEventSignalId;
16253 static void signalExpanded(GtkExpander* pExpander, GParamSpec*, gpointer widget)
16255 GtkInstanceExpander* pThis = static_cast<GtkInstanceExpander*>(widget);
16256 SolarMutexGuard aGuard;
16258 if (gtk_expander_get_resize_toplevel(pExpander))
16260 GtkWidget *pToplevel = gtk_widget_get_toplevel(GTK_WIDGET(pExpander));
16262 // https://gitlab.gnome.org/GNOME/gtk/issues/70
16263 // I imagine at some point a release with a fix will be available in which
16264 // case this can be avoided depending on version number
16265 if (pToplevel && GTK_IS_WINDOW(pToplevel) && gtk_widget_get_realized(pToplevel))
16267 int nToplevelWidth, nToplevelHeight;
16268 int nChildHeight;
16270 GtkWidget* child = gtk_bin_get_child(GTK_BIN(pExpander));
16271 gtk_widget_get_preferred_height(child, &nChildHeight, nullptr);
16272 gtk_window_get_size(GTK_WINDOW(pToplevel), &nToplevelWidth, &nToplevelHeight);
16274 if (pThis->get_expanded())
16275 nToplevelHeight += nChildHeight;
16276 else
16277 nToplevelHeight -= nChildHeight;
16279 gtk_window_resize(GTK_WINDOW(pToplevel), nToplevelWidth, nToplevelHeight);
16283 pThis->signal_expanded();
16286 static gboolean signalButton(GtkWidget*, GdkEventButton*, gpointer)
16288 // don't let button press get to parent window, for the case of the
16289 // an expander in a sidebar where otherwise single click to expand
16290 // doesn't work
16291 return true;
16294 public:
16295 GtkInstanceExpander(GtkExpander* pExpander, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
16296 : GtkInstanceContainer(GTK_CONTAINER(pExpander), pBuilder, bTakeOwnership)
16297 , m_pExpander(pExpander)
16298 , m_nSignalId(g_signal_connect(m_pExpander, "notify::expanded", G_CALLBACK(signalExpanded), this))
16299 , m_nButtonPressEventSignalId(g_signal_connect_after(m_pExpander, "button-press-event", G_CALLBACK(signalButton), this))
16303 virtual void set_label(const OUString& rText) override
16305 ::set_label(GTK_LABEL(gtk_expander_get_label_widget(m_pExpander)), rText);
16308 virtual OUString get_label() const override
16310 return ::get_label(GTK_LABEL(gtk_expander_get_label_widget(m_pExpander)));
16313 virtual bool get_expanded() const override
16315 return gtk_expander_get_expanded(m_pExpander);
16318 virtual void set_expanded(bool bExpand) override
16320 gtk_expander_set_expanded(m_pExpander, bExpand);
16323 virtual ~GtkInstanceExpander() override
16325 g_signal_handler_disconnect(m_pExpander, m_nButtonPressEventSignalId);
16326 g_signal_handler_disconnect(m_pExpander, m_nSignalId);
16330 gboolean signalTooltipQuery(GtkWidget* pWidget, gint /*x*/, gint /*y*/,
16331 gboolean /*keyboard_mode*/, GtkTooltip *tooltip)
16333 const ImplSVHelpData& aHelpData = ImplGetSVHelpData();
16334 if (aHelpData.mbBalloonHelp) // extended tips
16336 // by default use accessible description
16337 AtkObject* pAtkObject = gtk_widget_get_accessible(pWidget);
16338 const char* pDesc = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
16339 if (pDesc && pDesc[0])
16341 gtk_tooltip_set_text(tooltip, pDesc);
16342 return true;
16345 // fallback to the mechanism which needs help installed
16346 OString sHelpId = ::get_help_id(pWidget);
16347 Help* pHelp = !sHelpId.isEmpty() ? Application::GetHelp() : nullptr;
16348 if (pHelp)
16350 OUString sHelpText = pHelp->GetHelpText(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), static_cast<weld::Widget*>(nullptr));
16351 if (!sHelpText.isEmpty())
16353 gtk_tooltip_set_text(tooltip, OUStringToOString(sHelpText, RTL_TEXTENCODING_UTF8).getStr());
16354 return true;
16359 const char* pDesc = gtk_widget_get_tooltip_text(pWidget);
16360 if (pDesc && pDesc[0])
16362 gtk_tooltip_set_text(tooltip, pDesc);
16363 return true;
16366 return false;
16370 namespace
16373 AtkObject* drawing_area_get_accessibity(GtkWidget *pWidget)
16375 AtkObject* pDefaultAccessible = default_drawing_area_get_accessible(pWidget);
16376 void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceDrawingArea");
16377 GtkInstanceDrawingArea* pDrawingArea = static_cast<GtkInstanceDrawingArea*>(pData);
16378 AtkObject *pAtkObj = pDrawingArea ? pDrawingArea->GetAtkObject(pDefaultAccessible) : nullptr;
16379 if (pAtkObj)
16380 return pAtkObj;
16381 return pDefaultAccessible;
16384 void ensure_intercept_drawing_area_accessibility()
16386 static bool bDone;
16387 if (!bDone)
16389 gpointer pClass = g_type_class_ref(GTK_TYPE_DRAWING_AREA);
16390 GtkWidgetClass* pWidgetClass = GTK_WIDGET_CLASS(pClass);
16391 default_drawing_area_get_accessible = pWidgetClass->get_accessible;
16392 pWidgetClass->get_accessible = drawing_area_get_accessibity;
16393 g_type_class_unref(pClass);
16394 bDone = true;
16398 void ensure_disable_ctrl_page_up_down(GType eType)
16400 gpointer pClass = g_type_class_ref(eType);
16401 GtkWidgetClass* pWidgetClass = GTK_WIDGET_CLASS(pClass);
16402 GtkBindingSet* pBindingSet = gtk_binding_set_by_class(pWidgetClass);
16403 gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Up, GDK_CONTROL_MASK);
16404 gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Up, static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK));
16405 gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Down, GDK_CONTROL_MASK);
16406 gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Down, static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK));
16407 g_type_class_unref(pClass);
16410 // tdf#130400 disable ctrl+page_up and ctrl+page_down bindings so the
16411 // keystrokes are consumed by the surrounding notebook bindings instead
16412 void ensure_disable_ctrl_page_up_down_bindings()
16414 static bool bDone;
16415 if (!bDone)
16417 ensure_disable_ctrl_page_up_down(GTK_TYPE_TREE_VIEW);
16418 ensure_disable_ctrl_page_up_down(GTK_TYPE_SPIN_BUTTON);
16419 bDone = true;
16423 class GtkInstanceBuilder : public weld::Builder
16425 private:
16426 ResHookProc m_pStringReplace;
16427 OString m_aUtf8HelpRoot;
16428 OUString m_aIconTheme;
16429 OUString m_aUILang;
16430 GtkBuilder* m_pBuilder;
16431 GSList* m_pObjectList;
16432 GtkWidget* m_pParentWidget;
16433 gulong m_nNotifySignalId;
16434 std::vector<GtkButton*> m_aMnemonicButtons;
16435 std::vector<GtkLabel*> m_aMnemonicLabels;
16437 VclPtr<SystemChildWindow> m_xInterimGlue;
16438 bool m_bAllowCycleFocusOut;
16440 void postprocess_widget(GtkWidget* pWidget)
16442 const bool bHideHelp = comphelper::LibreOfficeKit::isActive() &&
16443 officecfg::Office::Common::Help::HelpRootURL::get().isEmpty();
16445 //fixup icons
16446 //wanted: better way to do this, e.g. make gtk use gio for
16447 //loading from a filename and provide gio protocol handler
16448 //for our image in a zip urls
16450 //unpack the images and keep them as dirs and just
16451 //add the paths to the gtk icon theme dir
16452 if (GTK_IS_IMAGE(pWidget))
16454 GtkImage* pImage = GTK_IMAGE(pWidget);
16455 const gchar* icon_name;
16456 gtk_image_get_icon_name(pImage, &icon_name, nullptr);
16457 if (icon_name)
16459 OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
16460 if (aIconName != "pan-up-symbolic" && aIconName != "pan-down-symbolic")
16462 if (GdkPixbuf* pixbuf = load_icon_by_name_theme_lang(aIconName, m_aIconTheme, m_aUILang))
16464 gtk_image_set_from_pixbuf(pImage, pixbuf);
16465 g_object_unref(pixbuf);
16470 else if (GTK_IS_TOOL_BUTTON(pWidget))
16472 GtkToolButton* pToolButton = GTK_TOOL_BUTTON(pWidget);
16473 if (const gchar* icon_name = gtk_tool_button_get_icon_name(pToolButton))
16475 OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
16476 if (aIconName != "pan-up-symbolic" && aIconName != "pan-down-symbolic")
16478 if (GdkPixbuf* pixbuf = load_icon_by_name_theme_lang(aIconName, m_aIconTheme, m_aUILang))
16480 GtkWidget* pImage = gtk_image_new_from_pixbuf(pixbuf);
16481 g_object_unref(pixbuf);
16482 gtk_tool_button_set_icon_widget(pToolButton, pImage);
16483 gtk_widget_show(pImage);
16488 // if no tooltip reuse the label as default tooltip
16489 if (!gtk_widget_get_tooltip_text(pWidget))
16491 if (const gchar* label = gtk_tool_button_get_label(pToolButton))
16492 gtk_widget_set_tooltip_text(pWidget, label);
16496 //set helpids
16497 const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pWidget));
16498 size_t nLen = pStr ? strlen(pStr) : 0;
16499 if (nLen)
16501 OString sBuildableName(pStr, nLen);
16502 OString sHelpId = m_aUtf8HelpRoot + sBuildableName;
16503 set_help_id(pWidget, sHelpId);
16504 //hook up for extended help
16505 const ImplSVHelpData& aHelpData = ImplGetSVHelpData();
16506 if (aHelpData.mbBalloonHelp && !GTK_IS_DIALOG(pWidget) && !GTK_IS_ASSISTANT(pWidget))
16508 gtk_widget_set_has_tooltip(pWidget, true);
16509 g_signal_connect(pWidget, "query-tooltip", G_CALLBACK(signalTooltipQuery), nullptr);
16512 if (bHideHelp && sBuildableName == "help")
16513 gtk_widget_hide(pWidget);
16516 // expand placeholder and collect potentially missing mnemonics
16517 if (GTK_IS_BUTTON(pWidget))
16519 GtkButton* pButton = GTK_BUTTON(pWidget);
16520 if (m_pStringReplace != nullptr)
16522 OUString aLabel(get_label(pButton));
16523 if (!aLabel.isEmpty())
16524 set_label(pButton, (*m_pStringReplace)(aLabel));
16526 if (gtk_button_get_use_underline(pButton) && !gtk_button_get_use_stock(pButton))
16527 m_aMnemonicButtons.push_back(pButton);
16529 else if (GTK_IS_LABEL(pWidget))
16531 GtkLabel* pLabel = GTK_LABEL(pWidget);
16532 if (m_pStringReplace != nullptr)
16534 OUString aLabel(get_label(pLabel));
16535 if (!aLabel.isEmpty())
16536 set_label(pLabel, (*m_pStringReplace)(aLabel));
16538 if (gtk_label_get_use_underline(pLabel))
16539 m_aMnemonicLabels.push_back(pLabel);
16541 else if (GTK_IS_TEXT_VIEW(pWidget))
16543 GtkTextView* pTextView = GTK_TEXT_VIEW(pWidget);
16544 if (m_pStringReplace != nullptr)
16546 GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(pTextView);
16547 GtkTextIter start, end;
16548 gtk_text_buffer_get_bounds(pBuffer, &start, &end);
16549 char* pTextStr = gtk_text_buffer_get_text(pBuffer, &start, &end, true);
16550 int nTextLen = pTextStr ? strlen(pTextStr) : 0;
16551 if (nTextLen)
16553 OUString sOldText(pTextStr, nTextLen, RTL_TEXTENCODING_UTF8);
16554 OString sText(OUStringToOString((*m_pStringReplace)(sOldText), RTL_TEXTENCODING_UTF8));
16555 gtk_text_buffer_set_text(pBuffer, sText.getStr(), sText.getLength());
16557 g_free(pTextStr);
16560 else if (GTK_IS_WINDOW(pWidget))
16562 if (m_pStringReplace != nullptr) {
16563 GtkWindow* pWindow = GTK_WINDOW(pWidget);
16564 set_title(pWindow, (*m_pStringReplace)(get_title(pWindow)));
16565 if (GTK_IS_MESSAGE_DIALOG(pWindow))
16567 GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(pWindow);
16568 set_primary_text(pMessageDialog, (*m_pStringReplace)(get_primary_text(pMessageDialog)));
16569 set_secondary_text(pMessageDialog, (*m_pStringReplace)(get_secondary_text(pMessageDialog)));
16575 //GtkBuilder sets translation domain during parse, and unsets it again afterwards.
16576 //In order for GtkBuilder to find the translations bindtextdomain has to be called
16577 //for the domain. So here on the first setting of "domain" we call Translate::Create
16578 //to make sure that happens. Without this, if some other part of LibreOffice has
16579 //used the translation machinery for this domain it will still work, but if it
16580 //hasn't, e.g. tdf#119929, then the translation fails
16581 void translation_domain_set()
16583 Translate::Create(gtk_builder_get_translation_domain(m_pBuilder), LanguageTag(m_aUILang));
16584 g_signal_handler_disconnect(m_pBuilder, m_nNotifySignalId);
16587 static void signalNotify(GObject*, GParamSpec *pSpec, gpointer pData)
16589 g_return_if_fail(pSpec != nullptr);
16590 if (strcmp(pSpec->name, "translation-domain") == 0)
16592 GtkInstanceBuilder* pBuilder = static_cast<GtkInstanceBuilder*>(pData);
16593 pBuilder->translation_domain_set();
16597 static void postprocess(gpointer data, gpointer user_data)
16599 GObject* pObject = static_cast<GObject*>(data);
16600 if (!GTK_IS_WIDGET(pObject))
16601 return;
16602 GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
16603 pThis->postprocess_widget(GTK_WIDGET(pObject));
16606 void DisallowCycleFocusOut()
16608 assert(!m_bAllowCycleFocusOut); // we only expect this to be called when this holds
16610 GtkWidget* pTopLevel = gtk_widget_get_toplevel(m_pParentWidget);
16611 assert(pTopLevel);
16612 GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pTopLevel);
16613 assert(pFrame);
16614 // unhook handler and let gtk cycle its own way through this widget's
16615 // children because it has no non-gtk siblings
16616 pFrame->DisallowCycleFocusOut();
16619 static void signalMap(GtkWidget*, gpointer user_data)
16621 GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
16622 // tdf#138047 wait until map to do this because the final SalFrame may
16623 // not be the same as at ctor time
16624 pThis->DisallowCycleFocusOut();
16627 void AllowCycleFocusOut()
16629 assert(!m_bAllowCycleFocusOut); // we only expect this to be called when this holds
16631 GtkWidget* pTopLevel = gtk_widget_get_toplevel(m_pParentWidget);
16632 assert(pTopLevel);
16633 GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pTopLevel);
16634 assert(pFrame);
16635 // rehook handler and let vcl cycle its own way through this widget's
16636 // children
16637 pFrame->AllowCycleFocusOut();
16640 static void signalUnmap(GtkWidget*, gpointer user_data)
16642 GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
16643 pThis->AllowCycleFocusOut();
16646 public:
16647 GtkInstanceBuilder(GtkWidget* pParent, const OUString& rUIRoot, const OUString& rUIFile,
16648 SystemChildWindow* pInterimGlue, bool bAllowCycleFocusOut)
16649 : weld::Builder()
16650 , m_pStringReplace(Translate::GetReadStringHook())
16651 , m_pParentWidget(pParent)
16652 , m_nNotifySignalId(0)
16653 , m_xInterimGlue(pInterimGlue)
16654 , m_bAllowCycleFocusOut(bAllowCycleFocusOut)
16656 OUString sHelpRoot(rUIFile);
16657 ensure_intercept_drawing_area_accessibility();
16658 ensure_disable_ctrl_page_up_down_bindings();
16660 sal_Int32 nIdx = sHelpRoot.lastIndexOf('.');
16661 if (nIdx != -1)
16662 sHelpRoot = sHelpRoot.copy(0, nIdx);
16663 sHelpRoot += OUString('/');
16664 m_aUtf8HelpRoot = OUStringToOString(sHelpRoot, RTL_TEXTENCODING_UTF8);
16665 m_aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
16666 m_aUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
16668 OUString aUri(rUIRoot + rUIFile);
16669 OUString aPath;
16670 osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
16671 m_pBuilder = gtk_builder_new();
16672 m_nNotifySignalId = g_signal_connect_data(G_OBJECT(m_pBuilder), "notify", G_CALLBACK(signalNotify), this, nullptr, G_CONNECT_AFTER);
16673 auto rc = gtk_builder_add_from_file(m_pBuilder, OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr(), nullptr);
16674 assert(rc && "could not load UI file");
16675 (void) rc;
16677 m_pObjectList = gtk_builder_get_objects(m_pBuilder);
16678 g_slist_foreach(m_pObjectList, postprocess, this);
16680 GenerateMissingMnemonics();
16682 if (m_xInterimGlue)
16684 assert(m_pParentWidget);
16685 g_object_set_data(G_OBJECT(m_pParentWidget), "InterimWindowGlue", m_xInterimGlue.get());
16687 if (!m_bAllowCycleFocusOut)
16689 g_signal_connect(G_OBJECT(m_pParentWidget), "map", G_CALLBACK(signalMap), this);
16690 g_signal_connect(G_OBJECT(m_pParentWidget), "unmap", G_CALLBACK(signalUnmap), this);
16695 void GenerateMissingMnemonics()
16697 MnemonicGenerator aMnemonicGenerator('_');
16698 for (const auto a : m_aMnemonicButtons)
16699 aMnemonicGenerator.RegisterMnemonic(get_label(a));
16700 for (const auto a : m_aMnemonicLabels)
16701 aMnemonicGenerator.RegisterMnemonic(get_label(a));
16703 for (const auto a : m_aMnemonicButtons)
16705 OUString aLabel(get_label(a));
16706 OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
16707 if (aLabel == aNewLabel)
16708 continue;
16709 set_label(a, aNewLabel);
16711 for (const auto a : m_aMnemonicLabels)
16713 OUString aLabel(get_label(a));
16714 OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
16715 if (aLabel == aNewLabel)
16716 continue;
16717 set_label(a, aNewLabel);
16720 m_aMnemonicLabels.clear();
16721 m_aMnemonicButtons.clear();
16724 OString get_current_page_help_id()
16726 OString sPageHelpId;
16727 // check to see if there is a notebook called tabcontrol and get the
16728 // helpid for the current page of that
16729 std::unique_ptr<weld::Notebook> xNotebook(weld_notebook("tabcontrol"));
16730 if (xNotebook)
16732 if (GtkInstanceContainer* pPage = dynamic_cast<GtkInstanceContainer*>(xNotebook->get_page(xNotebook->get_current_page_ident())))
16734 GtkWidget* pContainer = pPage->getWidget();
16735 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pContainer));
16736 GList* pChild = g_list_first(pChildren);
16737 if (pChild)
16739 GtkWidget* pPageWidget = static_cast<GtkWidget*>(pChild->data);
16740 sPageHelpId = ::get_help_id(pPageWidget);
16742 g_list_free(pChildren);
16745 return sPageHelpId;
16748 virtual ~GtkInstanceBuilder() override
16750 g_slist_free(m_pObjectList);
16751 g_object_unref(m_pBuilder);
16753 if (m_xInterimGlue && !m_bAllowCycleFocusOut)
16754 AllowCycleFocusOut();
16756 m_xInterimGlue.disposeAndClear();
16759 //ideally we would have/use weld::Container add and explicitly
16760 //call add when we want to do this, but in the vcl impl the
16761 //parent has to be set when the child is created, so for the
16762 //gtk impl emulate this by doing this implicitly at weld time
16763 void auto_add_parentless_widgets_to_container(GtkWidget* pWidget)
16765 if (gtk_widget_get_toplevel(pWidget) == pWidget && !GTK_IS_POPOVER(pWidget) && !GTK_IS_WINDOW(pWidget))
16766 gtk_container_add(GTK_CONTAINER(m_pParentWidget), pWidget);
16769 virtual std::unique_ptr<weld::MessageDialog> weld_message_dialog(const OString &id) override
16771 GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(gtk_builder_get_object(m_pBuilder, id.getStr()));
16772 if (!pMessageDialog)
16773 return nullptr;
16774 gtk_window_set_transient_for(GTK_WINDOW(pMessageDialog), GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
16775 return std::make_unique<GtkInstanceMessageDialog>(pMessageDialog, this, true);
16778 virtual std::unique_ptr<weld::Assistant> weld_assistant(const OString &id) override
16780 GtkAssistant* pAssistant = GTK_ASSISTANT(gtk_builder_get_object(m_pBuilder, id.getStr()));
16781 if (!pAssistant)
16782 return nullptr;
16783 if (m_pParentWidget)
16784 gtk_window_set_transient_for(GTK_WINDOW(pAssistant), GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
16785 return std::make_unique<GtkInstanceAssistant>(pAssistant, this, true);
16788 virtual std::unique_ptr<weld::Dialog> weld_dialog(const OString &id) override
16790 GtkWindow* pDialog = GTK_WINDOW(gtk_builder_get_object(m_pBuilder, id.getStr()));
16791 if (!pDialog)
16792 return nullptr;
16793 if (m_pParentWidget)
16794 gtk_window_set_transient_for(pDialog, GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
16795 return std::make_unique<GtkInstanceDialog>(pDialog, this, true);
16798 virtual std::unique_ptr<weld::Window> create_screenshot_window() override
16800 GtkWidget* pTopLevel = nullptr;
16802 for (GSList* l = m_pObjectList; l; l = g_slist_next(l))
16804 GObject* pObj = static_cast<GObject*>(l->data);
16806 if (!GTK_IS_WIDGET(pObj) || gtk_widget_get_parent(GTK_WIDGET(pObj)))
16807 continue;
16809 if (!pTopLevel)
16810 pTopLevel = GTK_WIDGET(pObj);
16811 else if (GTK_IS_WINDOW(pObj))
16812 pTopLevel = GTK_WIDGET(pObj);
16815 if (!pTopLevel)
16816 return nullptr;
16818 GtkWindow* pDialog;
16819 if (GTK_IS_WINDOW(pTopLevel))
16820 pDialog = GTK_WINDOW(pTopLevel);
16821 else
16823 pDialog = GTK_WINDOW(gtk_dialog_new());
16824 ::set_help_id(GTK_WIDGET(pDialog), ::get_help_id(pTopLevel));
16826 GtkWidget* pContentArea = gtk_dialog_get_content_area(GTK_DIALOG(pDialog));
16827 gtk_container_add(GTK_CONTAINER(pContentArea), pTopLevel);
16828 gtk_widget_show_all(pTopLevel);
16831 if (m_pParentWidget)
16832 gtk_window_set_transient_for(pDialog, GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
16833 return std::make_unique<GtkInstanceDialog>(pDialog, this, true);
16836 virtual std::unique_ptr<weld::Widget> weld_widget(const OString &id) override
16838 GtkWidget* pWidget = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, id.getStr()));
16839 if (!pWidget)
16840 return nullptr;
16841 auto_add_parentless_widgets_to_container(pWidget);
16842 return std::make_unique<GtkInstanceWidget>(pWidget, this, false);
16845 virtual std::unique_ptr<weld::Container> weld_container(const OString &id) override
16847 GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, id.getStr()));
16848 if (!pContainer)
16849 return nullptr;
16850 auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
16851 return std::make_unique<GtkInstanceContainer>(pContainer, this, false);
16854 virtual std::unique_ptr<weld::Box> weld_box(const OString &id) override
16856 GtkBox* pBox = GTK_BOX(gtk_builder_get_object(m_pBuilder, id.getStr()));
16857 if (!pBox)
16858 return nullptr;
16859 auto_add_parentless_widgets_to_container(GTK_WIDGET(pBox));
16860 return std::make_unique<GtkInstanceBox>(pBox, this, false);
16863 virtual std::unique_ptr<weld::Paned> weld_paned(const OString &id) override
16865 GtkPaned* pPaned = GTK_PANED(gtk_builder_get_object(m_pBuilder, id.getStr()));
16866 if (!pPaned)
16867 return nullptr;
16868 auto_add_parentless_widgets_to_container(GTK_WIDGET(pPaned));
16869 return std::make_unique<GtkInstancePaned>(pPaned, this, false);
16872 virtual std::unique_ptr<weld::Frame> weld_frame(const OString &id) override
16874 GtkFrame* pFrame = GTK_FRAME(gtk_builder_get_object(m_pBuilder, id.getStr()));
16875 if (!pFrame)
16876 return nullptr;
16877 auto_add_parentless_widgets_to_container(GTK_WIDGET(pFrame));
16878 return std::make_unique<GtkInstanceFrame>(pFrame, this, false);
16881 virtual std::unique_ptr<weld::ScrolledWindow> weld_scrolled_window(const OString &id, bool bUserManagedScrolling = false) override
16883 GtkScrolledWindow* pScrolledWindow = GTK_SCROLLED_WINDOW(gtk_builder_get_object(m_pBuilder, id.getStr()));
16884 if (!pScrolledWindow)
16885 return nullptr;
16886 auto_add_parentless_widgets_to_container(GTK_WIDGET(pScrolledWindow));
16887 return std::make_unique<GtkInstanceScrolledWindow>(pScrolledWindow, this, false, bUserManagedScrolling);
16890 virtual std::unique_ptr<weld::Notebook> weld_notebook(const OString &id) override
16892 GtkNotebook* pNotebook = GTK_NOTEBOOK(gtk_builder_get_object(m_pBuilder, id.getStr()));
16893 if (!pNotebook)
16894 return nullptr;
16895 auto_add_parentless_widgets_to_container(GTK_WIDGET(pNotebook));
16896 return std::make_unique<GtkInstanceNotebook>(pNotebook, this, false);
16899 virtual std::unique_ptr<weld::Button> weld_button(const OString &id) override
16901 GtkButton* pButton = GTK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
16902 if (!pButton)
16903 return nullptr;
16904 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
16905 return std::make_unique<GtkInstanceButton>(pButton, this, false);
16908 virtual std::unique_ptr<weld::MenuButton> weld_menu_button(const OString &id) override
16910 GtkMenuButton* pButton = GTK_MENU_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
16911 if (!pButton)
16912 return nullptr;
16913 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
16914 return std::make_unique<GtkInstanceMenuButton>(pButton, nullptr, this, false);
16917 virtual std::unique_ptr<weld::MenuToggleButton> weld_menu_toggle_button(const OString &id) override
16919 GtkMenuButton* pButton = GTK_MENU_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
16920 if (!pButton)
16921 return nullptr;
16922 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
16923 // gtk doesn't come with exactly the the concept
16924 GtkBuilder* pMenuToggleButton = makeMenuToggleButtonBuilder();
16925 return std::make_unique<GtkInstanceMenuToggleButton>(pMenuToggleButton, pButton, this, false);
16928 virtual std::unique_ptr<weld::LinkButton> weld_link_button(const OString &id) override
16930 GtkLinkButton* pButton = GTK_LINK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
16931 if (!pButton)
16932 return nullptr;
16933 auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
16934 return std::make_unique<GtkInstanceLinkButton>(pButton, this, false);
16937 virtual std::unique_ptr<weld::ToggleButton> weld_toggle_button(const OString &id) override
16939 GtkToggleButton* pToggleButton = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
16940 if (!pToggleButton)
16941 return nullptr;
16942 auto_add_parentless_widgets_to_container(GTK_WIDGET(pToggleButton));
16943 return std::make_unique<GtkInstanceToggleButton>(pToggleButton, this, false);
16946 virtual std::unique_ptr<weld::RadioButton> weld_radio_button(const OString &id) override
16948 GtkRadioButton* pRadioButton = GTK_RADIO_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
16949 if (!pRadioButton)
16950 return nullptr;
16951 auto_add_parentless_widgets_to_container(GTK_WIDGET(pRadioButton));
16952 return std::make_unique<GtkInstanceRadioButton>(pRadioButton, this, false);
16955 virtual std::unique_ptr<weld::CheckButton> weld_check_button(const OString &id) override
16957 GtkCheckButton* pCheckButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
16958 if (!pCheckButton)
16959 return nullptr;
16960 auto_add_parentless_widgets_to_container(GTK_WIDGET(pCheckButton));
16961 return std::make_unique<GtkInstanceCheckButton>(pCheckButton, this, false);
16964 virtual std::unique_ptr<weld::Scale> weld_scale(const OString &id) override
16966 GtkScale* pScale = GTK_SCALE(gtk_builder_get_object(m_pBuilder, id.getStr()));
16967 if (!pScale)
16968 return nullptr;
16969 auto_add_parentless_widgets_to_container(GTK_WIDGET(pScale));
16970 return std::make_unique<GtkInstanceScale>(pScale, this, false);
16973 virtual std::unique_ptr<weld::ProgressBar> weld_progress_bar(const OString &id) override
16975 GtkProgressBar* pProgressBar = GTK_PROGRESS_BAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
16976 if (!pProgressBar)
16977 return nullptr;
16978 auto_add_parentless_widgets_to_container(GTK_WIDGET(pProgressBar));
16979 return std::make_unique<GtkInstanceProgressBar>(pProgressBar, this, false);
16982 virtual std::unique_ptr<weld::Spinner> weld_spinner(const OString &id) override
16984 GtkSpinner* pSpinner = GTK_SPINNER(gtk_builder_get_object(m_pBuilder, id.getStr()));
16985 if (!pSpinner)
16986 return nullptr;
16987 auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinner));
16988 return std::make_unique<GtkInstanceSpinner>(pSpinner, this, false);
16991 virtual std::unique_ptr<weld::Image> weld_image(const OString &id) override
16993 GtkImage* pImage = GTK_IMAGE(gtk_builder_get_object(m_pBuilder, id.getStr()));
16994 if (!pImage)
16995 return nullptr;
16996 auto_add_parentless_widgets_to_container(GTK_WIDGET(pImage));
16997 return std::make_unique<GtkInstanceImage>(pImage, this, false);
17000 virtual std::unique_ptr<weld::Calendar> weld_calendar(const OString &id) override
17002 GtkCalendar* pCalendar = GTK_CALENDAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
17003 if (!pCalendar)
17004 return nullptr;
17005 auto_add_parentless_widgets_to_container(GTK_WIDGET(pCalendar));
17006 return std::make_unique<GtkInstanceCalendar>(pCalendar, this, false);
17009 virtual std::unique_ptr<weld::Entry> weld_entry(const OString &id) override
17011 GtkEntry* pEntry = GTK_ENTRY(gtk_builder_get_object(m_pBuilder, id.getStr()));
17012 if (!pEntry)
17013 return nullptr;
17014 auto_add_parentless_widgets_to_container(GTK_WIDGET(pEntry));
17015 return std::make_unique<GtkInstanceEntry>(pEntry, this, false);
17018 virtual std::unique_ptr<weld::SpinButton> weld_spin_button(const OString &id) override
17020 GtkSpinButton* pSpinButton = GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
17021 if (!pSpinButton)
17022 return nullptr;
17023 auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton));
17024 return std::make_unique<GtkInstanceSpinButton>(pSpinButton, this, false);
17027 virtual std::unique_ptr<weld::MetricSpinButton> weld_metric_spin_button(const OString& id, FieldUnit eUnit) override
17029 return std::make_unique<weld::MetricSpinButton>(weld_spin_button(id), eUnit);
17032 virtual std::unique_ptr<weld::FormattedSpinButton> weld_formatted_spin_button(const OString &id) override
17034 GtkSpinButton* pSpinButton = GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
17035 if (!pSpinButton)
17036 return nullptr;
17037 auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton));
17038 return std::make_unique<GtkInstanceFormattedSpinButton>(pSpinButton, this, false);
17041 virtual std::unique_ptr<weld::ComboBox> weld_combo_box(const OString &id) override
17043 GtkComboBox* pComboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_pBuilder, id.getStr()));
17044 if (!pComboBox)
17045 return nullptr;
17046 auto_add_parentless_widgets_to_container(GTK_WIDGET(pComboBox));
17048 /* we replace GtkComboBox because of difficulties with too tall menus
17050 1) https://gitlab.gnome.org/GNOME/gtk/issues/1910
17051 has_entry long menus take forever to appear (tdf#125388)
17053 on measuring each row, the GtkComboBox GtkTreeMenu will call
17054 its area_apply_attributes_cb function on the row, but that calls
17055 gtk_tree_menu_get_path_item which then loops through each child of the
17056 menu looking for the widget of the row, so performance drops to useless.
17058 All area_apply_attributes_cb does it set menu item sensitivity, so block it from running
17059 with fragile hackery which assumes that the unwanted callback is the only one with a
17061 2) https://gitlab.gnome.org/GNOME/gtk/issues/94
17062 when a super tall combobox menu is activated, and the selected
17063 entry is sufficiently far down the list, then the menu doesn't
17064 appear under wayland
17066 3) https://gitlab.gnome.org/GNOME/gtk/issues/310
17067 no typeahead support
17069 4) we want to be able to control the width of the button, but have a drop down menu which
17070 is not limited to the width of the button
17072 5) https://bugs.documentfoundation.org/show_bug.cgi?id=131120
17073 super tall menu doesn't appear under X sometimes
17075 GtkBuilder* pComboBuilder = makeComboBoxBuilder();
17076 return std::make_unique<GtkInstanceComboBox>(pComboBuilder, pComboBox, this, false);
17079 virtual std::unique_ptr<weld::TreeView> weld_tree_view(const OString &id) override
17081 GtkTreeView* pTreeView = GTK_TREE_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
17082 if (!pTreeView)
17083 return nullptr;
17084 auto_add_parentless_widgets_to_container(GTK_WIDGET(pTreeView));
17085 return std::make_unique<GtkInstanceTreeView>(pTreeView, this, false);
17088 virtual std::unique_ptr<weld::IconView> weld_icon_view(const OString &id) override
17090 GtkIconView* pIconView = GTK_ICON_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
17091 if (!pIconView)
17092 return nullptr;
17093 auto_add_parentless_widgets_to_container(GTK_WIDGET(pIconView));
17094 return std::make_unique<GtkInstanceIconView>(pIconView, this, false);
17097 virtual std::unique_ptr<weld::EntryTreeView> weld_entry_tree_view(const OString& containerid, const OString& entryid, const OString& treeviewid) override
17099 GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, containerid.getStr()));
17100 if (!pContainer)
17101 return nullptr;
17102 auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
17103 return std::make_unique<GtkInstanceEntryTreeView>(pContainer, this, false,
17104 weld_entry(entryid),
17105 weld_tree_view(treeviewid));
17108 virtual std::unique_ptr<weld::Label> weld_label(const OString &id) override
17110 GtkLabel* pLabel = GTK_LABEL(gtk_builder_get_object(m_pBuilder, id.getStr()));
17111 if (!pLabel)
17112 return nullptr;
17113 auto_add_parentless_widgets_to_container(GTK_WIDGET(pLabel));
17114 return std::make_unique<GtkInstanceLabel>(pLabel, this, false);
17117 virtual std::unique_ptr<weld::TextView> weld_text_view(const OString &id) override
17119 GtkTextView* pTextView = GTK_TEXT_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
17120 if (!pTextView)
17121 return nullptr;
17122 auto_add_parentless_widgets_to_container(GTK_WIDGET(pTextView));
17123 return std::make_unique<GtkInstanceTextView>(pTextView, this, false);
17126 virtual std::unique_ptr<weld::Expander> weld_expander(const OString &id) override
17128 GtkExpander* pExpander = GTK_EXPANDER(gtk_builder_get_object(m_pBuilder, id.getStr()));
17129 if (!pExpander)
17130 return nullptr;
17131 auto_add_parentless_widgets_to_container(GTK_WIDGET(pExpander));
17132 return std::make_unique<GtkInstanceExpander>(pExpander, this, false);
17135 virtual std::unique_ptr<weld::DrawingArea> weld_drawing_area(const OString &id, const a11yref& rA11y,
17136 FactoryFunction /*pUITestFactoryFunction*/, void* /*pUserData*/) override
17138 GtkDrawingArea* pDrawingArea = GTK_DRAWING_AREA(gtk_builder_get_object(m_pBuilder, id.getStr()));
17139 if (!pDrawingArea)
17140 return nullptr;
17141 auto_add_parentless_widgets_to_container(GTK_WIDGET(pDrawingArea));
17142 return std::make_unique<GtkInstanceDrawingArea>(pDrawingArea, this, rA11y, false);
17145 virtual std::unique_ptr<weld::Menu> weld_menu(const OString &id) override
17147 GtkMenu* pMenu = GTK_MENU(gtk_builder_get_object(m_pBuilder, id.getStr()));
17148 if (!pMenu)
17149 return nullptr;
17150 return std::make_unique<GtkInstanceMenu>(pMenu, true);
17153 virtual std::unique_ptr<weld::Toolbar> weld_toolbar(const OString &id) override
17155 GtkToolbar* pToolbar = GTK_TOOLBAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
17156 if (!pToolbar)
17157 return nullptr;
17158 auto_add_parentless_widgets_to_container(GTK_WIDGET(pToolbar));
17159 return std::make_unique<GtkInstanceToolbar>(pToolbar, this, false);
17162 virtual std::unique_ptr<weld::SizeGroup> create_size_group() override
17164 return std::make_unique<GtkInstanceSizeGroup>();
17170 void GtkInstanceWindow::help()
17172 //show help for widget with keyboard focus
17173 GtkWidget* pWidget = gtk_window_get_focus(m_pWindow);
17174 if (!pWidget)
17175 pWidget = GTK_WIDGET(m_pWindow);
17176 OString sHelpId = ::get_help_id(pWidget);
17177 while (sHelpId.isEmpty())
17179 pWidget = gtk_widget_get_parent(pWidget);
17180 if (!pWidget)
17181 break;
17182 sHelpId = ::get_help_id(pWidget);
17184 std::unique_ptr<weld::Widget> xTemp(pWidget != m_pWidget ? new GtkInstanceWidget(pWidget, m_pBuilder, false) : nullptr);
17185 weld::Widget* pSource = xTemp ? xTemp.get() : this;
17186 bool bRunNormalHelpRequest = !m_aHelpRequestHdl.IsSet() || m_aHelpRequestHdl.Call(*pSource);
17187 Help* pHelp = bRunNormalHelpRequest ? Application::GetHelp() : nullptr;
17188 if (!pHelp)
17189 return;
17191 // tdf#126007, there's a nice fallback route for offline help where
17192 // the current page of a notebook will get checked when the help
17193 // button is pressed and there was no help for the dialog found.
17195 // But for online help that route doesn't get taken, so bodge this here
17196 // by using the page help id if available and if the help button itself
17197 // was the original id
17198 if (m_pBuilder && sHelpId.endsWith("/help"))
17200 OString sPageId = m_pBuilder->get_current_page_help_id();
17201 if (!sPageId.isEmpty())
17202 sHelpId = sPageId;
17203 else
17205 // tdf#129068 likewise the help for the wrapping dialog is less
17206 // helpful than the help for the content area could be
17207 GtkContainer* pContainer = nullptr;
17208 if (GTK_IS_DIALOG(m_pWindow))
17209 pContainer = GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pWindow)));
17210 else if (GTK_IS_ASSISTANT(m_pWindow))
17212 GtkAssistant* pAssistant = GTK_ASSISTANT(m_pWindow);
17213 pContainer = GTK_CONTAINER(gtk_assistant_get_nth_page(pAssistant, gtk_assistant_get_current_page(pAssistant)));
17215 if (pContainer)
17217 GList* pChildren = gtk_container_get_children(pContainer);
17218 GList* pChild = g_list_first(pChildren);
17219 if (pChild)
17221 GtkWidget* pContentWidget = static_cast<GtkWidget*>(pChild->data);
17222 sHelpId = ::get_help_id(pContentWidget);
17224 g_list_free(pChildren);
17228 pHelp->Start(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), pSource);
17231 //iterate upwards through the hierarchy from this widgets through its parents
17232 //calling func with their helpid until func returns true or we run out of parents
17233 void GtkInstanceWidget::help_hierarchy_foreach(const std::function<bool(const OString&)>& func)
17235 GtkWidget* pParent = m_pWidget;
17236 while ((pParent = gtk_widget_get_parent(pParent)))
17238 if (func(::get_help_id(pParent)))
17239 return;
17243 weld::Builder* GtkInstance::CreateBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile)
17245 GtkInstanceWidget* pParentWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
17246 if (pParent && !pParentWidget) //remove when complete
17247 return SalInstance::CreateBuilder(pParent, rUIRoot, rUIFile);
17248 GtkWidget* pBuilderParent = pParentWidget ? pParentWidget->getWidget() : nullptr;
17249 return new GtkInstanceBuilder(pBuilderParent, rUIRoot, rUIFile, nullptr, true);
17252 // tdf#135965 for the case of native widgets inside a GtkSalFrame and F1 pressed, run help
17253 // on gtk widget help ids until we hit a vcl parent and then use vcl window help ids
17254 gboolean GtkSalFrame::NativeWidgetHelpPressed(GtkAccelGroup*, GObject*, guint, GdkModifierType, gpointer pFrame)
17256 Help* pHelp = Application::GetHelp();
17257 if (!pHelp)
17258 return true;
17260 GtkWindow* pWindow = static_cast<GtkWindow*>(pFrame);
17262 vcl::Window* pChildWindow = nullptr;
17264 //show help for widget with keyboard focus
17265 GtkWidget* pWidget = gtk_window_get_focus(pWindow);
17266 if (!pWidget)
17267 pWidget = GTK_WIDGET(pWindow);
17268 OString sHelpId = ::get_help_id(pWidget);
17269 while (sHelpId.isEmpty())
17271 pWidget = gtk_widget_get_parent(pWidget);
17272 if (!pWidget)
17273 break;
17274 pChildWindow = static_cast<vcl::Window*>(g_object_get_data(G_OBJECT(pWidget), "InterimWindowGlue"));
17275 if (pChildWindow)
17277 sHelpId = pChildWindow->GetHelpId();
17278 break;
17280 sHelpId = ::get_help_id(pWidget);
17283 if (pChildWindow)
17285 while (sHelpId.isEmpty())
17287 pChildWindow = pChildWindow->GetParent();
17288 if (!pChildWindow)
17289 break;
17290 sHelpId = pChildWindow->GetHelpId();
17292 if (!pChildWindow)
17293 return true;
17294 pHelp->Start(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), pChildWindow);
17295 return true;
17298 if (!pWidget)
17299 return true;
17300 std::unique_ptr<weld::Widget> xTemp(new GtkInstanceWidget(pWidget, nullptr, false));
17301 pHelp->Start(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), xTemp.get());
17302 return true;
17305 weld::Builder* GtkInstance::CreateInterimBuilder(vcl::Window* pParent, const OUString& rUIRoot, const OUString& rUIFile,
17306 bool bAllowCycleFocusOut, sal_uInt64)
17308 // Create a foreign window which we know is a GtkGrid and make the native widgets a child of that, so we can
17309 // support GtkWidgets within a vcl::Window
17310 SystemWindowData winData = {};
17311 winData.bClipUsingNativeWidget = true;
17312 auto xEmbedWindow = VclPtr<SystemChildWindow>::Create(pParent, 0, &winData, false);
17313 xEmbedWindow->Show(true, ShowFlags::NoActivate);
17314 xEmbedWindow->set_expand(true);
17316 const SystemEnvData* pEnvData = xEmbedWindow->GetSystemData();
17317 if (!pEnvData)
17318 return nullptr;
17320 GtkWidget *pWindow = static_cast<GtkWidget*>(pEnvData->pWidget);
17321 gtk_widget_show_all(pWindow);
17323 // build the widget tree as a child of the GtkEventBox GtkGrid parent
17324 return new GtkInstanceBuilder(pWindow, rUIRoot, rUIFile, xEmbedWindow.get(), bAllowCycleFocusOut);
17327 weld::MessageDialog* GtkInstance::CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType, VclButtonsType eButtonsType, const OUString &rPrimaryMessage)
17329 GtkInstanceWidget* pParentInstance = dynamic_cast<GtkInstanceWidget*>(pParent);
17330 GtkWindow* pParentWindow = pParentInstance ? pParentInstance->getWindow() : nullptr;
17331 GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(pParentWindow, GTK_DIALOG_MODAL,
17332 VclToGtk(eMessageType), VclToGtk(eButtonsType), "%s",
17333 OUStringToOString(rPrimaryMessage, RTL_TEXTENCODING_UTF8).getStr()));
17334 return new GtkInstanceMessageDialog(pMessageDialog, nullptr, true);
17337 weld::Window* GtkInstance::GetFrameWeld(const css::uno::Reference<css::awt::XWindow>& rWindow)
17339 if (SalGtkXWindow* pGtkXWindow = dynamic_cast<SalGtkXWindow*>(rWindow.get()))
17340 return pGtkXWindow->getFrameWeld();
17341 return SalInstance::GetFrameWeld(rWindow);
17344 weld::Window* GtkSalFrame::GetFrameWeld() const
17346 if (!m_xFrameWeld)
17347 m_xFrameWeld.reset(new GtkInstanceWindow(GTK_WINDOW(gtk_widget_get_toplevel(getWindow())), nullptr, false));
17348 return m_xFrameWeld.get();
17351 void* GtkInstance::CreateGStreamerSink(const SystemChildWindow *pWindow)
17353 #if ENABLE_GSTREAMER_1_0
17354 auto aSymbol = gstElementFactoryNameSymbol();
17355 if (!aSymbol)
17356 return nullptr;
17358 const SystemEnvData* pEnvData = pWindow->GetSystemData();
17359 if (!pEnvData)
17360 return nullptr;
17362 GstElement* pVideosink = aSymbol("gtksink", "gtksink");
17363 if (!pVideosink)
17364 return nullptr;
17366 GtkWidget *pGstWidget;
17367 g_object_get(pVideosink, "widget", &pGstWidget, nullptr);
17368 gtk_widget_set_vexpand(pGstWidget, true);
17369 gtk_widget_set_hexpand(pGstWidget, true);
17371 GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
17372 gtk_container_add(GTK_CONTAINER(pParent), pGstWidget);
17373 g_object_unref(pGstWidget);
17374 gtk_widget_show_all(pParent);
17376 return pVideosink;
17377 #else
17378 (void)pWindow;
17379 return nullptr;
17380 #endif
17383 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */