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