bump product version to 7.6.3.2-android
[LibreOffice.git] / vcl / unx / gtk3 / gtkframe.cxx
blob6152d64bd86e2bf76ee2f62217b2a20a3860047c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <unx/gtk/gtkframe.hxx>
21 #include <unx/gtk/gtkdata.hxx>
22 #include <unx/gtk/gtkinst.hxx>
23 #include <unx/gtk/gtkgdi.hxx>
24 #include <unx/gtk/gtksalmenu.hxx>
25 #include <unx/gtk/hudawareness.h>
26 #include <vcl/event.hxx>
27 #include <vcl/i18nhelp.hxx>
28 #include <vcl/keycodes.hxx>
29 #include <unx/geninst.h>
30 #include <headless/svpgdi.hxx>
31 #include <sal/log.hxx>
32 #include <comphelper/diagnose_ex.hxx>
33 #include <vcl/toolkit/floatwin.hxx>
34 #include <vcl/toolkit/unowrap.hxx>
35 #include <vcl/svapp.hxx>
36 #include <vcl/weld.hxx>
37 #include <vcl/window.hxx>
38 #include <vcl/settings.hxx>
40 #include <gtk/gtk.h>
42 #include <X11/Xlib.h>
43 #include <X11/Xutil.h>
44 #include <unx/gtk/gtkbackend.hxx>
46 #include <strings.hrc>
47 #include <window.h>
49 #include <basegfx/vector/b2ivector.hxx>
50 #include <officecfg/Office/Common.hxx>
52 #include <dlfcn.h>
54 #include <algorithm>
56 #if OSL_DEBUG_LEVEL > 1
57 # include <cstdio>
58 #endif
60 #include <i18nlangtag/mslangid.hxx>
62 #include <cstdlib>
63 #include <cmath>
65 #include <com/sun/star/awt/MouseButton.hpp>
66 #include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
67 #include <com/sun/star/frame/Desktop.hpp>
68 #include <com/sun/star/util/XModifiable.hpp>
70 #if !GTK_CHECK_VERSION(4, 0, 0)
71 # define GDK_ALT_MASK GDK_MOD1_MASK
72 # define GDK_TOPLEVEL_STATE_MAXIMIZED GDK_WINDOW_STATE_MAXIMIZED
73 # define GDK_TOPLEVEL_STATE_MINIMIZED GDK_WINDOW_STATE_ICONIFIED
74 # define gdk_wayland_surface_get_wl_surface gdk_wayland_window_get_wl_surface
75 # define gdk_x11_surface_get_xid gdk_x11_window_get_xid
76 #endif
78 using namespace com::sun::star;
80 int GtkSalFrame::m_nFloats = 0;
82 static GDBusConnection* pSessionBus = nullptr;
84 static void EnsureSessionBus()
86 if (!pSessionBus)
87 pSessionBus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
90 sal_uInt16 GtkSalFrame::GetKeyModCode( guint state )
92 sal_uInt16 nCode = 0;
93 if( state & GDK_SHIFT_MASK )
94 nCode |= KEY_SHIFT;
95 if( state & GDK_CONTROL_MASK )
96 nCode |= KEY_MOD1;
97 if (state & GDK_ALT_MASK)
98 nCode |= KEY_MOD2;
99 if( state & GDK_SUPER_MASK )
100 nCode |= KEY_MOD3;
101 return nCode;
104 sal_uInt16 GtkSalFrame::GetMouseModCode( guint state )
106 sal_uInt16 nCode = GetKeyModCode( state );
107 if( state & GDK_BUTTON1_MASK )
108 nCode |= MOUSE_LEFT;
109 if( state & GDK_BUTTON2_MASK )
110 nCode |= MOUSE_MIDDLE;
111 if( state & GDK_BUTTON3_MASK )
112 nCode |= MOUSE_RIGHT;
114 return nCode;
117 // KEY_F26 is the last function key known to keycodes.hxx
118 static bool IsFunctionKeyVal(guint keyval)
120 return keyval >= GDK_KEY_F1 && keyval <= GDK_KEY_F26;
123 sal_uInt16 GtkSalFrame::GetKeyCode(guint keyval)
125 sal_uInt16 nCode = 0;
126 if( keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9 )
127 nCode = KEY_0 + (keyval-GDK_KEY_0);
128 else if( keyval >= GDK_KEY_KP_0 && keyval <= GDK_KEY_KP_9 )
129 nCode = KEY_0 + (keyval-GDK_KEY_KP_0);
130 else if( keyval >= GDK_KEY_A && keyval <= GDK_KEY_Z )
131 nCode = KEY_A + (keyval-GDK_KEY_A );
132 else if( keyval >= GDK_KEY_a && keyval <= GDK_KEY_z )
133 nCode = KEY_A + (keyval-GDK_KEY_a );
134 else if (IsFunctionKeyVal(keyval))
136 switch( keyval )
138 // - - - - - Sun keyboard, see vcl/unx/source/app/saldisp.cxx
139 // although GDK_KEY_F1 ... GDK_KEY_L10 are known to
140 // gdk/gdkkeysyms.h, they are unlikely to be generated
141 // except possibly by Solaris systems
142 // this whole section needs review
143 case GDK_KEY_L2:
144 nCode = KEY_F12;
145 break;
146 case GDK_KEY_L3: nCode = KEY_PROPERTIES; break;
147 case GDK_KEY_L4: nCode = KEY_UNDO; break;
148 case GDK_KEY_L6: nCode = KEY_COPY; break; // KEY_F16
149 case GDK_KEY_L8: nCode = KEY_PASTE; break; // KEY_F18
150 case GDK_KEY_L10: nCode = KEY_CUT; break; // KEY_F20
151 default:
152 nCode = KEY_F1 + (keyval-GDK_KEY_F1); break;
155 else
157 switch( keyval )
159 case GDK_KEY_KP_Down:
160 case GDK_KEY_Down: nCode = KEY_DOWN; break;
161 case GDK_KEY_KP_Up:
162 case GDK_KEY_Up: nCode = KEY_UP; break;
163 case GDK_KEY_KP_Left:
164 case GDK_KEY_Left: nCode = KEY_LEFT; break;
165 case GDK_KEY_KP_Right:
166 case GDK_KEY_Right: nCode = KEY_RIGHT; break;
167 case GDK_KEY_KP_Begin:
168 case GDK_KEY_KP_Home:
169 case GDK_KEY_Begin:
170 case GDK_KEY_Home: nCode = KEY_HOME; break;
171 case GDK_KEY_KP_End:
172 case GDK_KEY_End: nCode = KEY_END; break;
173 case GDK_KEY_KP_Page_Up:
174 case GDK_KEY_Page_Up: nCode = KEY_PAGEUP; break;
175 case GDK_KEY_KP_Page_Down:
176 case GDK_KEY_Page_Down: nCode = KEY_PAGEDOWN; break;
177 case GDK_KEY_KP_Enter:
178 case GDK_KEY_Return: nCode = KEY_RETURN; break;
179 case GDK_KEY_Escape: nCode = KEY_ESCAPE; break;
180 case GDK_KEY_ISO_Left_Tab:
181 case GDK_KEY_KP_Tab:
182 case GDK_KEY_Tab: nCode = KEY_TAB; break;
183 case GDK_KEY_BackSpace: nCode = KEY_BACKSPACE; break;
184 case GDK_KEY_KP_Space:
185 case GDK_KEY_space: nCode = KEY_SPACE; break;
186 case GDK_KEY_KP_Insert:
187 case GDK_KEY_Insert: nCode = KEY_INSERT; break;
188 case GDK_KEY_KP_Delete:
189 case GDK_KEY_Delete: nCode = KEY_DELETE; break;
190 case GDK_KEY_plus:
191 case GDK_KEY_KP_Add: nCode = KEY_ADD; break;
192 case GDK_KEY_minus:
193 case GDK_KEY_KP_Subtract: nCode = KEY_SUBTRACT; break;
194 case GDK_KEY_asterisk:
195 case GDK_KEY_KP_Multiply: nCode = KEY_MULTIPLY; break;
196 case GDK_KEY_slash:
197 case GDK_KEY_KP_Divide: nCode = KEY_DIVIDE; break;
198 case GDK_KEY_period: nCode = KEY_POINT; break;
199 case GDK_KEY_decimalpoint: nCode = KEY_POINT; break;
200 case GDK_KEY_comma: nCode = KEY_COMMA; break;
201 case GDK_KEY_less: nCode = KEY_LESS; break;
202 case GDK_KEY_greater: nCode = KEY_GREATER; break;
203 case GDK_KEY_KP_Equal:
204 case GDK_KEY_equal: nCode = KEY_EQUAL; break;
205 case GDK_KEY_Find: nCode = KEY_FIND; break;
206 case GDK_KEY_Menu: nCode = KEY_CONTEXTMENU;break;
207 case GDK_KEY_Help: nCode = KEY_HELP; break;
208 case GDK_KEY_Undo: nCode = KEY_UNDO; break;
209 case GDK_KEY_Redo: nCode = KEY_REPEAT; break;
210 // on a sun keyboard this actually is usually SunXK_Stop = 0x0000FF69 (XK_Cancel),
211 // but VCL doesn't have a key definition for that
212 case GDK_KEY_Cancel: nCode = KEY_F11; break;
213 case GDK_KEY_KP_Decimal:
214 case GDK_KEY_KP_Separator: nCode = KEY_DECIMAL; break;
215 case GDK_KEY_asciitilde: nCode = KEY_TILDE; break;
216 case GDK_KEY_leftsinglequotemark:
217 case GDK_KEY_quoteleft: nCode = KEY_QUOTELEFT; break;
218 case GDK_KEY_bracketleft: nCode = KEY_BRACKETLEFT; break;
219 case GDK_KEY_bracketright: nCode = KEY_BRACKETRIGHT; break;
220 case GDK_KEY_semicolon: nCode = KEY_SEMICOLON; break;
221 case GDK_KEY_quoteright: nCode = KEY_QUOTERIGHT; break;
222 case GDK_KEY_braceright: nCode = KEY_RIGHTCURLYBRACKET; break;
223 case GDK_KEY_colon: nCode = KEY_COLON; break;
224 // some special cases, also see saldisp.cxx
225 // - - - - - - - - - - - - - Apollo - - - - - - - - - - - - - 0x1000
226 // These can be found in ap_keysym.h
227 case 0x1000FF02: // apXK_Copy
228 nCode = KEY_COPY;
229 break;
230 case 0x1000FF03: // apXK_Cut
231 nCode = KEY_CUT;
232 break;
233 case 0x1000FF04: // apXK_Paste
234 nCode = KEY_PASTE;
235 break;
236 case 0x1000FF14: // apXK_Repeat
237 nCode = KEY_REPEAT;
238 break;
239 // Exit, Save
240 // - - - - - - - - - - - - - - D E C - - - - - - - - - - - - - 0x1000
241 // These can be found in DECkeysym.h
242 case 0x1000FF00:
243 nCode = KEY_DELETE;
244 break;
245 // - - - - - - - - - - - - - - H P - - - - - - - - - - - - - 0x1000
246 // These can be found in HPkeysym.h
247 case 0x1000FF73: // hpXK_DeleteChar
248 nCode = KEY_DELETE;
249 break;
250 case 0x1000FF74: // hpXK_BackTab
251 case 0x1000FF75: // hpXK_KP_BackTab
252 nCode = KEY_TAB;
253 break;
254 // - - - - - - - - - - - - - - I B M - - - - - - - - - - - - -
255 // - - - - - - - - - - - - - - O S F - - - - - - - - - - - - - 0x1004
256 // These also can be found in HPkeysym.h
257 case 0x1004FF02: // osfXK_Copy
258 nCode = KEY_COPY;
259 break;
260 case 0x1004FF03: // osfXK_Cut
261 nCode = KEY_CUT;
262 break;
263 case 0x1004FF04: // osfXK_Paste
264 nCode = KEY_PASTE;
265 break;
266 case 0x1004FF07: // osfXK_BackTab
267 nCode = KEY_TAB;
268 break;
269 case 0x1004FF08: // osfXK_BackSpace
270 nCode = KEY_BACKSPACE;
271 break;
272 case 0x1004FF1B: // osfXK_Escape
273 nCode = KEY_ESCAPE;
274 break;
275 // Up, Down, Left, Right, PageUp, PageDown
276 // - - - - - - - - - - - - - - S C O - - - - - - - - - - - - -
277 // - - - - - - - - - - - - - - S G I - - - - - - - - - - - - - 0x1007
278 // - - - - - - - - - - - - - - S N I - - - - - - - - - - - - -
279 // - - - - - - - - - - - - - - S U N - - - - - - - - - - - - - 0x1005
280 // These can be found in Sunkeysym.h
281 case 0x1005FF10: // SunXK_F36
282 nCode = KEY_F11;
283 break;
284 case 0x1005FF11: // SunXK_F37
285 nCode = KEY_F12;
286 break;
287 case 0x1005FF70: // SunXK_Props
288 nCode = KEY_PROPERTIES;
289 break;
290 case 0x1005FF71: // SunXK_Front
291 nCode = KEY_FRONT;
292 break;
293 case 0x1005FF72: // SunXK_Copy
294 nCode = KEY_COPY;
295 break;
296 case 0x1005FF73: // SunXK_Open
297 nCode = KEY_OPEN;
298 break;
299 case 0x1005FF74: // SunXK_Paste
300 nCode = KEY_PASTE;
301 break;
302 case 0x1005FF75: // SunXK_Cut
303 nCode = KEY_CUT;
304 break;
305 // - - - - - - - - - - - - - X F 8 6 - - - - - - - - - - - - - 0x1008
306 // These can be found in XF86keysym.h
307 // but more importantly they are also available GTK/Gdk version 3
308 // and hence are already provided in gdk/gdkkeysyms.h, and hence
309 // in gdk/gdk.h
310 case GDK_KEY_Copy: nCode = KEY_COPY; break; // 0x1008ff57
311 case GDK_KEY_Cut: nCode = KEY_CUT; break; // 0x1008ff58
312 case GDK_KEY_Open: nCode = KEY_OPEN; break; // 0x1008ff6b
313 case GDK_KEY_Paste: nCode = KEY_PASTE; break; // 0x1008ff6d
317 return nCode;
320 #if !GTK_CHECK_VERSION(4, 0, 0)
321 guint GtkSalFrame::GetKeyValFor(GdkKeymap* pKeyMap, guint16 hardware_keycode, guint8 group)
323 guint updated_keyval = 0;
324 gdk_keymap_translate_keyboard_state(pKeyMap, hardware_keycode,
325 GdkModifierType(0), group, &updated_keyval, nullptr, nullptr, nullptr);
326 return updated_keyval;
328 #endif
330 namespace {
332 // F10 means either KEY_F10 or KEY_MENU, which has to be decided
333 // in the independent part.
334 struct KeyAlternate
336 sal_uInt16 nKeyCode;
337 sal_Unicode nCharCode;
338 KeyAlternate() : nKeyCode( 0 ), nCharCode( 0 ) {}
339 KeyAlternate( sal_uInt16 nKey, sal_Unicode nChar = 0 ) : nKeyCode( nKey ), nCharCode( nChar ) {}
344 static KeyAlternate
345 GetAlternateKeyCode( const sal_uInt16 nKeyCode )
347 KeyAlternate aAlternate;
349 switch( nKeyCode )
351 case KEY_F10: aAlternate = KeyAlternate( KEY_MENU );break;
352 case KEY_F24: aAlternate = KeyAlternate( KEY_SUBTRACT, '-' );break;
355 return aAlternate;
358 #if OSL_DEBUG_LEVEL > 0
359 static bool dumpframes = false;
360 #endif
362 bool GtkSalFrame::doKeyCallback( guint state,
363 guint keyval,
364 guint16 hardware_keycode,
365 guint8 group,
366 sal_Unicode aOrigCode,
367 bool bDown,
368 bool bSendRelease
371 SalKeyEvent aEvent;
373 aEvent.mnCharCode = aOrigCode;
374 aEvent.mnRepeat = 0;
376 vcl::DeletionListener aDel( this );
378 #if OSL_DEBUG_LEVEL > 0
379 const char* pKeyDebug = getenv("VCL_GTK3_PAINTDEBUG");
381 if (pKeyDebug && *pKeyDebug == '1')
383 if (bDown)
385 // shift-zero forces a re-draw and event is swallowed
386 if (keyval == GDK_KEY_0)
388 SAL_INFO("vcl.gtk3", "force widget_queue_draw.");
389 gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
390 return false;
392 else if (keyval == GDK_KEY_1)
394 SAL_INFO("vcl.gtk3", "force repaint all.");
395 TriggerPaintEvent();
396 return false;
398 else if (keyval == GDK_KEY_2)
400 dumpframes = !dumpframes;
401 SAL_INFO("vcl.gtk3", "toggle dump frames to " << dumpframes);
402 return false;
406 #endif
409 * #i42122# translate all keys with Ctrl and/or Alt to group 0 else
410 * shortcuts (e.g. Ctrl-o) will not work but be inserted by the
411 * application
413 * #i52338# do this for all keys that the independent part has no key code
414 * for
416 * fdo#41169 rather than use group 0, detect if there is a group which can
417 * be used to input Latin text and use that if possible
419 aEvent.mnCode = GetKeyCode( keyval );
420 #if !GTK_CHECK_VERSION(4, 0, 0)
421 if( aEvent.mnCode == 0 )
423 gint best_group = SAL_MAX_INT32;
425 // Try and find Latin layout
426 GdkKeymap* keymap = gdk_keymap_get_default();
427 GdkKeymapKey *keys;
428 gint n_keys;
429 if (gdk_keymap_get_entries_for_keyval(keymap, GDK_KEY_A, &keys, &n_keys))
431 // Find the lowest group that supports Latin layout
432 for (gint i = 0; i < n_keys; ++i)
434 if (keys[i].level != 0 && keys[i].level != 1)
435 continue;
436 best_group = std::min(best_group, keys[i].group);
437 if (best_group == 0)
438 break;
440 g_free(keys);
443 //Unavailable, go with original group then I suppose
444 if (best_group == SAL_MAX_INT32)
445 best_group = group;
447 guint updated_keyval = GetKeyValFor(keymap, hardware_keycode, best_group);
448 aEvent.mnCode = GetKeyCode(updated_keyval);
450 #else
451 (void)hardware_keycode;
452 (void)group;
453 #endif
455 aEvent.mnCode |= GetKeyModCode( state );
457 bool bStopProcessingKey;
458 if (bDown)
460 // tdf#152404 Commit uncommitted text before dispatching key shortcuts. In
461 // certain cases such as pressing Control-Alt-C in a Writer document while
462 // there is uncommitted text will call GtkSalFrame::EndExtTextInput() which
463 // will dispatch a SalEvent::EndExtTextInput event. Writer's handler for that
464 // event will delete the uncommitted text and then insert the committed text
465 // but LibreOffice will crash when deleting the uncommitted text because
466 // deletion of the text also removes and deletes the newly inserted comment.
467 if (m_pIMHandler && !m_pIMHandler->m_aInputEvent.maText.isEmpty() && (aEvent.mnCode & (KEY_MOD1 | KEY_MOD2)))
468 m_pIMHandler->doCallEndExtTextInput();
470 bStopProcessingKey = CallCallbackExc(SalEvent::KeyInput, &aEvent);
471 // #i46889# copy AlternateKeyCode handling from generic plugin
472 if (!bStopProcessingKey)
474 KeyAlternate aAlternate = GetAlternateKeyCode( aEvent.mnCode );
475 if( aAlternate.nKeyCode )
477 aEvent.mnCode = aAlternate.nKeyCode;
478 if( aAlternate.nCharCode )
479 aEvent.mnCharCode = aAlternate.nCharCode;
480 bStopProcessingKey = CallCallbackExc(SalEvent::KeyInput, &aEvent);
483 if( bSendRelease && ! aDel.isDeleted() )
485 CallCallbackExc(SalEvent::KeyUp, &aEvent);
488 else
489 bStopProcessingKey = CallCallbackExc(SalEvent::KeyUp, &aEvent);
490 return bStopProcessingKey;
493 GtkSalFrame::GtkSalFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
494 : m_nXScreen( getDisplay()->GetDefaultXScreen() )
495 , m_pHeaderBar(nullptr)
496 , m_bGraphics(false)
497 , m_nSetFocusSignalId(0)
498 #if !GTK_CHECK_VERSION(4, 0, 0)
499 , m_aSmoothScrollIdle("GtkSalFrame m_aSmoothScrollIdle")
500 #endif
502 getDisplay()->registerFrame( this );
503 m_bDefaultPos = true;
504 m_bDefaultSize = ( (nStyle & SalFrameStyleFlags::SIZEABLE) && ! pParent );
505 Init( pParent, nStyle );
508 GtkSalFrame::GtkSalFrame( SystemParentData* pSysData )
509 : m_nXScreen( getDisplay()->GetDefaultXScreen() )
510 , m_pHeaderBar(nullptr)
511 , m_bGraphics(false)
512 , m_nSetFocusSignalId(0)
513 #if !GTK_CHECK_VERSION(4, 0, 0)
514 , m_aSmoothScrollIdle("GtkSalFrame m_aSmoothScrollIdle")
515 #endif
517 getDisplay()->registerFrame( this );
518 // permanently ignore errors from our unruly children ...
519 GetGenericUnixSalData()->ErrorTrapPush();
520 m_bDefaultPos = true;
521 m_bDefaultSize = true;
522 Init( pSysData );
525 // AppMenu watch functions.
527 static void ObjectDestroyedNotify( gpointer data )
529 if ( data ) {
530 g_object_unref( data );
534 #if !GTK_CHECK_VERSION(4,0,0)
535 static void hud_activated( gboolean hud_active, gpointer user_data )
537 if ( hud_active )
539 SolarMutexGuard aGuard;
540 GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
541 GtkSalMenu* pSalMenu = reinterpret_cast< GtkSalMenu* >( pSalFrame->GetMenu() );
543 if ( pSalMenu )
544 pSalMenu->UpdateFull();
547 #endif
549 static void attach_menu_model(GtkSalFrame* pSalFrame)
551 GtkWidget* pWidget = pSalFrame->getWindow();
552 GdkSurface* gdkWindow = widget_get_surface(pWidget);
554 if ( gdkWindow == nullptr || g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) != nullptr )
555 return;
557 // Create menu model and action group attached to this frame.
558 GMenuModel* pMenuModel = G_MENU_MODEL( g_lo_menu_new() );
559 GActionGroup* pActionGroup = reinterpret_cast<GActionGroup*>(g_lo_action_group_new());
561 // Set window properties.
562 g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-menubar", pMenuModel, ObjectDestroyedNotify );
563 g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-action-group", pActionGroup, ObjectDestroyedNotify );
565 #if !GTK_CHECK_VERSION(4,0,0)
566 // Get a DBus session connection.
567 EnsureSessionBus();
568 if (!pSessionBus)
569 return;
571 // Generate menu paths.
572 sal_uIntPtr windowId = GtkSalFrame::GetNativeWindowHandle(pWidget);
573 gchar* aDBusWindowPath = g_strdup_printf( "/org/libreoffice/window/%lu", windowId );
574 gchar* aDBusMenubarPath = g_strdup_printf( "/org/libreoffice/window/%lu/menus/menubar", windowId );
576 GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
577 #if defined(GDK_WINDOWING_X11)
578 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
580 gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_ID", "org.libreoffice" );
581 gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_MENUBAR_OBJECT_PATH", aDBusMenubarPath );
582 gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_WINDOW_OBJECT_PATH", aDBusWindowPath );
583 gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_OBJECT_PATH", "/org/libreoffice" );
584 gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_UNIQUE_BUS_NAME", g_dbus_connection_get_unique_name( pSessionBus ) );
586 #endif
587 #if defined(GDK_WINDOWING_WAYLAND)
588 if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
590 gdk_wayland_window_set_dbus_properties_libgtk_only(gdkWindow, "org.libreoffice",
591 nullptr,
592 aDBusMenubarPath,
593 aDBusWindowPath,
594 "/org/libreoffice",
595 g_dbus_connection_get_unique_name( pSessionBus ));
597 #endif
598 // Publish the menu model and the action group.
599 SAL_INFO("vcl.unity", "exporting menu model at " << pMenuModel << " for window " << windowId);
600 pSalFrame->m_nMenuExportId = g_dbus_connection_export_menu_model (pSessionBus, aDBusMenubarPath, pMenuModel, nullptr);
601 SAL_INFO("vcl.unity", "exporting action group at " << pActionGroup << " for window " << windowId);
602 pSalFrame->m_nActionGroupExportId = g_dbus_connection_export_action_group( pSessionBus, aDBusWindowPath, pActionGroup, nullptr);
603 pSalFrame->m_nHudAwarenessId = hud_awareness_register( pSessionBus, aDBusMenubarPath, hud_activated, pSalFrame, nullptr, nullptr );
605 g_free( aDBusWindowPath );
606 g_free( aDBusMenubarPath );
607 #endif
610 void on_registrar_available( GDBusConnection * /*connection*/,
611 const gchar * /*name*/,
612 const gchar * /*name_owner*/,
613 gpointer user_data )
615 SolarMutexGuard aGuard;
617 GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
619 SalMenu* pSalMenu = pSalFrame->GetMenu();
621 if ( pSalMenu != nullptr )
623 GtkSalMenu* pGtkSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
624 pGtkSalMenu->EnableUnity(true);
628 // This is called when the registrar becomes unavailable. It shows the menubar.
629 void on_registrar_unavailable( GDBusConnection * /*connection*/,
630 const gchar * /*name*/,
631 gpointer user_data )
633 SolarMutexGuard aGuard;
635 SAL_INFO("vcl.unity", "on_registrar_unavailable");
637 GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
639 SalMenu* pSalMenu = pSalFrame->GetMenu();
641 if ( pSalMenu ) {
642 GtkSalMenu* pGtkSalMenu = static_cast< GtkSalMenu* >( pSalMenu );
643 pGtkSalMenu->EnableUnity(false);
647 void GtkSalFrame::EnsureAppMenuWatch()
649 if ( m_nWatcherId )
650 return;
652 // Get a DBus session connection.
653 EnsureSessionBus();
654 if (!pSessionBus)
655 return;
657 // Publish the menu only if AppMenu registrar is available.
658 m_nWatcherId = g_bus_watch_name_on_connection( pSessionBus,
659 "com.canonical.AppMenu.Registrar",
660 G_BUS_NAME_WATCHER_FLAGS_NONE,
661 on_registrar_available,
662 on_registrar_unavailable,
663 this,
664 nullptr );
667 void GtkSalFrame::InvalidateGraphics()
669 if( m_pGraphics )
671 m_bGraphics = false;
675 GtkSalFrame::~GtkSalFrame()
677 #if !GTK_CHECK_VERSION(4,0,0)
678 m_aSmoothScrollIdle.Stop();
679 m_aSmoothScrollIdle.ClearInvokeHandler();
680 #endif
682 if (m_pDropTarget)
684 m_pDropTarget->deinitialize();
685 m_pDropTarget = nullptr;
688 if (m_pDragSource)
690 m_pDragSource->deinitialize();
691 m_pDragSource= nullptr;
694 InvalidateGraphics();
696 if (m_pParent)
698 m_pParent->m_aChildren.remove( this );
701 getDisplay()->deregisterFrame( this );
703 if( m_pRegion )
705 cairo_region_destroy( m_pRegion );
708 m_pIMHandler.reset();
710 //tdf#108705 remove grabs on event widget before
711 //destroying event widget
712 while (m_nGrabLevel)
713 removeGrabLevel();
716 SolarMutexGuard aGuard;
718 if (m_nWatcherId)
719 g_bus_unwatch_name(m_nWatcherId);
721 if (m_nPortalSettingChangedSignalId)
722 g_signal_handler_disconnect(m_pSettingsPortal, m_nPortalSettingChangedSignalId);
724 if (m_pSettingsPortal)
725 g_object_unref(m_pSettingsPortal);
727 if (m_nSessionClientSignalId)
728 g_signal_handler_disconnect(m_pSessionClient, m_nSessionClientSignalId);
730 if (m_pSessionClient)
731 g_object_unref(m_pSessionClient);
733 if (m_pSessionManager)
734 g_object_unref(m_pSessionManager);
737 GtkWidget *pEventWidget = getMouseEventWidget();
738 for (auto handler_id : m_aMouseSignalIds)
739 g_signal_handler_disconnect(G_OBJECT(pEventWidget), handler_id);
741 #if !GTK_CHECK_VERSION(4, 0, 0)
742 if( m_pFixedContainer )
743 gtk_widget_destroy( GTK_WIDGET( m_pFixedContainer ) );
744 if( m_pEventBox )
745 gtk_widget_destroy( GTK_WIDGET(m_pEventBox) );
746 if( m_pTopLevelGrid )
747 gtk_widget_destroy( GTK_WIDGET(m_pTopLevelGrid) );
748 #else
749 g_signal_handler_disconnect(G_OBJECT(gtk_widget_get_display(pEventWidget)), m_nSettingChangedSignalId);
750 #endif
752 SolarMutexGuard aGuard;
754 if( m_pWindow )
756 g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", nullptr );
758 if ( pSessionBus )
760 if ( m_nHudAwarenessId )
761 hud_awareness_unregister( pSessionBus, m_nHudAwarenessId );
762 if ( m_nMenuExportId )
763 g_dbus_connection_unexport_menu_model( pSessionBus, m_nMenuExportId );
764 if ( m_nActionGroupExportId )
765 g_dbus_connection_unexport_action_group( pSessionBus, m_nActionGroupExportId );
767 m_xFrameWeld.reset();
768 #if !GTK_CHECK_VERSION(4,0,0)
769 gtk_widget_destroy( m_pWindow );
770 #else
771 if (GTK_IS_WINDOW(m_pWindow))
772 gtk_window_destroy(GTK_WINDOW(m_pWindow));
773 else
774 g_clear_pointer(&m_pWindow, gtk_widget_unparent);
775 #endif
779 #if !GTK_CHECK_VERSION(4,0,0)
780 if( m_pForeignParent )
781 g_object_unref( G_OBJECT( m_pForeignParent ) );
782 if( m_pForeignTopLevel )
783 g_object_unref( G_OBJECT( m_pForeignTopLevel) );
784 #endif
786 m_pGraphics.reset();
788 if (m_pSurface)
789 cairo_surface_destroy(m_pSurface);
792 void GtkSalFrame::moveWindow( tools::Long nX, tools::Long nY )
794 if( isChild( false ) )
796 GtkWidget* pParent = m_pParent ? gtk_widget_get_parent(m_pWindow) : nullptr;
797 // tdf#130414 it's possible that we were reparented and are no longer inside
798 // our original GtkFixed parent
799 if (pParent && GTK_IS_FIXED(pParent))
801 gtk_fixed_move( GTK_FIXED(pParent),
802 m_pWindow,
803 nX - m_pParent->maGeometry.x(), nY - m_pParent->maGeometry.y() );
805 return;
807 #if GTK_CHECK_VERSION(4,0,0)
808 if (GTK_IS_POPOVER(m_pWindow))
810 GdkRectangle aRect;
811 aRect.x = nX;
812 aRect.y = nY;
813 aRect.width = 1;
814 aRect.height = 1;
815 gtk_popover_set_pointing_to(GTK_POPOVER(m_pWindow), &aRect);
816 return;
818 #else
819 gtk_window_move( GTK_WINDOW(m_pWindow), nX, nY );
820 #endif
823 void GtkSalFrame::widget_set_size_request(tools::Long nWidth, tools::Long nHeight)
825 gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), nWidth, nHeight );
826 #if GTK_CHECK_VERSION(4,0,0)
827 gtk_widget_set_size_request(GTK_WIDGET(m_pDrawingArea), nWidth, nHeight );
828 #endif
831 void GtkSalFrame::window_resize(tools::Long nWidth, tools::Long nHeight)
833 m_nWidthRequest = nWidth;
834 m_nHeightRequest = nHeight;
835 if (!GTK_IS_WINDOW(m_pWindow))
837 #if GTK_CHECK_VERSION(4,0,0)
838 gtk_widget_set_size_request(GTK_WIDGET(m_pDrawingArea), nWidth, nHeight);
839 #endif
840 return;
842 gtk_window_set_default_size(GTK_WINDOW(m_pWindow), nWidth, nHeight);
843 #if !GTK_CHECK_VERSION(4,0,0)
844 gtk_window_resize(GTK_WINDOW(m_pWindow), nWidth, nHeight);
845 #endif
848 void GtkSalFrame::resizeWindow( tools::Long nWidth, tools::Long nHeight )
850 if( isChild( false ) )
852 widget_set_size_request(nWidth, nHeight);
854 else if( ! isChild( true, false ) )
855 window_resize(nWidth, nHeight);
858 #if !GTK_CHECK_VERSION(4,0,0)
859 // tdf#124694 GtkFixed takes the max size of all its children as its
860 // preferred size, causing it to not clip its child, but grow instead.
862 static void
863 ooo_fixed_get_preferred_height(GtkWidget*, gint *minimum, gint *natural)
865 *minimum = 0;
866 *natural = 0;
869 static void
870 ooo_fixed_get_preferred_width(GtkWidget*, gint *minimum, gint *natural)
872 *minimum = 0;
873 *natural = 0;
876 static void
877 ooo_fixed_class_init(GtkFixedClass *klass)
879 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
880 widget_class->get_accessible = ooo_fixed_get_accessible;
881 widget_class->get_preferred_height = ooo_fixed_get_preferred_height;
882 widget_class->get_preferred_width = ooo_fixed_get_preferred_width;
886 * Always use a sub-class of GtkFixed we can tag for a11y. This allows us to
887 * utilize GAIL for the toplevel window and toolkit implementation incl.
888 * key event listener support ..
891 GType
892 ooo_fixed_get_type()
894 static GType type = 0;
896 if (!type) {
897 static const GTypeInfo tinfo =
899 sizeof (GtkFixedClass),
900 nullptr, /* base init */
901 nullptr, /* base finalize */
902 reinterpret_cast<GClassInitFunc>(ooo_fixed_class_init), /* class init */
903 nullptr, /* class finalize */
904 nullptr, /* class data */
905 sizeof (GtkFixed), /* instance size */
906 0, /* nb preallocs */
907 nullptr, /* instance init */
908 nullptr /* value table */
911 type = g_type_register_static( GTK_TYPE_FIXED, "OOoFixed",
912 &tinfo, GTypeFlags(0));
915 return type;
918 #endif
920 void GtkSalFrame::updateScreenNumber()
922 #if !GTK_CHECK_VERSION(4,0,0)
923 int nScreen = 0;
924 GdkScreen *pScreen = gtk_widget_get_screen( m_pWindow );
925 if( pScreen )
926 nScreen = getDisplay()->getSystem()->getScreenMonitorIdx( pScreen, maGeometry.x(), maGeometry.y() );
927 maGeometry.setScreen(nScreen);
928 #endif
931 GtkWidget *GtkSalFrame::getMouseEventWidget() const
933 #if !GTK_CHECK_VERSION(4,0,0)
934 return GTK_WIDGET(m_pEventBox);
935 #else
936 return GTK_WIDGET(m_pFixedContainer);
937 #endif
940 static void damaged(void *handle,
941 sal_Int32 nExtentsX, sal_Int32 nExtentsY,
942 sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight)
944 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(handle);
945 pThis->damaged(nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight);
948 void GtkSalFrame::InitCommon()
950 m_pSurface = nullptr;
951 m_nGrabLevel = 0;
952 m_bSalObjectSetPosSize = false;
953 m_nPortalSettingChangedSignalId = 0;
954 m_nSessionClientSignalId = 0;
955 m_pSettingsPortal = nullptr;
956 m_pSessionManager = nullptr;
957 m_pSessionClient = nullptr;
959 m_aDamageHandler.handle = this;
960 m_aDamageHandler.damaged = ::damaged;
962 #if !GTK_CHECK_VERSION(4,0,0)
963 m_aSmoothScrollIdle.SetInvokeHandler(LINK(this, GtkSalFrame, AsyncScroll));
964 #endif
966 m_pTopLevelGrid = GTK_GRID(gtk_grid_new());
967 container_add(m_pWindow, GTK_WIDGET(m_pTopLevelGrid));
969 #if !GTK_CHECK_VERSION(4,0,0)
970 m_pEventBox = GTK_EVENT_BOX(gtk_event_box_new());
971 gtk_widget_add_events( GTK_WIDGET(m_pEventBox),
972 GDK_ALL_EVENTS_MASK );
973 gtk_widget_set_vexpand(GTK_WIDGET(m_pEventBox), true);
974 gtk_widget_set_hexpand(GTK_WIDGET(m_pEventBox), true);
975 gtk_grid_attach(m_pTopLevelGrid, GTK_WIDGET(m_pEventBox), 0, 0, 1, 1);
976 #endif
978 // add the fixed container child,
979 // fixed is needed since we have to position plugin windows
980 #if !GTK_CHECK_VERSION(4,0,0)
981 m_pFixedContainer = GTK_FIXED(g_object_new( ooo_fixed_get_type(), nullptr ));
982 m_pDrawingArea = m_pFixedContainer;
983 #else
984 m_pOverlay = GTK_OVERLAY(gtk_overlay_new());
985 #if GTK_CHECK_VERSION(4,9,0)
986 m_pFixedContainer = GTK_FIXED(g_object_new( ooo_fixed_get_type(), nullptr ));
987 #else
988 m_pFixedContainer = GTK_FIXED(gtk_fixed_new());
989 #endif
990 m_pDrawingArea = GTK_DRAWING_AREA(gtk_drawing_area_new());
991 #endif
992 gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), true);
993 gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), 1, 1);
994 #if !GTK_CHECK_VERSION(4,0,0)
995 gtk_container_add( GTK_CONTAINER(m_pEventBox), GTK_WIDGET(m_pFixedContainer) );
996 #else
997 gtk_widget_set_vexpand(GTK_WIDGET(m_pOverlay), true);
998 gtk_widget_set_hexpand(GTK_WIDGET(m_pOverlay), true);
999 gtk_grid_attach(m_pTopLevelGrid, GTK_WIDGET(m_pOverlay), 0, 0, 1, 1);
1000 gtk_overlay_set_child(m_pOverlay, GTK_WIDGET(m_pDrawingArea));
1001 gtk_overlay_add_overlay(m_pOverlay, GTK_WIDGET(m_pFixedContainer));
1002 #endif
1004 GtkWidget *pEventWidget = getMouseEventWidget();
1005 #if !GTK_CHECK_VERSION(4,0,0)
1006 gtk_widget_set_app_paintable(GTK_WIDGET(m_pFixedContainer), true);
1007 gtk_widget_set_redraw_on_allocate(GTK_WIDGET(m_pFixedContainer), false);
1008 #endif
1010 #if GTK_CHECK_VERSION(4,0,0)
1011 m_nSettingChangedSignalId = g_signal_connect(G_OBJECT(gtk_widget_get_display(pEventWidget)), "setting-changed", G_CALLBACK(signalStyleUpdated), this);
1012 #else
1013 // use pEventWidget instead of m_pWindow to avoid infinite event loop under Linux Mint Mate 18.3
1014 g_signal_connect(G_OBJECT(pEventWidget), "style-updated", G_CALLBACK(signalStyleUpdated), this);
1015 #endif
1016 gtk_widget_set_has_tooltip(pEventWidget, true);
1017 // connect signals
1018 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "query-tooltip", G_CALLBACK(signalTooltipQuery), this ));
1019 #if !GTK_CHECK_VERSION(4,0,0)
1020 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-press-event", G_CALLBACK(signalButton), this ));
1021 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-release-event", G_CALLBACK(signalButton), this ));
1023 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "motion-notify-event", G_CALLBACK(signalMotion), this ));
1024 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "leave-notify-event", G_CALLBACK(signalCrossing), this ));
1025 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "enter-notify-event", G_CALLBACK(signalCrossing), this ));
1027 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "scroll-event", G_CALLBACK(signalScroll), this ));
1028 #else
1029 GtkGesture *pClick = gtk_gesture_click_new();
1030 gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pClick), 0);
1031 // use GTK_PHASE_TARGET instead of default GTK_PHASE_BUBBLE because I don't
1032 // want click events in gtk widgets inside the overlay, e.g. the font size
1033 // combobox GtkEntry in the toolbar, to be propagated down to this widget
1034 gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(pClick), GTK_PHASE_TARGET);
1035 gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pClick));
1036 g_signal_connect(pClick, "pressed", G_CALLBACK(gesturePressed), this);
1037 g_signal_connect(pClick, "released", G_CALLBACK(gestureReleased), this);
1039 GtkEventController* pMotionController = gtk_event_controller_motion_new();
1040 g_signal_connect(pMotionController, "motion", G_CALLBACK(signalMotion), this);
1041 g_signal_connect(pMotionController, "enter", G_CALLBACK(signalEnter), this);
1042 g_signal_connect(pMotionController, "leave", G_CALLBACK(signalLeave), this);
1043 gtk_widget_add_controller(pEventWidget, pMotionController);
1045 GtkEventController* pScrollController = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
1046 g_signal_connect(pScrollController, "scroll", G_CALLBACK(signalScroll), this);
1047 gtk_widget_add_controller(pEventWidget, pScrollController);
1048 #endif
1050 #if GTK_CHECK_VERSION(4,0,0)
1051 GtkGesture* pZoomGesture = gtk_gesture_zoom_new();
1052 gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pZoomGesture));
1053 #else
1054 GtkGesture* pZoomGesture = gtk_gesture_zoom_new(GTK_WIDGET(pEventWidget));
1055 g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pZoomGesture);
1056 #endif
1057 gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(pZoomGesture),
1058 GTK_PHASE_TARGET);
1059 // Note that the default zoom gesture signal handler needs to run first to setup correct
1060 // scale delta. Otherwise the first "begin" event will always contain scale delta of infinity.
1061 g_signal_connect_after(pZoomGesture, "begin", G_CALLBACK(signalZoomBegin), this);
1062 g_signal_connect_after(pZoomGesture, "update", G_CALLBACK(signalZoomUpdate), this);
1063 g_signal_connect_after(pZoomGesture, "end", G_CALLBACK(signalZoomEnd), this);
1065 #if GTK_CHECK_VERSION(4,0,0)
1066 GtkGesture* pRotateGesture = gtk_gesture_rotate_new();
1067 gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pRotateGesture));
1068 #else
1069 GtkGesture* pRotateGesture = gtk_gesture_rotate_new(GTK_WIDGET(pEventWidget));
1070 g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pRotateGesture);
1071 #endif
1072 gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(pRotateGesture),
1073 GTK_PHASE_TARGET);
1074 g_signal_connect(pRotateGesture, "begin", G_CALLBACK(signalRotateBegin), this);
1075 g_signal_connect(pRotateGesture, "update", G_CALLBACK(signalRotateUpdate), this);
1076 g_signal_connect(pRotateGesture, "end", G_CALLBACK(signalRotateEnd), this);
1078 //Drop Target Stuff
1079 #if GTK_CHECK_VERSION(4,0,0)
1080 GtkDropTargetAsync* pDropTarget = gtk_drop_target_async_new(nullptr, GdkDragAction(GDK_ACTION_ALL));
1081 g_signal_connect(G_OBJECT(pDropTarget), "drag-enter", G_CALLBACK(signalDragMotion), this);
1082 g_signal_connect(G_OBJECT(pDropTarget), "drag-motion", G_CALLBACK(signalDragMotion), this);
1083 g_signal_connect(G_OBJECT(pDropTarget), "drag-leave", G_CALLBACK(signalDragLeave), this);
1084 g_signal_connect(G_OBJECT(pDropTarget), "drop", G_CALLBACK(signalDragDrop), this);
1085 gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pDropTarget));
1086 #else
1087 gtk_drag_dest_set(GTK_WIDGET(pEventWidget), GtkDestDefaults(0), nullptr, 0, GdkDragAction(0));
1088 gtk_drag_dest_set_track_motion(GTK_WIDGET(pEventWidget), true);
1089 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-motion", G_CALLBACK(signalDragMotion), this ));
1090 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-drop", G_CALLBACK(signalDragDrop), this ));
1091 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-received", G_CALLBACK(signalDragDropReceived), this ));
1092 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-leave", G_CALLBACK(signalDragLeave), this ));
1093 #endif
1095 #if !GTK_CHECK_VERSION(4,0,0)
1096 //Drag Source Stuff
1097 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-end", G_CALLBACK(signalDragEnd), this ));
1098 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-failed", G_CALLBACK(signalDragFailed), this ));
1099 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-delete", G_CALLBACK(signalDragDelete), this ));
1100 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-get", G_CALLBACK(signalDragDataGet), this ));
1101 #endif
1103 #if !GTK_CHECK_VERSION(4,0,0)
1104 g_signal_connect( G_OBJECT(m_pFixedContainer), "draw", G_CALLBACK(signalDraw), this );
1105 g_signal_connect( G_OBJECT(m_pFixedContainer), "size-allocate", G_CALLBACK(sizeAllocated), this );
1106 #else
1107 gtk_drawing_area_set_draw_func(m_pDrawingArea, signalDraw, this, nullptr);
1108 g_signal_connect(G_OBJECT(m_pDrawingArea), "resize", G_CALLBACK(sizeAllocated), this);
1109 #endif
1111 g_signal_connect(G_OBJECT(m_pFixedContainer), "realize", G_CALLBACK(signalRealize), this);
1113 #if !GTK_CHECK_VERSION(4,0,0)
1114 GtkGesture *pSwipe = gtk_gesture_swipe_new(pEventWidget);
1115 g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pSwipe);
1116 #else
1117 GtkGesture *pSwipe = gtk_gesture_swipe_new();
1118 gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pSwipe));
1119 #endif
1120 g_signal_connect(pSwipe, "swipe", G_CALLBACK(gestureSwipe), this);
1121 gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pSwipe), GTK_PHASE_TARGET);
1123 #if !GTK_CHECK_VERSION(4,0,0)
1124 GtkGesture *pLongPress = gtk_gesture_long_press_new(pEventWidget);
1125 g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pLongPress);
1126 #else
1127 GtkGesture *pLongPress = gtk_gesture_long_press_new();
1128 gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pLongPress));
1129 #endif
1130 g_signal_connect(pLongPress, "pressed", G_CALLBACK(gestureLongPress), this);
1131 gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pLongPress), GTK_PHASE_TARGET);
1133 #if !GTK_CHECK_VERSION(4,0,0)
1134 g_signal_connect_after( G_OBJECT(m_pWindow), "focus-in-event", G_CALLBACK(signalFocus), this );
1135 g_signal_connect_after( G_OBJECT(m_pWindow), "focus-out-event", G_CALLBACK(signalFocus), this );
1136 if (GTK_IS_WINDOW(m_pWindow)) // i.e. not if it's a GtkEventBox which doesn't have the signal
1137 m_nSetFocusSignalId = g_signal_connect( G_OBJECT(m_pWindow), "set-focus", G_CALLBACK(signalSetFocus), this );
1138 #else
1139 GtkEventController* pFocusController = gtk_event_controller_focus_new();
1140 g_signal_connect(pFocusController, "enter", G_CALLBACK(signalFocusEnter), this);
1141 g_signal_connect(pFocusController, "leave", G_CALLBACK(signalFocusLeave), this);
1142 gtk_widget_set_focusable(pEventWidget, true);
1143 gtk_widget_add_controller(pEventWidget, pFocusController);
1144 if (GTK_IS_WINDOW(m_pWindow)) // i.e. not if it's a GtkEventBox which doesn't have the property (presumably?)
1145 m_nSetFocusSignalId = g_signal_connect( G_OBJECT(m_pWindow), "notify::focus-widget", G_CALLBACK(signalSetFocus), this );
1146 #endif
1147 #if !GTK_CHECK_VERSION(4,0,0)
1148 g_signal_connect( G_OBJECT(m_pWindow), "map-event", G_CALLBACK(signalMap), this );
1149 g_signal_connect( G_OBJECT(m_pWindow), "unmap-event", G_CALLBACK(signalUnmap), this );
1150 g_signal_connect( G_OBJECT(m_pWindow), "delete-event", G_CALLBACK(signalDelete), this );
1151 #else
1152 g_signal_connect( G_OBJECT(m_pWindow), "map", G_CALLBACK(signalMap), this );
1153 g_signal_connect( G_OBJECT(m_pWindow), "unmap", G_CALLBACK(signalUnmap), this );
1154 if (GTK_IS_WINDOW(m_pWindow))
1155 g_signal_connect( G_OBJECT(m_pWindow), "close-request", G_CALLBACK(signalDelete), this );
1156 #endif
1157 #if !GTK_CHECK_VERSION(4,0,0)
1158 g_signal_connect( G_OBJECT(m_pWindow), "configure-event", G_CALLBACK(signalConfigure), this );
1159 #endif
1161 #if !GTK_CHECK_VERSION(4,0,0)
1162 g_signal_connect( G_OBJECT(m_pWindow), "key-press-event", G_CALLBACK(signalKey), this );
1163 g_signal_connect( G_OBJECT(m_pWindow), "key-release-event", G_CALLBACK(signalKey), this );
1164 #else
1165 m_pKeyController = GTK_EVENT_CONTROLLER_KEY(gtk_event_controller_key_new());
1166 g_signal_connect(m_pKeyController, "key-pressed", G_CALLBACK(signalKeyPressed), this);
1167 g_signal_connect(m_pKeyController, "key-released", G_CALLBACK(signalKeyReleased), this);
1168 gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(m_pKeyController));
1170 #endif
1171 g_signal_connect( G_OBJECT(m_pWindow), "destroy", G_CALLBACK(signalDestroy), this );
1173 // init members
1174 m_nKeyModifiers = ModKeyFlags::NONE;
1175 m_bFullscreen = false;
1176 #if GTK_CHECK_VERSION(4,0,0)
1177 m_nState = static_cast<GdkToplevelState>(0);
1178 #else
1179 m_nState = GDK_WINDOW_STATE_WITHDRAWN;
1180 #endif
1181 m_pIMHandler = nullptr;
1182 m_pRegion = nullptr;
1183 m_pDropTarget = nullptr;
1184 m_pDragSource = nullptr;
1185 m_bGeometryIsProvisional = false;
1186 m_bIconSetWhileUnmapped = false;
1187 m_bTooltipBlocked = false;
1188 m_ePointerStyle = static_cast<PointerStyle>(0xffff);
1189 m_pSalMenu = nullptr;
1190 m_nWatcherId = 0;
1191 m_nMenuExportId = 0;
1192 m_nActionGroupExportId = 0;
1193 m_nHudAwarenessId = 0;
1195 #if !GTK_CHECK_VERSION(4,0,0)
1196 gtk_widget_add_events( m_pWindow,
1197 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1198 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
1199 GDK_SCROLL_MASK | GDK_TOUCHPAD_GESTURE_MASK
1201 #endif
1203 // show the widgets
1204 #if !GTK_CHECK_VERSION(4,0,0)
1205 gtk_widget_show_all(GTK_WIDGET(m_pTopLevelGrid));
1206 #else
1207 gtk_widget_show(GTK_WIDGET(m_pTopLevelGrid));
1208 #endif
1210 // realize the window, we need an XWindow id
1211 gtk_widget_realize( m_pWindow );
1213 if (GTK_IS_WINDOW(m_pWindow))
1215 #if !GTK_CHECK_VERSION(4,0,0)
1216 g_signal_connect(G_OBJECT(m_pWindow), "window-state-event", G_CALLBACK(signalWindowState), this);
1217 #else
1218 GdkSurface* gdkWindow = widget_get_surface(m_pWindow);
1219 g_signal_connect(G_OBJECT(gdkWindow), "notify::state", G_CALLBACK(signalWindowState), this);
1220 #endif
1223 //system data
1224 m_aSystemData.SetWindowHandle(GetNativeWindowHandle(m_pWindow));
1225 m_aSystemData.aShellWindow = reinterpret_cast<sal_IntPtr>(this);
1226 m_aSystemData.pSalFrame = this;
1227 m_aSystemData.pWidget = m_pWindow;
1228 m_aSystemData.nScreen = m_nXScreen.getXScreen();
1229 m_aSystemData.toolkit = SystemEnvData::Toolkit::Gtk;
1231 #if defined(GDK_WINDOWING_X11)
1232 GdkDisplay *pDisplay = getGdkDisplay();
1233 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
1235 m_aSystemData.pDisplay = gdk_x11_display_get_xdisplay(pDisplay);
1236 m_aSystemData.platform = SystemEnvData::Platform::Xcb;
1237 #if !GTK_CHECK_VERSION(4,0,0)
1238 GdkScreen* pScreen = gtk_widget_get_screen(m_pWindow);
1239 GdkVisual* pVisual = gdk_screen_get_system_visual(pScreen);
1240 m_aSystemData.pVisual = gdk_x11_visual_get_xvisual(pVisual);
1241 #endif
1243 #endif
1244 #if defined(GDK_WINDOWING_WAYLAND)
1245 if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
1247 m_aSystemData.pDisplay = gdk_wayland_display_get_wl_display(pDisplay);
1248 m_aSystemData.platform = SystemEnvData::Platform::Wayland;
1250 #endif
1252 m_bGraphics = false;
1253 m_pGraphics = nullptr;
1255 m_nFloatFlags = FloatWinPopupFlags::NONE;
1256 m_bFloatPositioned = false;
1258 m_nWidthRequest = 0;
1259 m_nHeightRequest = 0;
1261 // fake an initial geometry, gets updated via configure event or SetPosSize
1262 if (m_bDefaultPos || m_bDefaultSize)
1264 Size aDefSize = calcDefaultSize();
1265 maGeometry.setPosSize({ -1, -1 }, aDefSize);
1266 maGeometry.setDecorations(0, 0, 0, 0);
1268 updateScreenNumber();
1270 SetIcon(SV_ICON_ID_OFFICE);
1273 GtkSalFrame *GtkSalFrame::getFromWindow( GtkWidget *pWindow )
1275 return static_cast<GtkSalFrame *>(g_object_get_data( G_OBJECT( pWindow ), "SalFrame" ));
1278 void GtkSalFrame::DisallowCycleFocusOut()
1280 if (!m_nSetFocusSignalId)
1281 return;
1282 // don't enable/disable can-focus as control enters and leaves
1283 // embedded native gtk widgets
1284 g_signal_handler_disconnect(G_OBJECT(m_pWindow), m_nSetFocusSignalId);
1285 m_nSetFocusSignalId = 0;
1287 #if !GTK_CHECK_VERSION(4, 0, 0)
1288 // gtk3: set container without can-focus and focus will tab between
1289 // the native embedded widgets using the default gtk handling for
1290 // that
1291 // gtk4: no need because the native widgets are the only
1292 // thing in the overlay and the drawing widget is underneath so
1293 // the natural focus cycle is sufficient
1294 gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), false);
1295 #endif
1298 bool GtkSalFrame::IsCycleFocusOutDisallowed() const
1300 return m_nSetFocusSignalId == 0;
1303 void GtkSalFrame::AllowCycleFocusOut()
1305 if (m_nSetFocusSignalId)
1306 return;
1307 #if !GTK_CHECK_VERSION(4,0,0)
1308 // enable/disable can-focus as control enters and leaves
1309 // embedded native gtk widgets
1310 m_nSetFocusSignalId = g_signal_connect(G_OBJECT(m_pWindow), "set-focus", G_CALLBACK(signalSetFocus), this);
1311 #else
1312 m_nSetFocusSignalId = g_signal_connect(G_OBJECT(m_pWindow), "notify::focus-widget", G_CALLBACK(signalSetFocus), this);
1313 #endif
1315 #if !GTK_CHECK_VERSION(4, 0, 0)
1316 // set container without can-focus and focus will tab between
1317 // the native embedded widgets using the default gtk handling for
1318 // that
1319 // gtk4: no need because the native widgets are the only
1320 // thing in the overlay and the drawing widget is underneath so
1321 // the natural focus cycle is sufficient
1322 gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), true);
1323 #endif
1326 namespace
1328 enum ColorScheme
1330 DEFAULT,
1331 PREFER_DARK,
1332 PREFER_LIGHT
1335 void ReadColorScheme(GDBusProxy* proxy, GVariant** out)
1337 g_autoptr (GVariant) ret =
1338 g_dbus_proxy_call_sync(proxy, "Read",
1339 g_variant_new ("(ss)", "org.freedesktop.appearance", "color-scheme"),
1340 G_DBUS_CALL_FLAGS_NONE, G_MAXINT, nullptr, nullptr);
1341 if (!ret)
1342 return;
1344 g_autoptr (GVariant) child = nullptr;
1345 g_variant_get(ret, "(v)", &child);
1346 g_variant_get(child, "v", out);
1348 return;
1352 void GtkSalFrame::SetColorScheme(GVariant* variant)
1354 if (!m_pWindow)
1355 return;
1357 guint32 color_scheme;
1359 switch (officecfg::Office::Common::Misc::Appearance::get())
1361 default:
1362 case 0: // Auto
1364 if (variant)
1366 color_scheme = g_variant_get_uint32(variant);
1367 if (color_scheme > PREFER_LIGHT)
1368 color_scheme = DEFAULT;
1370 else
1371 color_scheme = DEFAULT;
1372 break;
1374 case 1: // Light
1375 color_scheme = PREFER_LIGHT;
1376 break;
1377 case 2: // Dark
1378 color_scheme = PREFER_DARK;
1379 break;
1382 bool bDarkIconTheme(color_scheme == PREFER_DARK);
1383 GtkSettings* pSettings = gtk_widget_get_settings(m_pWindow);
1384 g_object_set(pSettings, "gtk-application-prefer-dark-theme", bDarkIconTheme, nullptr);
1387 bool GtkSalFrame::GetUseDarkMode() const
1389 if (!m_pWindow)
1390 return false;
1391 GtkSettings* pSettings = gtk_widget_get_settings(m_pWindow);
1392 gboolean bDarkIconTheme = false;
1393 g_object_get(pSettings, "gtk-application-prefer-dark-theme", &bDarkIconTheme, nullptr);
1394 return bDarkIconTheme;
1397 static void settings_portal_changed_cb(GDBusProxy*, const char*, const char* signal_name,
1398 GVariant* parameters, gpointer frame)
1400 if (g_strcmp0(signal_name, "SettingChanged"))
1401 return;
1403 g_autoptr (GVariant) value = nullptr;
1404 const char *name_space;
1405 const char *name;
1406 g_variant_get(parameters, "(&s&sv)", &name_space, &name, &value);
1408 if (g_strcmp0(name_space, "org.freedesktop.appearance") ||
1409 g_strcmp0(name, "color-scheme"))
1410 return;
1412 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
1413 pThis->SetColorScheme(value);
1416 void GtkSalFrame::ListenPortalSettings()
1418 EnsureSessionBus();
1420 if (!pSessionBus)
1421 return;
1423 m_pSettingsPortal = g_dbus_proxy_new_sync(pSessionBus,
1424 G_DBUS_PROXY_FLAGS_NONE,
1425 nullptr,
1426 "org.freedesktop.portal.Desktop",
1427 "/org/freedesktop/portal/desktop",
1428 "org.freedesktop.portal.Settings",
1429 nullptr,
1430 nullptr);
1432 UpdateDarkMode();
1434 if (!m_pSettingsPortal)
1435 return;
1437 m_nPortalSettingChangedSignalId = g_signal_connect(m_pSettingsPortal, "g-signal", G_CALLBACK(settings_portal_changed_cb), this);
1440 static void session_client_response(GDBusProxy* client_proxy)
1442 g_dbus_proxy_call(client_proxy,
1443 "EndSessionResponse",
1444 g_variant_new ("(bs)", true, ""),
1445 G_DBUS_CALL_FLAGS_NONE,
1446 G_MAXINT,
1447 nullptr, nullptr, nullptr);
1450 // unset documents "modify" flag so they won't veto closing
1451 static void clear_modify_and_terminate()
1453 css::uno::Reference<css::uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext();
1454 uno::Reference<frame::XDesktop> xDesktop(frame::Desktop::create(xContext));
1455 uno::Reference<css::container::XEnumeration> xComponents = xDesktop->getComponents()->createEnumeration();
1456 while (xComponents->hasMoreElements())
1458 css::uno::Reference<css::util::XModifiable> xModifiable(xComponents->nextElement(), css::uno::UNO_QUERY);
1459 if (xModifiable)
1460 xModifiable->setModified(false);
1462 xDesktop->terminate();
1465 static void session_client_signal(GDBusProxy* client_proxy, const char*, const char* signal_name,
1466 GVariant* /*parameters*/, gpointer frame)
1468 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
1470 if (g_str_equal (signal_name, "QueryEndSession"))
1472 css::uno::Reference<css::uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext();
1473 uno::Reference<frame::XDesktop2> xDesktop(frame::Desktop::create(xContext));
1475 bool bModified = false;
1477 // find the XModifiable for this GtkSalFrame
1478 if (UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper(false))
1480 VclPtr<vcl::Window> xThisWindow = pThis->GetWindow();
1481 css::uno::Reference<css::container::XIndexAccess> xList = xDesktop->getFrames();
1482 sal_Int32 nFrameCount = xList->getCount();
1483 for (sal_Int32 i = 0; i < nFrameCount; ++i)
1485 css::uno::Reference<css::frame::XFrame> xFrame;
1486 xList->getByIndex(i) >>= xFrame;
1487 if (!xFrame)
1488 continue;
1489 VclPtr<vcl::Window> xWin = pWrapper->GetWindow(xFrame->getContainerWindow());
1490 if (!xWin)
1491 continue;
1492 if (xWin->GetFrameWindow() != xThisWindow)
1493 continue;
1494 css::uno::Reference<css::frame::XController> xController = xFrame->getController();
1495 if (!xController)
1496 break;
1497 css::uno::Reference<css::util::XModifiable> xModifiable(xController->getModel(), css::uno::UNO_QUERY);
1498 if (!xModifiable)
1499 break;
1500 bModified = xModifiable->isModified();
1501 break;
1505 pThis->SessionManagerInhibit(bModified, APPLICATION_INHIBIT_LOGOUT, VclResId(STR_UNSAVED_DOCUMENTS),
1506 gtk_window_get_icon_name(GTK_WINDOW(pThis->getWindow())));
1508 session_client_response(client_proxy);
1510 else if (g_str_equal (signal_name, "CancelEndSession"))
1512 // restore back to uninhibited (to set again if queried), so frames
1513 // that go away before the next logout don't affect that logout
1514 pThis->SessionManagerInhibit(false, APPLICATION_INHIBIT_LOGOUT, VclResId(STR_UNSAVED_DOCUMENTS),
1515 gtk_window_get_icon_name(GTK_WINDOW(pThis->getWindow())));
1517 else if (g_str_equal (signal_name, "EndSession"))
1519 session_client_response(client_proxy);
1520 clear_modify_and_terminate();
1522 else if (g_str_equal (signal_name, "Stop"))
1524 clear_modify_and_terminate();
1528 void GtkSalFrame::ListenSessionManager()
1530 EnsureSessionBus();
1532 if (!pSessionBus)
1533 return;
1535 m_pSessionManager = g_dbus_proxy_new_sync(pSessionBus,
1536 G_DBUS_PROXY_FLAGS_NONE,
1537 nullptr,
1538 "org.gnome.SessionManager",
1539 "/org/gnome/SessionManager",
1540 "org.gnome.SessionManager",
1541 nullptr,
1542 nullptr);
1544 if (!m_pSessionManager)
1545 return;
1547 GVariant* res = g_dbus_proxy_call_sync(m_pSessionManager,
1548 "RegisterClient",
1549 g_variant_new ("(ss)", "org.libreoffice", ""),
1550 G_DBUS_CALL_FLAGS_NONE,
1551 G_MAXINT,
1552 nullptr,
1553 nullptr);
1555 if (!res)
1556 return;
1558 gchar* client_path;
1559 g_variant_get(res, "(o)", &client_path);
1560 g_variant_unref(res);
1562 m_pSessionClient = g_dbus_proxy_new_sync(pSessionBus,
1563 G_DBUS_PROXY_FLAGS_NONE,
1564 nullptr,
1565 "org.gnome.SessionManager",
1566 client_path,
1567 "org.gnome.SessionManager.ClientPrivate",
1568 nullptr,
1569 nullptr);
1571 g_free(client_path);
1573 if (!m_pSessionClient)
1574 return;
1576 m_nSessionClientSignalId = g_signal_connect(m_pSessionClient, "g-signal", G_CALLBACK(session_client_signal), this);
1579 void GtkSalFrame::UpdateDarkMode()
1581 g_autoptr (GVariant) value = nullptr;
1582 if (m_pSettingsPortal)
1583 ReadColorScheme(m_pSettingsPortal, &value);
1584 SetColorScheme(value);
1587 #if GTK_CHECK_VERSION(4,0,0)
1588 static void PopoverClosed(GtkPopover*, GtkSalFrame* pThis)
1590 SolarMutexGuard aGuard;
1591 pThis->closePopup();
1593 #endif
1595 void GtkSalFrame::Init( SalFrame* pParent, SalFrameStyleFlags nStyle )
1597 if( nStyle & SalFrameStyleFlags::DEFAULT ) // ensure default style
1599 nStyle |= SalFrameStyleFlags::MOVEABLE | SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::CLOSEABLE;
1600 nStyle &= ~SalFrameStyleFlags::FLOAT;
1603 m_pParent = static_cast<GtkSalFrame*>(pParent);
1604 #if !GTK_CHECK_VERSION(4,0,0)
1605 m_pForeignParent = nullptr;
1606 m_aForeignParentWindow = None;
1607 m_pForeignTopLevel = nullptr;
1608 m_aForeignTopLevelWindow = None;
1609 #endif
1610 m_nStyle = nStyle;
1612 bool bPopup = ((nStyle & SalFrameStyleFlags::FLOAT) &&
1613 !(nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION));
1615 if( nStyle & SalFrameStyleFlags::SYSTEMCHILD )
1617 #if !GTK_CHECK_VERSION(4,0,0)
1618 m_pWindow = gtk_event_box_new();
1619 #else
1620 m_pWindow = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1621 #endif
1622 if( m_pParent )
1624 // insert into container
1625 gtk_fixed_put( m_pParent->getFixedContainer(),
1626 m_pWindow, 0, 0 );
1629 else
1631 #if !GTK_CHECK_VERSION(4,0,0)
1632 m_pWindow = gtk_window_new(bPopup ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL);
1633 #else
1634 if (!bPopup)
1635 m_pWindow = gtk_window_new();
1636 else
1638 m_pWindow = gtk_popover_new();
1639 gtk_popover_set_has_arrow(GTK_POPOVER(m_pWindow), false);
1640 g_signal_connect(m_pWindow, "closed", G_CALLBACK(PopoverClosed), this);
1642 #endif
1644 #if !GTK_CHECK_VERSION(4,0,0)
1645 // hook up F1 to show help for embedded native gtk widgets
1646 GtkAccelGroup *pGroup = gtk_accel_group_new();
1647 GClosure* closure = g_cclosure_new(G_CALLBACK(GtkSalFrame::NativeWidgetHelpPressed), GTK_WINDOW(m_pWindow), nullptr);
1648 gtk_accel_group_connect(pGroup, GDK_KEY_F1, static_cast<GdkModifierType>(0), GTK_ACCEL_LOCKED, closure);
1649 gtk_window_add_accel_group(GTK_WINDOW(m_pWindow), pGroup);
1650 #endif
1653 g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", this );
1654 g_object_set_data( G_OBJECT( m_pWindow ), "libo-version", const_cast<char *>(LIBO_VERSION_DOTTED));
1656 // force wm class hint
1657 if (!isChild())
1659 if (m_pParent)
1660 m_sWMClass = m_pParent->m_sWMClass;
1661 updateWMClass();
1664 if (GTK_IS_WINDOW(m_pWindow))
1666 if (m_pParent)
1668 GtkWidget* pTopLevel = widget_get_toplevel(m_pParent->m_pWindow);
1669 #if !GTK_CHECK_VERSION(4,0,0)
1670 if (!isChild())
1671 gtk_window_set_screen(GTK_WINDOW(m_pWindow), gtk_widget_get_screen(pTopLevel));
1672 #endif
1674 if (!(m_pParent->m_nStyle & SalFrameStyleFlags::PLUG))
1675 gtk_window_set_transient_for(GTK_WINDOW(m_pWindow), GTK_WINDOW(pTopLevel));
1676 m_pParent->m_aChildren.push_back( this );
1677 gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pTopLevel)), GTK_WINDOW(m_pWindow));
1679 else
1681 gtk_window_group_add_window(gtk_window_group_new(), GTK_WINDOW(m_pWindow));
1682 g_object_unref(gtk_window_get_group(GTK_WINDOW(m_pWindow)));
1685 else if (GTK_IS_POPOVER(m_pWindow))
1687 assert(m_pParent);
1688 gtk_widget_set_parent(m_pWindow, m_pParent->getMouseEventWidget());
1691 // set window type
1692 bool bDecoHandling =
1693 ! isChild() &&
1694 ( ! (nStyle & SalFrameStyleFlags::FLOAT) ||
1695 (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) );
1697 if( bDecoHandling )
1699 #if !GTK_CHECK_VERSION(4,0,0)
1700 GdkWindowTypeHint eType = GDK_WINDOW_TYPE_HINT_NORMAL;
1701 if( (nStyle & SalFrameStyleFlags::DIALOG) && m_pParent != nullptr )
1702 eType = GDK_WINDOW_TYPE_HINT_DIALOG;
1703 #endif
1704 if( nStyle & SalFrameStyleFlags::INTRO )
1706 #if !GTK_CHECK_VERSION(4,0,0)
1707 gtk_window_set_role( GTK_WINDOW(m_pWindow), "splashscreen" );
1708 eType = GDK_WINDOW_TYPE_HINT_SPLASHSCREEN;
1709 #endif
1711 else if( nStyle & SalFrameStyleFlags::TOOLWINDOW )
1713 #if !GTK_CHECK_VERSION(4,0,0)
1714 eType = GDK_WINDOW_TYPE_HINT_DIALOG;
1715 gtk_window_set_skip_taskbar_hint( GTK_WINDOW(m_pWindow), true );
1716 #endif
1718 else if( nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
1720 #if !GTK_CHECK_VERSION(4,0,0)
1721 eType = GDK_WINDOW_TYPE_HINT_TOOLBAR;
1722 gtk_window_set_focus_on_map(GTK_WINDOW(m_pWindow), false);
1723 #endif
1724 gtk_window_set_decorated(GTK_WINDOW(m_pWindow), false);
1726 #if !GTK_CHECK_VERSION(4,0,0)
1727 gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), eType );
1728 gtk_window_set_gravity( GTK_WINDOW(m_pWindow), GDK_GRAVITY_STATIC );
1729 #endif
1730 gtk_window_set_resizable( GTK_WINDOW(m_pWindow), bool(nStyle & SalFrameStyleFlags::SIZEABLE) );
1732 #if !GTK_CHECK_VERSION(4,0,0)
1733 #if defined(GDK_WINDOWING_WAYLAND)
1734 //rhbz#1392145 under wayland/csd if we've overridden the default widget direction in order to set LibreOffice's
1735 //UI to the configured ui language but the system ui locale is a different text direction, then the toplevel
1736 //built-in close button of the titlebar follows the overridden direction rather than continue in the same
1737 //direction as every other titlebar on the user's desktop. So if they don't match set an explicit
1738 //header bar with the desired 'outside' direction
1739 if ((eType == GDK_WINDOW_TYPE_HINT_NORMAL || eType == GDK_WINDOW_TYPE_HINT_DIALOG) && DLSYM_GDK_IS_WAYLAND_DISPLAY(GtkSalFrame::getGdkDisplay()))
1741 const bool bDesktopIsRTL = MsLangId::isRightToLeft(MsLangId::getConfiguredSystemUILanguage());
1742 const bool bAppIsRTL = gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL;
1743 if (bDesktopIsRTL != bAppIsRTL)
1745 m_pHeaderBar = GTK_HEADER_BAR(gtk_header_bar_new());
1746 gtk_widget_set_direction(GTK_WIDGET(m_pHeaderBar), bDesktopIsRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
1747 gtk_header_bar_set_show_close_button(m_pHeaderBar, true);
1748 gtk_window_set_titlebar(GTK_WINDOW(m_pWindow), GTK_WIDGET(m_pHeaderBar));
1749 gtk_widget_show(GTK_WIDGET(m_pHeaderBar));
1752 #endif
1753 #endif
1755 #if !GTK_CHECK_VERSION(4,0,0)
1756 else if( nStyle & SalFrameStyleFlags::FLOAT )
1757 gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), GDK_WINDOW_TYPE_HINT_POPUP_MENU );
1758 #endif
1760 InitCommon();
1762 if (!bPopup)
1764 // Enable GMenuModel native menu
1765 attach_menu_model(this);
1767 // Listen to portal settings for e.g. prefer dark theme
1768 ListenPortalSettings();
1770 // Listen to session manager for e.g. query-end
1771 ListenSessionManager();
1775 #if !GTK_CHECK_VERSION(4,0,0)
1776 GdkNativeWindow GtkSalFrame::findTopLevelSystemWindow( GdkNativeWindow )
1778 //FIXME: no findToplevelSystemWindow
1779 return 0;
1781 #endif
1783 void GtkSalFrame::Init( SystemParentData* pSysData )
1785 m_pParent = nullptr;
1786 #if !GTK_CHECK_VERSION(4,0,0)
1787 m_aForeignParentWindow = pSysData->aWindow;
1788 m_pForeignParent = nullptr;
1789 m_aForeignTopLevelWindow = findTopLevelSystemWindow(pSysData->aWindow);
1790 m_pForeignTopLevel = gdk_x11_window_foreign_new_for_display( getGdkDisplay(), m_aForeignTopLevelWindow );
1791 gdk_window_set_events( m_pForeignTopLevel, GDK_STRUCTURE_MASK );
1793 if( pSysData->nSize > sizeof(pSysData->nSize)+sizeof(pSysData->aWindow) && pSysData->bXEmbedSupport )
1795 m_pWindow = gtk_plug_new_for_display( getGdkDisplay(), pSysData->aWindow );
1796 gtk_widget_set_can_default(m_pWindow, true);
1797 gtk_widget_set_can_focus(m_pWindow, true);
1798 gtk_widget_set_sensitive(m_pWindow, true);
1800 else
1802 m_pWindow = gtk_window_new( GTK_WINDOW_POPUP );
1804 #endif
1805 m_nStyle = SalFrameStyleFlags::PLUG;
1806 InitCommon();
1808 #if !GTK_CHECK_VERSION(4,0,0)
1809 m_pForeignParent = gdk_x11_window_foreign_new_for_display( getGdkDisplay(), m_aForeignParentWindow );
1810 gdk_window_set_events( m_pForeignParent, GDK_STRUCTURE_MASK );
1811 #else
1812 (void)pSysData;
1813 #endif
1815 //FIXME: Handling embedded windows, is going to be fun ...
1818 void GtkSalFrame::SetExtendedFrameStyle(SalExtStyle)
1822 SalGraphics* GtkSalFrame::AcquireGraphics()
1824 if( m_bGraphics )
1825 return nullptr;
1827 if( !m_pGraphics )
1829 m_pGraphics.reset( new GtkSalGraphics( this, m_pWindow ) );
1830 if (!m_pSurface)
1832 AllocateFrame();
1833 TriggerPaintEvent();
1835 m_pGraphics->setSurface(m_pSurface, m_aFrameSize);
1837 m_bGraphics = true;
1838 return m_pGraphics.get();
1841 void GtkSalFrame::ReleaseGraphics( SalGraphics* pGraphics )
1843 (void) pGraphics;
1844 assert( pGraphics == m_pGraphics.get() );
1845 m_bGraphics = false;
1848 bool GtkSalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
1850 getDisplay()->SendInternalEvent( this, pData.release() );
1851 return true;
1854 void GtkSalFrame::SetTitle( const OUString& rTitle )
1856 if (m_pWindow && GTK_IS_WINDOW(m_pWindow) && !isChild())
1858 OString sTitle(OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8));
1859 gtk_window_set_title(GTK_WINDOW(m_pWindow), sTitle.getStr());
1860 #if !GTK_CHECK_VERSION(4,0,0)
1861 if (m_pHeaderBar)
1862 gtk_header_bar_set_title(m_pHeaderBar, sTitle.getStr());
1863 #endif
1867 void GtkSalFrame::SetIcon(const char* appicon)
1869 gtk_window_set_icon_name(GTK_WINDOW(m_pWindow), appicon);
1871 #if defined(GDK_WINDOWING_WAYLAND)
1872 if (!DLSYM_GDK_IS_WAYLAND_DISPLAY(getGdkDisplay()))
1873 return;
1875 #if GTK_CHECK_VERSION(4,0,0)
1876 GdkSurface* gdkWindow = gtk_native_get_surface(gtk_widget_get_native(m_pWindow));
1877 gdk_wayland_toplevel_set_application_id((GDK_TOPLEVEL(gdkWindow)), appicon);
1878 #else
1879 static auto set_application_id = reinterpret_cast<void (*) (GdkWindow*, const char*)>(
1880 dlsym(nullptr, "gdk_wayland_window_set_application_id"));
1881 if (set_application_id)
1883 GdkSurface* gdkWindow = widget_get_surface(m_pWindow);
1884 set_application_id(gdkWindow, appicon);
1886 #endif
1887 // gdk_wayland_window_set_application_id doesn't seem to work before
1888 // the window is mapped, so set this for real when/if we are mapped
1889 m_bIconSetWhileUnmapped = !gtk_widget_get_mapped(m_pWindow);
1890 #endif
1893 void GtkSalFrame::SetIcon( sal_uInt16 nIcon )
1895 if( (m_nStyle & (SalFrameStyleFlags::PLUG|SalFrameStyleFlags::SYSTEMCHILD|SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::INTRO|SalFrameStyleFlags::OWNERDRAWDECORATION))
1896 || ! m_pWindow )
1897 return;
1899 gchar* appicon;
1901 if (nIcon == SV_ICON_ID_TEXT)
1902 appicon = g_strdup ("libreoffice-writer");
1903 else if (nIcon == SV_ICON_ID_SPREADSHEET)
1904 appicon = g_strdup ("libreoffice-calc");
1905 else if (nIcon == SV_ICON_ID_DRAWING)
1906 appicon = g_strdup ("libreoffice-draw");
1907 else if (nIcon == SV_ICON_ID_PRESENTATION)
1908 appicon = g_strdup ("libreoffice-impress");
1909 else if (nIcon == SV_ICON_ID_DATABASE)
1910 appicon = g_strdup ("libreoffice-base");
1911 else if (nIcon == SV_ICON_ID_FORMULA)
1912 appicon = g_strdup ("libreoffice-math");
1913 else
1914 appicon = g_strdup ("libreoffice-startcenter");
1916 SetIcon(appicon);
1918 g_free (appicon);
1921 void GtkSalFrame::SetMenu( SalMenu* pSalMenu )
1923 m_pSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
1926 SalMenu* GtkSalFrame::GetMenu()
1928 return m_pSalMenu;
1931 void GtkSalFrame::Center()
1933 if (!GTK_IS_WINDOW(m_pWindow))
1934 return;
1935 #if !GTK_CHECK_VERSION(4,0,0)
1936 if (m_pParent)
1937 gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER_ON_PARENT);
1938 else
1939 gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER);
1940 #endif
1943 Size GtkSalFrame::calcDefaultSize()
1945 Size aScreenSize(getDisplay()->GetScreenSize(GetDisplayScreen()));
1946 int scale = gtk_widget_get_scale_factor(m_pWindow);
1947 aScreenSize.setWidth( aScreenSize.Width() / scale );
1948 aScreenSize.setHeight( aScreenSize.Height() / scale );
1949 return bestmaxFrameSizeForScreenSize(aScreenSize);
1952 void GtkSalFrame::SetDefaultSize()
1954 Size aDefSize = calcDefaultSize();
1956 SetPosSize( 0, 0, aDefSize.Width(), aDefSize.Height(),
1957 SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
1959 if( (m_nStyle & SalFrameStyleFlags::DEFAULT) && m_pWindow )
1960 gtk_window_maximize( GTK_WINDOW(m_pWindow) );
1963 void GtkSalFrame::Show( bool bVisible, bool /*bNoActivate*/ )
1965 if( !m_pWindow )
1966 return;
1968 if( bVisible )
1970 getDisplay()->startupNotificationCompleted();
1972 if( m_bDefaultPos )
1973 Center();
1974 if( m_bDefaultSize )
1975 SetDefaultSize();
1976 setMinMaxSize();
1978 if (isFloatGrabWindow() && !getDisplay()->GetCaptureFrame())
1980 m_pParent->grabPointer(true, true, true);
1981 m_pParent->addGrabLevel();
1984 #if defined(GDK_WINDOWING_WAYLAND) && !GTK_CHECK_VERSION(4,0,0)
1986 rhbz#1334915, gnome#779143, tdf#100158
1987 https://gitlab.gnome.org/GNOME/gtk/-/issues/767
1989 before gdk_wayland_window_set_application_id was available gtk
1990 under wayland lacked a way to change the app_id of a window, so
1991 brute force everything as a startcenter when initially shown to at
1992 least get the default LibreOffice icon and not the broken app icon
1994 static bool bAppIdImmutable = DLSYM_GDK_IS_WAYLAND_DISPLAY(getGdkDisplay()) &&
1995 !dlsym(nullptr, "gdk_wayland_window_set_application_id");
1996 if (bAppIdImmutable)
1998 OString sOrigName(g_get_prgname());
1999 g_set_prgname("libreoffice-startcenter");
2000 gtk_widget_show(m_pWindow);
2001 g_set_prgname(sOrigName.getStr());
2003 else
2005 gtk_widget_show(m_pWindow);
2007 #else
2008 gtk_widget_show(m_pWindow);
2009 #endif
2011 if( isFloatGrabWindow() )
2013 m_nFloats++;
2014 if (!getDisplay()->GetCaptureFrame())
2016 grabPointer(true, true, true);
2017 addGrabLevel();
2019 // #i44068# reset parent's IM context
2020 if( m_pParent )
2021 m_pParent->EndExtTextInput(EndExtTextInputFlags::NONE);
2024 else
2026 if( isFloatGrabWindow() )
2028 m_nFloats--;
2029 if (!getDisplay()->GetCaptureFrame())
2031 removeGrabLevel();
2032 grabPointer(false, true, false);
2033 m_pParent->removeGrabLevel();
2034 bool bParentIsFloatGrabWindow = m_pParent->isFloatGrabWindow();
2035 m_pParent->grabPointer(bParentIsFloatGrabWindow, true, bParentIsFloatGrabWindow);
2038 gtk_widget_hide( m_pWindow );
2039 if( m_pIMHandler )
2040 m_pIMHandler->focusChanged( false );
2044 void GtkSalFrame::setMinMaxSize()
2046 /* #i34504# metacity (and possibly others) do not treat
2047 * _NET_WM_STATE_FULLSCREEN and max_width/height independently;
2048 * whether they should is undefined. So don't set the max size hint
2049 * for a full screen window.
2051 if( !m_pWindow || isChild() )
2052 return;
2054 #if !GTK_CHECK_VERSION(4, 0, 0)
2055 GdkGeometry aGeo;
2056 int aHints = 0;
2057 if( m_nStyle & SalFrameStyleFlags::SIZEABLE )
2059 if( m_aMinSize.Width() && m_aMinSize.Height() && ! m_bFullscreen )
2061 aGeo.min_width = m_aMinSize.Width();
2062 aGeo.min_height = m_aMinSize.Height();
2063 aHints |= GDK_HINT_MIN_SIZE;
2065 if( m_aMaxSize.Width() && m_aMaxSize.Height() && ! m_bFullscreen )
2067 aGeo.max_width = m_aMaxSize.Width();
2068 aGeo.max_height = m_aMaxSize.Height();
2069 aHints |= GDK_HINT_MAX_SIZE;
2072 else
2074 if (!m_bFullscreen && m_nWidthRequest && m_nHeightRequest)
2076 aGeo.min_width = m_nWidthRequest;
2077 aGeo.min_height = m_nHeightRequest;
2078 aHints |= GDK_HINT_MIN_SIZE;
2080 aGeo.max_width = m_nWidthRequest;
2081 aGeo.max_height = m_nHeightRequest;
2082 aHints |= GDK_HINT_MAX_SIZE;
2086 if( m_bFullscreen && m_aMaxSize.Width() && m_aMaxSize.Height() )
2088 aGeo.max_width = m_aMaxSize.Width();
2089 aGeo.max_height = m_aMaxSize.Height();
2090 aHints |= GDK_HINT_MAX_SIZE;
2092 if( aHints )
2094 gtk_window_set_geometry_hints( GTK_WINDOW(m_pWindow),
2095 nullptr,
2096 &aGeo,
2097 GdkWindowHints( aHints ) );
2099 #endif
2102 void GtkSalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight )
2104 if( ! isChild() )
2106 m_aMaxSize = Size( nWidth, nHeight );
2107 setMinMaxSize();
2110 void GtkSalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight )
2112 if( ! isChild() )
2114 m_aMinSize = Size( nWidth, nHeight );
2115 if( m_pWindow )
2117 widget_set_size_request(nWidth, nHeight);
2118 setMinMaxSize();
2123 void GtkSalFrame::AllocateFrame()
2125 basegfx::B2IVector aFrameSize( maGeometry.width(), maGeometry.height() );
2126 if (m_pSurface && m_aFrameSize.getX() == aFrameSize.getX() &&
2127 m_aFrameSize.getY() == aFrameSize.getY() )
2128 return;
2130 if( aFrameSize.getX() == 0 )
2131 aFrameSize.setX( 1 );
2132 if( aFrameSize.getY() == 0 )
2133 aFrameSize.setY( 1 );
2135 if (m_pSurface)
2136 cairo_surface_destroy(m_pSurface);
2138 m_pSurface = surface_create_similar_surface(widget_get_surface(m_pWindow),
2139 CAIRO_CONTENT_COLOR_ALPHA,
2140 aFrameSize.getX(),
2141 aFrameSize.getY());
2142 m_aFrameSize = aFrameSize;
2144 cairo_surface_set_user_data(m_pSurface, SvpSalGraphics::getDamageKey(), &m_aDamageHandler, nullptr);
2145 SAL_INFO("vcl.gtk3", "allocated Frame size of " << maGeometry.width() << " x " << maGeometry.height());
2147 if (m_pGraphics)
2148 m_pGraphics->setSurface(m_pSurface, m_aFrameSize);
2151 void GtkSalFrame::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags )
2153 if( !m_pWindow || isChild( true, false ) )
2154 return;
2156 if( (nFlags & ( SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT )) &&
2157 (nWidth > 0 && nHeight > 0 ) // sometimes stupid things happen
2160 m_bDefaultSize = false;
2162 maGeometry.setSize({ nWidth, nHeight });
2164 if (isChild(false) || GTK_IS_POPOVER(m_pWindow))
2165 widget_set_size_request(nWidth, nHeight);
2166 else if( ! ( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED ) )
2167 window_resize(nWidth, nHeight);
2169 setMinMaxSize();
2171 else if( m_bDefaultSize )
2172 SetDefaultSize();
2174 m_bDefaultSize = false;
2176 if( nFlags & ( SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ) )
2178 if( m_pParent )
2180 if( AllSettings::GetLayoutRTL() )
2181 nX = m_pParent->maGeometry.width()-m_nWidthRequest-1-nX;
2182 nX += m_pParent->maGeometry.x();
2183 nY += m_pParent->maGeometry.y();
2186 if (nFlags & SAL_FRAME_POSSIZE_X)
2187 maGeometry.setX(nX);
2188 if (nFlags & SAL_FRAME_POSSIZE_Y)
2189 maGeometry.setY(nY);
2190 m_bGeometryIsProvisional = true;
2192 m_bDefaultPos = false;
2194 moveWindow(maGeometry.x(), maGeometry.y());
2196 updateScreenNumber();
2198 else if( m_bDefaultPos )
2199 Center();
2201 m_bDefaultPos = false;
2204 void GtkSalFrame::GetClientSize( tools::Long& rWidth, tools::Long& rHeight )
2206 if( m_pWindow && !(m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) )
2208 rWidth = maGeometry.width();
2209 rHeight = maGeometry.height();
2211 else
2212 rWidth = rHeight = 0;
2215 void GtkSalFrame::GetWorkArea( tools::Rectangle& rRect )
2217 GdkRectangle aRect;
2218 #if !GTK_CHECK_VERSION(4, 0, 0)
2219 GdkScreen *pScreen = gtk_widget_get_screen(m_pWindow);
2220 tools::Rectangle aRetRect;
2221 int max = gdk_screen_get_n_monitors (pScreen);
2222 for (int i = 0; i < max; ++i)
2224 gdk_screen_get_monitor_workarea(pScreen, i, &aRect);
2225 tools::Rectangle aMonitorRect(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height);
2226 aRetRect.Union(aMonitorRect);
2228 rRect = aRetRect;
2229 #else
2230 GdkDisplay* pDisplay = gtk_widget_get_display(m_pWindow);
2231 GdkSurface* gdkWindow = widget_get_surface(m_pWindow);
2232 GdkMonitor* pMonitor = gdk_display_get_monitor_at_surface(pDisplay, gdkWindow);
2233 gdk_monitor_get_geometry(pMonitor, &aRect);
2234 rRect = tools::Rectangle(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height);
2235 #endif
2238 SalFrame* GtkSalFrame::GetParent() const
2240 return m_pParent;
2243 void GtkSalFrame::SetWindowState(const vcl::WindowData* pState)
2245 if( ! m_pWindow || ! pState || isChild( true, false ) )
2246 return;
2248 const vcl::WindowDataMask nMaxGeometryMask = vcl::WindowDataMask::PosSize |
2249 vcl::WindowDataMask::MaximizedX | vcl::WindowDataMask::MaximizedY |
2250 vcl::WindowDataMask::MaximizedWidth | vcl::WindowDataMask::MaximizedHeight;
2252 if( (pState->mask() & vcl::WindowDataMask::State) &&
2253 ! ( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED ) &&
2254 (pState->state() & vcl::WindowState::Maximized) &&
2255 (pState->mask() & nMaxGeometryMask) == nMaxGeometryMask )
2257 resizeWindow(pState->width(), pState->height());
2258 moveWindow(pState->x(), pState->y());
2259 m_bDefaultPos = m_bDefaultSize = false;
2261 updateScreenNumber();
2263 m_nState = GdkToplevelState(m_nState | GDK_TOPLEVEL_STATE_MAXIMIZED);
2264 m_aRestorePosSize = pState->posSize();
2266 else if (pState->mask() & vcl::WindowDataMask::PosSize)
2268 sal_uInt16 nPosSizeFlags = 0;
2269 tools::Long nX = pState->x() - (m_pParent ? m_pParent->maGeometry.x() : 0);
2270 tools::Long nY = pState->y() - (m_pParent ? m_pParent->maGeometry.y() : 0);
2271 if (pState->mask() & vcl::WindowDataMask::X)
2272 nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
2273 else
2274 nX = maGeometry.x() - (m_pParent ? m_pParent->maGeometry.x() : 0);
2275 if (pState->mask() & vcl::WindowDataMask::Y)
2276 nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
2277 else
2278 nY = maGeometry.y() - (m_pParent ? m_pParent->maGeometry.y() : 0);
2279 if (pState->mask() & vcl::WindowDataMask::Width)
2280 nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
2281 if (pState->mask() & vcl::WindowDataMask::Height)
2282 nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
2283 SetPosSize(nX, nY, pState->width(), pState->height(), nPosSizeFlags);
2286 if (pState->mask() & vcl::WindowDataMask::State && !isChild())
2288 if (pState->state() & vcl::WindowState::Maximized)
2289 gtk_window_maximize( GTK_WINDOW(m_pWindow) );
2290 else
2291 gtk_window_unmaximize( GTK_WINDOW(m_pWindow) );
2292 /* #i42379# there is no rollup state in GDK; and rolled up windows are
2293 * (probably depending on the WM) reported as iconified. If we iconify a
2294 * window here that was e.g. a dialog, then it will be unmapped but still
2295 * not be displayed in the task list, so it's an iconified window that
2296 * the user cannot get out of this state. So do not set the iconified state
2297 * on windows with a parent (that is transient frames) since these tend
2298 * to not be represented in an icon task list.
2300 bool bMinimize = pState->state() & vcl::WindowState::Minimized && !m_pParent;
2301 #if GTK_CHECK_VERSION(4, 0, 0)
2302 if (bMinimize)
2303 gtk_window_minimize(GTK_WINDOW(m_pWindow));
2304 else
2305 gtk_window_unminimize(GTK_WINDOW(m_pWindow));
2306 #else
2307 if (bMinimize)
2308 gtk_window_iconify(GTK_WINDOW(m_pWindow));
2309 else
2310 gtk_window_deiconify(GTK_WINDOW(m_pWindow));
2311 #endif
2313 TriggerPaintEvent();
2316 namespace
2318 void GetPosAndSize(GtkWindow *pWindow, tools::Long& rX, tools::Long &rY, tools::Long &rWidth, tools::Long &rHeight)
2320 gint width, height;
2321 #if !GTK_CHECK_VERSION(4, 0, 0)
2322 gint root_x, root_y;
2323 gtk_window_get_position(GTK_WINDOW(pWindow), &root_x, &root_y);
2324 rX = root_x;
2325 rY = root_y;
2327 gtk_window_get_size(GTK_WINDOW(pWindow), &width, &height);
2328 #else
2329 rX = 0;
2330 rY = 0;
2331 gtk_window_get_default_size(GTK_WINDOW(pWindow), &width, &height);
2332 #endif
2333 rWidth = width;
2334 rHeight = height;
2337 tools::Rectangle GetPosAndSize(GtkWindow *pWindow)
2339 tools::Long nX, nY, nWidth, nHeight;
2340 GetPosAndSize(pWindow, nX, nY, nWidth, nHeight);
2341 return tools::Rectangle(nX, nY, nX + nWidth, nY + nHeight);
2345 bool GtkSalFrame::GetWindowState(vcl::WindowData* pState)
2347 pState->setState(vcl::WindowState::Normal);
2348 pState->setMask(vcl::WindowDataMask::PosSizeState);
2350 // rollup ? gtk 2.2 does not seem to support the shaded state
2351 if( m_nState & GDK_TOPLEVEL_STATE_MINIMIZED )
2352 pState->rState() |= vcl::WindowState::Minimized;
2353 if( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED )
2355 pState->rState() |= vcl::WindowState::Maximized;
2356 pState->setPosSize(m_aRestorePosSize);
2357 tools::Rectangle aPosSize = GetPosAndSize(GTK_WINDOW(m_pWindow));
2358 pState->SetMaximizedX(aPosSize.Left());
2359 pState->SetMaximizedY(aPosSize.Top());
2360 pState->SetMaximizedWidth(aPosSize.GetWidth());
2361 pState->SetMaximizedHeight(aPosSize.GetHeight());
2362 pState->rMask() |= vcl::WindowDataMask::MaximizedX |
2363 vcl::WindowDataMask::MaximizedY |
2364 vcl::WindowDataMask::MaximizedWidth |
2365 vcl::WindowDataMask::MaximizedHeight;
2367 else
2368 pState->setPosSize(GetPosAndSize(GTK_WINDOW(m_pWindow)));
2370 return true;
2373 void GtkSalFrame::SetScreen( unsigned int nNewScreen, SetType eType, tools::Rectangle const *pSize )
2375 if( !m_pWindow )
2376 return;
2378 if (maGeometry.screen() == nNewScreen && eType == SetType::RetainSize)
2379 return;
2381 #if !GTK_CHECK_VERSION(4, 0, 0)
2382 int nX = maGeometry.x(), nY = maGeometry.y(),
2383 nWidth = maGeometry.width(), nHeight = maGeometry.height();
2384 GdkScreen *pScreen = nullptr;
2385 GdkRectangle aNewMonitor;
2387 bool bSpanAllScreens = nNewScreen == static_cast<unsigned int>(-1);
2388 bool bSpanMonitorsWhenFullscreen = bSpanAllScreens && getDisplay()->getSystem()->GetDisplayScreenCount() > 1;
2389 gint nMonitor = -1;
2390 if (bSpanMonitorsWhenFullscreen) //span all screens
2392 pScreen = gtk_widget_get_screen( m_pWindow );
2393 aNewMonitor.x = 0;
2394 aNewMonitor.y = 0;
2395 aNewMonitor.width = gdk_screen_get_width(pScreen);
2396 aNewMonitor.height = gdk_screen_get_height(pScreen);
2398 else
2400 bool bSameMonitor = false;
2402 if (!bSpanAllScreens)
2404 pScreen = getDisplay()->getSystem()->getScreenMonitorFromIdx( nNewScreen, nMonitor );
2405 if (!pScreen)
2407 g_warning ("Attempt to move GtkSalFrame to invalid screen %d => "
2408 "fallback to current\n", nNewScreen);
2412 if (!pScreen)
2414 pScreen = gtk_widget_get_screen( m_pWindow );
2415 bSameMonitor = true;
2418 // Heavy lifting, need to move screen ...
2419 if( pScreen != gtk_widget_get_screen( m_pWindow ))
2420 gtk_window_set_screen( GTK_WINDOW( m_pWindow ), pScreen );
2422 gint nOldMonitor = gdk_screen_get_monitor_at_window(
2423 pScreen, widget_get_surface( m_pWindow ) );
2424 if (bSameMonitor)
2425 nMonitor = nOldMonitor;
2427 #if OSL_DEBUG_LEVEL > 1
2428 if( nMonitor == nOldMonitor )
2429 g_warning( "An apparently pointless SetScreen - should we elide it ?" );
2430 #endif
2432 GdkRectangle aOldMonitor;
2433 gdk_screen_get_monitor_geometry( pScreen, nOldMonitor, &aOldMonitor );
2434 gdk_screen_get_monitor_geometry( pScreen, nMonitor, &aNewMonitor );
2436 nX = aNewMonitor.x + nX - aOldMonitor.x;
2437 nY = aNewMonitor.y + nY - aOldMonitor.y;
2440 bool bResize = false;
2441 bool bVisible = gtk_widget_get_mapped( m_pWindow );
2442 if( bVisible )
2443 Show( false );
2445 if( eType == SetType::Fullscreen )
2447 nX = aNewMonitor.x;
2448 nY = aNewMonitor.y;
2449 nWidth = aNewMonitor.width;
2450 nHeight = aNewMonitor.height;
2451 bResize = true;
2453 // #i110881# for the benefit of compiz set a max size here
2454 // else setting to fullscreen fails for unknown reasons
2455 m_aMaxSize.setWidth( aNewMonitor.width );
2456 m_aMaxSize.setHeight( aNewMonitor.height );
2459 if( pSize && eType == SetType::UnFullscreen )
2461 nX = pSize->Left();
2462 nY = pSize->Top();
2463 nWidth = pSize->GetWidth();
2464 nHeight = pSize->GetHeight();
2465 bResize = true;
2468 if (bResize)
2470 // temporarily re-sizeable
2471 if( !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
2472 gtk_window_set_resizable( GTK_WINDOW(m_pWindow), true );
2473 window_resize(nWidth, nHeight);
2476 gtk_window_move(GTK_WINDOW(m_pWindow), nX, nY);
2478 GdkFullscreenMode eMode =
2479 bSpanMonitorsWhenFullscreen ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR;
2481 gdk_window_set_fullscreen_mode(widget_get_surface(m_pWindow), eMode);
2483 GtkWidget* pMenuBarContainerWidget = m_pSalMenu ? m_pSalMenu->GetMenuBarContainerWidget() : nullptr;
2484 if( eType == SetType::Fullscreen )
2486 if (pMenuBarContainerWidget)
2487 gtk_widget_hide(pMenuBarContainerWidget);
2488 if (bSpanMonitorsWhenFullscreen)
2489 gtk_window_fullscreen(GTK_WINDOW(m_pWindow));
2490 else
2491 gtk_window_fullscreen_on_monitor(GTK_WINDOW(m_pWindow), pScreen, nMonitor);
2494 else if( eType == SetType::UnFullscreen )
2496 if (pMenuBarContainerWidget)
2497 gtk_widget_show(pMenuBarContainerWidget);
2498 gtk_window_unfullscreen( GTK_WINDOW( m_pWindow ) );
2501 if( eType == SetType::UnFullscreen &&
2502 !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
2503 gtk_window_set_resizable( GTK_WINDOW( m_pWindow ), FALSE );
2505 // FIXME: we should really let gtk+ handle our widget hierarchy ...
2506 if( m_pParent && gtk_widget_get_screen( m_pParent->m_pWindow ) != pScreen )
2507 SetParent( nullptr );
2509 std::list< GtkSalFrame* > aChildren = m_aChildren;
2510 for (auto const& child : aChildren)
2511 child->SetScreen( nNewScreen, SetType::RetainSize );
2513 m_bDefaultPos = m_bDefaultSize = false;
2514 updateScreenNumber();
2516 if( bVisible )
2517 Show( true );
2519 #else
2520 (void)pSize; // assume restore will restore the original size without our help
2522 bool bSpanMonitorsWhenFullscreen = nNewScreen == static_cast<unsigned int>(-1);
2524 GdkFullscreenMode eMode =
2525 bSpanMonitorsWhenFullscreen ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR;
2527 g_object_set(widget_get_surface(m_pWindow), "fullscreen-mode", eMode, nullptr);
2529 GtkWidget* pMenuBarContainerWidget = m_pSalMenu ? m_pSalMenu->GetMenuBarContainerWidget() : nullptr;
2530 if (eType == SetType::Fullscreen)
2532 if (!(m_nStyle & SalFrameStyleFlags::SIZEABLE))
2534 // temp make it resizable, restore when unfullscreened
2535 gtk_window_set_resizable(GTK_WINDOW(m_pWindow), true);
2538 if (pMenuBarContainerWidget)
2539 gtk_widget_hide(pMenuBarContainerWidget);
2540 if (bSpanMonitorsWhenFullscreen)
2541 gtk_window_fullscreen(GTK_WINDOW(m_pWindow));
2542 else
2544 GdkDisplay* pDisplay = gtk_widget_get_display(m_pWindow);
2545 GListModel* pList = gdk_display_get_monitors(pDisplay);
2546 GdkMonitor* pMonitor = static_cast<GdkMonitor*>(g_list_model_get_item(pList, nNewScreen));
2547 if (!pMonitor)
2548 pMonitor = gdk_display_get_monitor_at_surface(pDisplay, widget_get_surface(m_pWindow));
2549 gtk_window_fullscreen_on_monitor(GTK_WINDOW(m_pWindow), pMonitor);
2552 else if (eType == SetType::UnFullscreen)
2554 if (pMenuBarContainerWidget)
2555 gtk_widget_show(pMenuBarContainerWidget);
2556 gtk_window_unfullscreen(GTK_WINDOW(m_pWindow));
2558 if (!(m_nStyle & SalFrameStyleFlags::SIZEABLE))
2560 // restore temp resizability
2561 gtk_window_set_resizable(GTK_WINDOW(m_pWindow), false);
2565 for (auto const& child : m_aChildren)
2566 child->SetScreen(nNewScreen, SetType::RetainSize);
2568 m_bDefaultPos = m_bDefaultSize = false;
2569 updateScreenNumber();
2570 #endif
2573 void GtkSalFrame::SetScreenNumber( unsigned int nNewScreen )
2575 SetScreen( nNewScreen, SetType::RetainSize );
2578 void GtkSalFrame::updateWMClass()
2580 if (!DLSYM_GDK_IS_X11_DISPLAY(getGdkDisplay()))
2581 return;
2583 if (!gtk_widget_get_realized(m_pWindow))
2584 return;
2586 OString aResClass = OUStringToOString(m_sWMClass, RTL_TEXTENCODING_ASCII_US);
2587 const char *pResClass = !aResClass.isEmpty() ? aResClass.getStr() :
2588 SalGenericSystem::getFrameClassName();
2589 XClassHint* pClass = XAllocClassHint();
2590 OString aResName = SalGenericSystem::getFrameResName();
2591 pClass->res_name = const_cast<char*>(aResName.getStr());
2592 pClass->res_class = const_cast<char*>(pResClass);
2593 Display *display = gdk_x11_display_get_xdisplay(getGdkDisplay());
2594 XSetClassHint( display,
2595 GtkSalFrame::GetNativeWindowHandle(m_pWindow),
2596 pClass );
2597 XFree( pClass );
2600 void GtkSalFrame::SetApplicationID( const OUString &rWMClass )
2602 if( rWMClass != m_sWMClass && ! isChild() )
2604 m_sWMClass = rWMClass;
2605 updateWMClass();
2607 for (auto const& child : m_aChildren)
2608 child->SetApplicationID(rWMClass);
2612 void GtkSalFrame::ShowFullScreen( bool bFullScreen, sal_Int32 nScreen )
2614 m_bFullscreen = bFullScreen;
2616 if( !m_pWindow || isChild() )
2617 return;
2619 if( bFullScreen )
2621 m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(m_pWindow));
2622 SetScreen( nScreen, SetType::Fullscreen );
2624 else
2626 SetScreen( nScreen, SetType::UnFullscreen,
2627 !m_aRestorePosSize.IsEmpty() ? &m_aRestorePosSize : nullptr );
2628 m_aRestorePosSize = tools::Rectangle();
2632 void GtkSalFrame::SessionManagerInhibit(bool bStart, ApplicationInhibitFlags eType, std::u16string_view sReason, const char* application_id)
2634 guint nWindow(0);
2635 std::optional<Display*> aDisplay;
2637 if (DLSYM_GDK_IS_X11_DISPLAY(getGdkDisplay()))
2639 nWindow = GtkSalFrame::GetNativeWindowHandle(m_pWindow);
2640 aDisplay = gdk_x11_display_get_xdisplay(getGdkDisplay());
2643 m_SessionManagerInhibitor.inhibit(bStart, sReason, eType,
2644 nWindow, aDisplay, application_id);
2647 void GtkSalFrame::StartPresentation( bool bStart )
2649 SessionManagerInhibit(bStart, APPLICATION_INHIBIT_IDLE, u"presentation", nullptr);
2652 void GtkSalFrame::SetAlwaysOnTop( bool bOnTop )
2654 #if !GTK_CHECK_VERSION(4, 0, 0)
2655 if( m_pWindow )
2656 gtk_window_set_keep_above( GTK_WINDOW( m_pWindow ), bOnTop );
2657 #else
2658 (void)bOnTop;
2659 #endif
2662 static guint32 nLastUserInputTime = GDK_CURRENT_TIME;
2664 guint32 GtkSalFrame::GetLastInputEventTime()
2666 return nLastUserInputTime;
2669 void GtkSalFrame::UpdateLastInputEventTime(guint32 nUserInputTime)
2671 //gtk3 can generate a synthetic crossing event with a useless 0
2672 //(GDK_CURRENT_TIME) timestamp on showing a menu from the main
2673 //menubar, which is unhelpful, so ignore the 0 timestamps
2674 if (nUserInputTime == GDK_CURRENT_TIME)
2675 return;
2676 nLastUserInputTime = nUserInputTime;
2679 void GtkSalFrame::ToTop( SalFrameToTop nFlags )
2681 if( !m_pWindow )
2682 return;
2684 if( isChild( false ) )
2685 GrabFocus();
2686 else if( gtk_widget_get_mapped( m_pWindow ) )
2688 auto nTimestamp = GetLastInputEventTime();
2689 #ifdef GDK_WINDOWING_X11
2690 GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
2691 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
2692 nTimestamp = gdk_x11_display_get_user_time(pDisplay);
2693 #endif
2694 if (!(nFlags & SalFrameToTop::GrabFocusOnly))
2695 gtk_window_present_with_time(GTK_WINDOW(m_pWindow), nTimestamp);
2696 #if !GTK_CHECK_VERSION(4, 0, 0)
2697 else
2698 gdk_window_focus(widget_get_surface(m_pWindow), nTimestamp);
2699 #endif
2700 GrabFocus();
2702 else
2704 if( nFlags & SalFrameToTop::RestoreWhenMin )
2705 gtk_window_present( GTK_WINDOW(m_pWindow) );
2709 void GtkSalFrame::SetPointer( PointerStyle ePointerStyle )
2711 if( !m_pWindow || ePointerStyle == m_ePointerStyle )
2712 return;
2714 m_ePointerStyle = ePointerStyle;
2715 GdkCursor *pCursor = getDisplay()->getCursor( ePointerStyle );
2716 widget_set_cursor(GTK_WIDGET(m_pWindow), pCursor);
2719 void GtkSalFrame::grabPointer( bool bGrab, bool bKeyboardAlso, bool bOwnerEvents )
2721 if (bGrab)
2723 // tdf#135779 move focus back inside usual input window out of any
2724 // other gtk widgets before grabbing the pointer
2725 GrabFocus();
2728 static const char* pEnv = getenv( "SAL_NO_MOUSEGRABS" );
2729 if (pEnv && *pEnv)
2730 return;
2732 if (!m_pWindow)
2733 return;
2735 #if !GTK_CHECK_VERSION(4, 0, 0)
2736 GdkSeat* pSeat = gdk_display_get_default_seat(getGdkDisplay());
2737 if (bGrab)
2739 GdkSeatCapabilities eCapability = bKeyboardAlso ? GDK_SEAT_CAPABILITY_ALL : GDK_SEAT_CAPABILITY_ALL_POINTING;
2740 gdk_seat_grab(pSeat, widget_get_surface(getMouseEventWidget()), eCapability,
2741 bOwnerEvents, nullptr, nullptr, nullptr, nullptr);
2743 else
2745 gdk_seat_ungrab(pSeat);
2747 #else
2748 (void)bKeyboardAlso;
2749 (void)bOwnerEvents;
2750 #endif
2753 void GtkSalFrame::CaptureMouse( bool bCapture )
2755 getDisplay()->CaptureMouse( bCapture ? this : nullptr );
2758 void GtkSalFrame::SetPointerPos( tools::Long nX, tools::Long nY )
2760 #if !GTK_CHECK_VERSION(4, 0, 0)
2761 GtkSalFrame* pFrame = this;
2762 while( pFrame && pFrame->isChild( false ) )
2763 pFrame = pFrame->m_pParent;
2764 if( ! pFrame )
2765 return;
2767 GdkScreen *pScreen = gtk_widget_get_screen(pFrame->m_pWindow);
2768 GdkDisplay *pDisplay = gdk_screen_get_display( pScreen );
2770 /* when the application tries to center the mouse in the dialog the
2771 * window isn't mapped already. So use coordinates relative to the root window.
2773 unsigned int nWindowLeft = maGeometry.x() + nX;
2774 unsigned int nWindowTop = maGeometry.y() + nY;
2776 GdkDeviceManager* pManager = gdk_display_get_device_manager(pDisplay);
2777 gdk_device_warp(gdk_device_manager_get_client_pointer(pManager), pScreen, nWindowLeft, nWindowTop);
2779 // #i38648# ask for the next motion hint
2780 gint x, y;
2781 GdkModifierType mask;
2782 gdk_window_get_pointer( widget_get_surface(pFrame->m_pWindow) , &x, &y, &mask );
2783 #else
2784 (void)nX;
2785 (void)nY;
2786 #endif
2789 void GtkSalFrame::Flush()
2791 gdk_display_flush( getGdkDisplay() );
2794 void GtkSalFrame::KeyCodeToGdkKey(const vcl::KeyCode& rKeyCode,
2795 guint* pGdkKeyCode, GdkModifierType *pGdkModifiers)
2797 if ( pGdkKeyCode == nullptr || pGdkModifiers == nullptr )
2798 return;
2800 // Get GDK key modifiers
2801 GdkModifierType nModifiers = GdkModifierType(0);
2803 if ( rKeyCode.IsShift() )
2804 nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_SHIFT_MASK );
2806 if ( rKeyCode.IsMod1() )
2807 nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_CONTROL_MASK );
2809 if ( rKeyCode.IsMod2() )
2810 nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_ALT_MASK );
2812 *pGdkModifiers = nModifiers;
2814 // Get GDK keycode.
2815 guint nKeyCode = 0;
2817 guint nCode = rKeyCode.GetCode();
2819 if ( nCode >= KEY_0 && nCode <= KEY_9 )
2820 nKeyCode = ( nCode - KEY_0 ) + GDK_KEY_0;
2821 else if ( nCode >= KEY_A && nCode <= KEY_Z )
2822 nKeyCode = ( nCode - KEY_A ) + GDK_KEY_A;
2823 else if ( nCode >= KEY_F1 && nCode <= KEY_F26 )
2824 nKeyCode = ( nCode - KEY_F1 ) + GDK_KEY_F1;
2825 else
2827 switch (nCode)
2829 case KEY_DOWN: nKeyCode = GDK_KEY_Down; break;
2830 case KEY_UP: nKeyCode = GDK_KEY_Up; break;
2831 case KEY_LEFT: nKeyCode = GDK_KEY_Left; break;
2832 case KEY_RIGHT: nKeyCode = GDK_KEY_Right; break;
2833 case KEY_HOME: nKeyCode = GDK_KEY_Home; break;
2834 case KEY_END: nKeyCode = GDK_KEY_End; break;
2835 case KEY_PAGEUP: nKeyCode = GDK_KEY_Page_Up; break;
2836 case KEY_PAGEDOWN: nKeyCode = GDK_KEY_Page_Down; break;
2837 case KEY_RETURN: nKeyCode = GDK_KEY_Return; break;
2838 case KEY_ESCAPE: nKeyCode = GDK_KEY_Escape; break;
2839 case KEY_TAB: nKeyCode = GDK_KEY_Tab; break;
2840 case KEY_BACKSPACE: nKeyCode = GDK_KEY_BackSpace; break;
2841 case KEY_SPACE: nKeyCode = GDK_KEY_space; break;
2842 case KEY_INSERT: nKeyCode = GDK_KEY_Insert; break;
2843 case KEY_DELETE: nKeyCode = GDK_KEY_Delete; break;
2844 case KEY_ADD: nKeyCode = GDK_KEY_plus; break;
2845 case KEY_SUBTRACT: nKeyCode = GDK_KEY_minus; break;
2846 case KEY_MULTIPLY: nKeyCode = GDK_KEY_asterisk; break;
2847 case KEY_DIVIDE: nKeyCode = GDK_KEY_slash; break;
2848 case KEY_POINT: nKeyCode = GDK_KEY_period; break;
2849 case KEY_COMMA: nKeyCode = GDK_KEY_comma; break;
2850 case KEY_LESS: nKeyCode = GDK_KEY_less; break;
2851 case KEY_GREATER: nKeyCode = GDK_KEY_greater; break;
2852 case KEY_EQUAL: nKeyCode = GDK_KEY_equal; break;
2853 case KEY_FIND: nKeyCode = GDK_KEY_Find; break;
2854 case KEY_CONTEXTMENU: nKeyCode = GDK_KEY_Menu; break;
2855 case KEY_HELP: nKeyCode = GDK_KEY_Help; break;
2856 case KEY_UNDO: nKeyCode = GDK_KEY_Undo; break;
2857 case KEY_REPEAT: nKeyCode = GDK_KEY_Redo; break;
2858 case KEY_DECIMAL: nKeyCode = GDK_KEY_KP_Decimal; break;
2859 case KEY_TILDE: nKeyCode = GDK_KEY_asciitilde; break;
2860 case KEY_QUOTELEFT: nKeyCode = GDK_KEY_quoteleft; break;
2861 case KEY_BRACKETLEFT: nKeyCode = GDK_KEY_bracketleft; break;
2862 case KEY_BRACKETRIGHT: nKeyCode = GDK_KEY_bracketright; break;
2863 case KEY_SEMICOLON: nKeyCode = GDK_KEY_semicolon; break;
2864 case KEY_QUOTERIGHT: nKeyCode = GDK_KEY_quoteright; break;
2865 case KEY_RIGHTCURLYBRACKET: nKeyCode = GDK_KEY_braceright; break;
2866 case KEY_COLON: nKeyCode = GDK_KEY_colon; break;
2868 // Special cases
2869 case KEY_COPY: nKeyCode = GDK_KEY_Copy; break;
2870 case KEY_CUT: nKeyCode = GDK_KEY_Cut; break;
2871 case KEY_PASTE: nKeyCode = GDK_KEY_Paste; break;
2872 case KEY_OPEN: nKeyCode = GDK_KEY_Open; break;
2876 *pGdkKeyCode = nKeyCode;
2879 OUString GtkSalFrame::GetKeyName( sal_uInt16 nKeyCode )
2881 guint nGtkKeyCode;
2882 GdkModifierType nGtkModifiers;
2883 KeyCodeToGdkKey(nKeyCode, &nGtkKeyCode, &nGtkModifiers );
2885 gchar* pName = gtk_accelerator_get_label(nGtkKeyCode, nGtkModifiers);
2886 OUString aRet = OStringToOUString(pName, RTL_TEXTENCODING_UTF8);
2887 g_free(pName);
2888 return aRet;
2891 GdkDisplay *GtkSalFrame::getGdkDisplay()
2893 return GetGtkSalData()->GetGdkDisplay();
2896 GtkSalDisplay *GtkSalFrame::getDisplay()
2898 return GetGtkSalData()->GetGtkDisplay();
2901 SalFrame::SalPointerState GtkSalFrame::GetPointerState()
2903 SalPointerState aState;
2904 #if !GTK_CHECK_VERSION(4, 0, 0)
2905 GdkScreen* pScreen;
2906 gint x, y;
2907 GdkModifierType aMask;
2908 gdk_display_get_pointer( getGdkDisplay(), &pScreen, &x, &y, &aMask );
2909 aState.maPos = Point( x - maGeometry.x(), y - maGeometry.y() );
2910 aState.mnState = GetMouseModCode( aMask );
2911 #endif
2912 return aState;
2915 KeyIndicatorState GtkSalFrame::GetIndicatorState()
2917 KeyIndicatorState nState = KeyIndicatorState::NONE;
2919 #if !GTK_CHECK_VERSION(4, 0, 0)
2920 GdkKeymap *pKeyMap = gdk_keymap_get_for_display(getGdkDisplay());
2922 if (gdk_keymap_get_caps_lock_state(pKeyMap))
2923 nState |= KeyIndicatorState::CAPSLOCK;
2924 if (gdk_keymap_get_num_lock_state(pKeyMap))
2925 nState |= KeyIndicatorState::NUMLOCK;
2926 if (gdk_keymap_get_scroll_lock_state(pKeyMap))
2927 nState |= KeyIndicatorState::SCROLLLOCK;
2928 #endif
2930 return nState;
2933 void GtkSalFrame::SimulateKeyPress( sal_uInt16 nKeyCode )
2935 g_warning ("missing simulate keypress %d", nKeyCode);
2938 void GtkSalFrame::SetInputContext( SalInputContext* pContext )
2940 if( ! pContext )
2941 return;
2943 if( ! (pContext->mnOptions & InputContextFlags::Text) )
2944 return;
2946 // create a new im context
2947 if( ! m_pIMHandler )
2948 m_pIMHandler.reset( new IMHandler( this ) );
2951 void GtkSalFrame::EndExtTextInput( EndExtTextInputFlags nFlags )
2953 if( m_pIMHandler )
2954 m_pIMHandler->endExtTextInput( nFlags );
2957 bool GtkSalFrame::MapUnicodeToKeyCode( sal_Unicode , LanguageType , vcl::KeyCode& )
2959 // not supported yet
2960 return false;
2963 LanguageType GtkSalFrame::GetInputLanguage()
2965 return LANGUAGE_DONTKNOW;
2968 void GtkSalFrame::UpdateSettings( AllSettings& rSettings )
2970 if( ! m_pWindow )
2971 return;
2973 GtkSalGraphics* pGraphics = m_pGraphics.get();
2974 bool bFreeGraphics = false;
2975 if( ! pGraphics )
2977 pGraphics = static_cast<GtkSalGraphics*>(AcquireGraphics());
2978 if ( !pGraphics )
2980 SAL_WARN("vcl.gtk3", "Could not get graphics - unable to update settings");
2981 return;
2983 bFreeGraphics = true;
2986 pGraphics->UpdateSettings( rSettings );
2988 if( bFreeGraphics )
2989 ReleaseGraphics( pGraphics );
2992 void GtkSalFrame::Beep()
2994 gdk_display_beep( getGdkDisplay() );
2997 const SystemEnvData* GtkSalFrame::GetSystemData() const
2999 return &m_aSystemData;
3002 void GtkSalFrame::ResolveWindowHandle(SystemEnvData& rData) const
3004 if (!rData.pWidget)
3005 return;
3006 SAL_WARN("vcl.gtk3", "its undesirable to need the NativeWindowHandle, see tdf#139609");
3007 rData.SetWindowHandle(GetNativeWindowHandle(static_cast<GtkWidget*>(rData.pWidget)));
3010 void GtkSalFrame::SetParent( SalFrame* pNewParent )
3012 GtkWindow* pWindow = GTK_IS_WINDOW(m_pWindow) ? GTK_WINDOW(m_pWindow) : nullptr;
3013 if (m_pParent)
3015 if (pWindow && GTK_IS_WINDOW(m_pParent->m_pWindow))
3016 gtk_window_group_remove_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), pWindow);
3017 m_pParent->m_aChildren.remove(this);
3019 m_pParent = static_cast<GtkSalFrame*>(pNewParent);
3020 if (m_pParent)
3022 m_pParent->m_aChildren.push_back(this);
3023 if (pWindow && GTK_IS_WINDOW(m_pParent->m_pWindow))
3024 gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), pWindow);
3026 if (!isChild() && pWindow)
3027 gtk_window_set_transient_for( pWindow,
3028 (m_pParent && ! m_pParent->isChild(true,false)) ? GTK_WINDOW(m_pParent->m_pWindow) : nullptr
3032 void GtkSalFrame::SetPluginParent( SystemParentData* )
3034 //FIXME: no SetPluginParent impl. for gtk3
3037 void GtkSalFrame::ResetClipRegion()
3039 #if !GTK_CHECK_VERSION(4, 0, 0)
3040 if( m_pWindow )
3041 gdk_window_shape_combine_region( widget_get_surface( m_pWindow ), nullptr, 0, 0 );
3042 #endif
3045 void GtkSalFrame::BeginSetClipRegion( sal_uInt32 )
3047 if( m_pRegion )
3048 cairo_region_destroy( m_pRegion );
3049 m_pRegion = cairo_region_create();
3052 void GtkSalFrame::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
3054 if( m_pRegion )
3056 GdkRectangle aRect;
3057 aRect.x = nX;
3058 aRect.y = nY;
3059 aRect.width = nWidth;
3060 aRect.height = nHeight;
3061 cairo_region_union_rectangle( m_pRegion, &aRect );
3065 void GtkSalFrame::EndSetClipRegion()
3067 #if !GTK_CHECK_VERSION(4, 0, 0)
3068 if( m_pWindow && m_pRegion )
3069 gdk_window_shape_combine_region( widget_get_surface(m_pWindow), m_pRegion, 0, 0 );
3070 #endif
3073 void GtkSalFrame::PositionByToolkit(const tools::Rectangle& rRect, FloatWinPopupFlags nFlags)
3075 if (ImplGetSVData()->maNWFData.mbCanDetermineWindowPosition)
3076 return;
3078 m_aFloatRect = rRect;
3079 m_nFloatFlags = nFlags;
3080 m_bFloatPositioned = true;
3083 void GtkSalFrame::SetModal(bool bModal)
3085 if (!m_pWindow)
3086 return;
3087 gtk_window_set_modal(GTK_WINDOW(m_pWindow), bModal);
3090 bool GtkSalFrame::GetModal() const
3092 if (!m_pWindow)
3093 return false;
3094 return gtk_window_get_modal(GTK_WINDOW(m_pWindow));
3097 gboolean GtkSalFrame::signalTooltipQuery(GtkWidget*, gint /*x*/, gint /*y*/,
3098 gboolean /*keyboard_mode*/, GtkTooltip *tooltip,
3099 gpointer frame)
3101 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3102 if (pThis->m_aTooltip.isEmpty() || pThis->m_bTooltipBlocked)
3103 return false;
3104 gtk_tooltip_set_text(tooltip,
3105 OUStringToOString(pThis->m_aTooltip, RTL_TEXTENCODING_UTF8).getStr());
3106 GdkRectangle aHelpArea;
3107 aHelpArea.x = pThis->m_aHelpArea.Left();
3108 aHelpArea.y = pThis->m_aHelpArea.Top();
3109 aHelpArea.width = pThis->m_aHelpArea.GetWidth();
3110 aHelpArea.height = pThis->m_aHelpArea.GetHeight();
3111 if (AllSettings::GetLayoutRTL())
3112 aHelpArea.x = pThis->maGeometry.width()-aHelpArea.width-1-aHelpArea.x;
3113 gtk_tooltip_set_tip_area(tooltip, &aHelpArea);
3114 return true;
3117 bool GtkSalFrame::ShowTooltip(const OUString& rHelpText, const tools::Rectangle& rHelpArea)
3119 m_aTooltip = rHelpText;
3120 m_aHelpArea = rHelpArea;
3121 gtk_widget_trigger_tooltip_query(getMouseEventWidget());
3122 return true;
3125 void GtkSalFrame::BlockTooltip()
3127 m_bTooltipBlocked = true;
3130 void GtkSalFrame::UnblockTooltip()
3132 m_bTooltipBlocked = false;
3135 void GtkSalFrame::HideTooltip()
3137 m_aTooltip.clear();
3138 GtkWidget* pEventWidget = getMouseEventWidget();
3139 gtk_widget_trigger_tooltip_query(pEventWidget);
3142 namespace
3144 void set_pointing_to(GtkPopover *pPopOver, vcl::Window* pParent, const tools::Rectangle& rHelpArea, const SalFrameGeometry& rGeometry)
3146 GdkRectangle aRect;
3147 aRect.x = FloatingWindow::ImplConvertToAbsPos(pParent, rHelpArea).Left() - rGeometry.x();
3148 aRect.y = rHelpArea.Top();
3149 aRect.width = 1;
3150 aRect.height = 1;
3152 GtkPositionType ePos = gtk_popover_get_position(pPopOver);
3153 switch (ePos)
3155 case GTK_POS_BOTTOM:
3156 case GTK_POS_TOP:
3157 aRect.width = rHelpArea.GetWidth();
3158 break;
3159 case GTK_POS_RIGHT:
3160 case GTK_POS_LEFT:
3161 aRect.height = rHelpArea.GetHeight();
3162 break;
3165 gtk_popover_set_pointing_to(pPopOver, &aRect);
3169 void* GtkSalFrame::ShowPopover(const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea, QuickHelpFlags nFlags)
3171 #if !GTK_CHECK_VERSION(4, 0, 0)
3172 GtkWidget *pWidget = gtk_popover_new(getMouseEventWidget());
3173 #else
3174 GtkWidget *pWidget = gtk_popover_new();
3175 gtk_widget_set_parent(pWidget, getMouseEventWidget());
3176 #endif
3177 OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
3178 GtkWidget *pLabel = gtk_label_new(sUTF.getStr());
3179 #if !GTK_CHECK_VERSION(4, 0, 0)
3180 gtk_container_add(GTK_CONTAINER(pWidget), pLabel);
3181 #else
3182 gtk_popover_set_child(GTK_POPOVER(pWidget), pLabel);
3183 #endif
3185 if (nFlags & QuickHelpFlags::Top)
3186 gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_BOTTOM);
3187 else if (nFlags & QuickHelpFlags::Bottom)
3188 gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_TOP);
3189 else if (nFlags & QuickHelpFlags::Left)
3190 gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_RIGHT);
3191 else if (nFlags & QuickHelpFlags::Right)
3192 gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_LEFT);
3194 set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry);
3196 #if !GTK_CHECK_VERSION(4, 0, 0)
3197 gtk_popover_set_modal(GTK_POPOVER(pWidget), false);
3198 #else
3199 gtk_popover_set_autohide(GTK_POPOVER(pWidget), false);
3200 #endif
3202 gtk_widget_show(pLabel);
3203 gtk_widget_show(pWidget);
3205 return pWidget;
3208 bool GtkSalFrame::UpdatePopover(void* nId, const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea)
3210 GtkWidget *pWidget = static_cast<GtkWidget*>(nId);
3212 set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry);
3214 #if !GTK_CHECK_VERSION(4, 0, 0)
3215 GtkWidget *pLabel = gtk_bin_get_child(GTK_BIN(pWidget));
3216 #else
3217 GtkWidget *pLabel = gtk_popover_get_child(GTK_POPOVER(pWidget));
3218 #endif
3219 OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
3220 gtk_label_set_text(GTK_LABEL(pLabel), sUTF.getStr());
3222 return true;
3225 bool GtkSalFrame::HidePopover(void* nId)
3227 GtkWidget *pWidget = static_cast<GtkWidget*>(nId);
3228 #if !GTK_CHECK_VERSION(4, 0, 0)
3229 gtk_widget_destroy(pWidget);
3230 #else
3231 g_clear_pointer(&pWidget, gtk_widget_unparent);
3232 #endif
3233 return true;
3236 void GtkSalFrame::addGrabLevel()
3238 #if !GTK_CHECK_VERSION(4, 0, 0)
3239 if (m_nGrabLevel == 0)
3240 gtk_grab_add(getMouseEventWidget());
3241 #endif
3242 ++m_nGrabLevel;
3245 void GtkSalFrame::removeGrabLevel()
3247 if (m_nGrabLevel > 0)
3249 --m_nGrabLevel;
3250 #if !GTK_CHECK_VERSION(4, 0, 0)
3251 if (m_nGrabLevel == 0)
3252 gtk_grab_remove(getMouseEventWidget());
3253 #endif
3257 void GtkSalFrame::closePopup()
3259 if (!m_nFloats)
3260 return;
3261 ImplSVData* pSVData = ImplGetSVData();
3262 if (!pSVData->mpWinData->mpFirstFloat)
3263 return;
3264 if (pSVData->mpWinData->mpFirstFloat->ImplGetFrame() != this)
3265 return;
3266 pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll);
3269 #if !GTK_CHECK_VERSION(4, 0, 0)
3270 namespace
3272 //tdf#117981 translate embedded video window mouse events to parent coordinates
3273 void translate_coords(GdkWindow* pSourceWindow, GtkWidget* pTargetWidget, int& rEventX, int& rEventY)
3275 gpointer user_data=nullptr;
3276 gdk_window_get_user_data(pSourceWindow, &user_data);
3277 GtkWidget* pRealEventWidget = static_cast<GtkWidget*>(user_data);
3278 if (pRealEventWidget)
3280 gtk_coord fX(0.0), fY(0.0);
3281 gtk_widget_translate_coordinates(pRealEventWidget, pTargetWidget, rEventX, rEventY, &fX, &fY);
3282 rEventX = fX;
3283 rEventY = fY;
3287 #endif
3289 void GtkSalFrame::GrabFocus()
3291 GtkWidget* pGrabWidget;
3292 #if !GTK_CHECK_VERSION(4, 0, 0)
3293 if (GTK_IS_EVENT_BOX(m_pWindow))
3294 pGrabWidget = GTK_WIDGET(m_pWindow);
3295 else
3296 pGrabWidget = GTK_WIDGET(m_pFixedContainer);
3297 // m_nSetFocusSignalId is 0 for the DisallowCycleFocusOut case where
3298 // we don't allow focus to enter the toplevel, but expect it to
3299 // stay in some embedded native gtk widget
3300 if (!gtk_widget_get_can_focus(pGrabWidget) && m_nSetFocusSignalId)
3301 gtk_widget_set_can_focus(pGrabWidget, true);
3302 #else
3303 pGrabWidget = GTK_WIDGET(m_pFixedContainer);
3304 #endif
3305 if (!gtk_widget_has_focus(pGrabWidget))
3307 gtk_widget_grab_focus(pGrabWidget);
3308 if (m_pIMHandler)
3309 m_pIMHandler->focusChanged(true);
3313 bool GtkSalFrame::DrawingAreaButton(SalEvent nEventType, int nEventX, int nEventY, int nButton, guint32 nTime, guint nState)
3315 UpdateLastInputEventTime(nTime);
3317 SalMouseEvent aEvent;
3318 switch(nButton)
3320 case 1: aEvent.mnButton = MOUSE_LEFT; break;
3321 case 2: aEvent.mnButton = MOUSE_MIDDLE; break;
3322 case 3: aEvent.mnButton = MOUSE_RIGHT; break;
3323 default: return false;
3326 aEvent.mnTime = nTime;
3327 aEvent.mnX = nEventX;
3328 aEvent.mnY = nEventY;
3329 aEvent.mnCode = GetMouseModCode(nState);
3331 if( AllSettings::GetLayoutRTL() )
3332 aEvent.mnX = maGeometry.width()-1-aEvent.mnX;
3334 CallCallbackExc(nEventType, &aEvent);
3336 return true;
3339 #if !GTK_CHECK_VERSION(4, 0, 0)
3341 void GtkSalFrame::UpdateGeometryFromEvent(int x_root, int y_root, int nEventX, int nEventY)
3343 //tdf#151509 don't overwrite geometry for system children
3344 if (m_nStyle & SalFrameStyleFlags::SYSTEMCHILD)
3345 return;
3347 int frame_x = x_root - nEventX;
3348 int frame_y = y_root - nEventY;
3349 if (m_bGeometryIsProvisional || frame_x != maGeometry.x() || frame_y != maGeometry.y())
3351 m_bGeometryIsProvisional = false;
3352 maGeometry.setPos({ frame_x, frame_y });
3353 ImplSVData* pSVData = ImplGetSVData();
3354 if (pSVData->maNWFData.mbCanDetermineWindowPosition)
3355 CallCallbackExc(SalEvent::Move, nullptr);
3359 gboolean GtkSalFrame::signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer frame)
3361 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3362 GtkWidget* pEventWidget = pThis->getMouseEventWidget();
3363 bool bDifferentEventWindow = pEvent->window != widget_get_surface(pEventWidget);
3365 if (pEvent->type == GDK_BUTTON_PRESS)
3367 // tdf#120764 It isn't allowed under wayland to have two visible popups that share
3368 // the same top level parent. The problem is that since gtk 3.24 tooltips are also
3369 // implemented as popups, which means that we cannot show any popup if there is a
3370 // visible tooltip. In fact, gtk will hide the tooltip by itself after this handler,
3371 // in case of a button press event. But if we intend to show a popup on button press
3372 // it will be too late, so just do it here:
3373 pThis->HideTooltip();
3375 // focus on click
3376 if (!bDifferentEventWindow)
3377 pThis->GrabFocus();
3380 SalEvent nEventType = SalEvent::NONE;
3381 switch( pEvent->type )
3383 case GDK_BUTTON_PRESS:
3384 nEventType = SalEvent::MouseButtonDown;
3385 break;
3386 case GDK_BUTTON_RELEASE:
3387 nEventType = SalEvent::MouseButtonUp;
3388 break;
3389 default:
3390 return false;
3393 vcl::DeletionListener aDel( pThis );
3395 if (pThis->isFloatGrabWindow())
3397 //rhbz#1505379 if the window that got the event isn't our one, or there's none
3398 //of our windows under the mouse then close this popup window
3399 if (bDifferentEventWindow ||
3400 gdk_device_get_window_at_position(pEvent->device, nullptr, nullptr) == nullptr)
3402 if (pEvent->type == GDK_BUTTON_PRESS)
3403 pThis->closePopup();
3404 else if (pEvent->type == GDK_BUTTON_RELEASE)
3405 return true;
3409 int nEventX = pEvent->x;
3410 int nEventY = pEvent->y;
3412 if (bDifferentEventWindow)
3413 translate_coords(pEvent->window, pEventWidget, nEventX, nEventY);
3415 if (!aDel.isDeleted())
3416 pThis->UpdateGeometryFromEvent(pEvent->x_root, pEvent->y_root, nEventX, nEventY);
3418 bool bRet = false;
3419 if (!aDel.isDeleted())
3421 bRet = pThis->DrawingAreaButton(nEventType,
3422 nEventX,
3423 nEventY,
3424 pEvent->button,
3425 pEvent->time,
3426 pEvent->state);
3429 return bRet;
3431 #else
3432 void GtkSalFrame::gesturePressed(GtkGestureClick* pGesture, int /*n_press*/, gdouble x, gdouble y, gpointer frame)
3434 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3435 pThis->gestureButton(pGesture, SalEvent::MouseButtonDown, x, y);
3438 void GtkSalFrame::gestureReleased(GtkGestureClick* pGesture, int /*n_press*/, gdouble x, gdouble y, gpointer frame)
3440 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3441 pThis->gestureButton(pGesture, SalEvent::MouseButtonUp, x, y);
3444 void GtkSalFrame::gestureButton(GtkGestureClick* pGesture, SalEvent nEventType, gdouble x, gdouble y)
3446 GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pGesture));
3447 GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pGesture));
3448 int nButton = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(pGesture));
3449 DrawingAreaButton(nEventType, x, y, nButton, gdk_event_get_time(pEvent), eType);
3451 #endif
3453 #if !GTK_CHECK_VERSION(4, 0, 0)
3454 void GtkSalFrame::LaunchAsyncScroll(GdkEvent const * pEvent)
3456 //if we don't match previous pending states, flush that queue now
3457 if (!m_aPendingScrollEvents.empty() && pEvent->scroll.state != m_aPendingScrollEvents.back()->scroll.state)
3459 m_aSmoothScrollIdle.Stop();
3460 m_aSmoothScrollIdle.Invoke();
3461 assert(m_aPendingScrollEvents.empty());
3463 //add scroll event to queue
3464 m_aPendingScrollEvents.push_back(gdk_event_copy(pEvent));
3465 if (!m_aSmoothScrollIdle.IsActive())
3466 m_aSmoothScrollIdle.Start();
3468 #endif
3470 void GtkSalFrame::DrawingAreaScroll(double delta_x, double delta_y, int nEventX, int nEventY, guint32 nTime, guint nState)
3472 SalWheelMouseEvent aEvent;
3474 aEvent.mnTime = nTime;
3475 aEvent.mnX = nEventX;
3476 // --- RTL --- (mirror mouse pos)
3477 if (AllSettings::GetLayoutRTL())
3478 aEvent.mnX = maGeometry.width() - 1 - aEvent.mnX;
3479 aEvent.mnY = nEventY;
3480 aEvent.mnCode = GetMouseModCode(nState);
3482 // rhbz#1344042 "Traditionally" in gtk3 we took a single up/down event as
3483 // equating to 3 scroll lines and a delta of 120. So scale the delta here
3484 // by 120 where a single mouse wheel click is an incoming delta_x of 1
3485 // and divide that by 40 to get the number of scroll lines
3486 if (delta_x != 0.0)
3488 aEvent.mnDelta = -delta_x * 120;
3489 aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
3490 if (aEvent.mnDelta == 0)
3491 aEvent.mnDelta = aEvent.mnNotchDelta;
3492 aEvent.mbHorz = true;
3493 aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0;
3494 CallCallbackExc(SalEvent::WheelMouse, &aEvent);
3497 if (delta_y != 0.0)
3499 aEvent.mnDelta = -delta_y * 120;
3500 aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
3501 if (aEvent.mnDelta == 0)
3502 aEvent.mnDelta = aEvent.mnNotchDelta;
3503 aEvent.mbHorz = false;
3504 aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0;
3505 CallCallbackExc(SalEvent::WheelMouse, &aEvent);
3509 #if !GTK_CHECK_VERSION(4, 0, 0)
3510 IMPL_LINK_NOARG(GtkSalFrame, AsyncScroll, Timer *, void)
3512 assert(!m_aPendingScrollEvents.empty());
3514 GdkEvent* pEvent = m_aPendingScrollEvents.back();
3515 auto nEventX = pEvent->scroll.x;
3516 auto nEventY = pEvent->scroll.y;
3517 auto nTime = pEvent->scroll.time;
3518 auto nState = pEvent->scroll.state;
3520 double delta_x(0.0), delta_y(0.0);
3521 for (auto pSubEvent : m_aPendingScrollEvents)
3523 delta_x += pSubEvent->scroll.delta_x;
3524 delta_y += pSubEvent->scroll.delta_y;
3525 gdk_event_free(pSubEvent);
3527 m_aPendingScrollEvents.clear();
3529 DrawingAreaScroll(delta_x, delta_y, nEventX, nEventY, nTime, nState);
3531 #endif
3533 #if !GTK_CHECK_VERSION(4, 0, 0)
3534 SalWheelMouseEvent GtkSalFrame::GetWheelEvent(const GdkEventScroll& rEvent)
3536 SalWheelMouseEvent aEvent;
3538 aEvent.mnTime = rEvent.time;
3539 aEvent.mnX = static_cast<sal_uLong>(rEvent.x);
3540 aEvent.mnY = static_cast<sal_uLong>(rEvent.y);
3541 aEvent.mnCode = GetMouseModCode(rEvent.state);
3543 switch (rEvent.direction)
3545 case GDK_SCROLL_UP:
3546 aEvent.mnDelta = 120;
3547 aEvent.mnNotchDelta = 1;
3548 aEvent.mnScrollLines = 3;
3549 aEvent.mbHorz = false;
3550 break;
3552 case GDK_SCROLL_DOWN:
3553 aEvent.mnDelta = -120;
3554 aEvent.mnNotchDelta = -1;
3555 aEvent.mnScrollLines = 3;
3556 aEvent.mbHorz = false;
3557 break;
3559 case GDK_SCROLL_LEFT:
3560 aEvent.mnDelta = 120;
3561 aEvent.mnNotchDelta = 1;
3562 aEvent.mnScrollLines = 3;
3563 aEvent.mbHorz = true;
3564 break;
3566 case GDK_SCROLL_RIGHT:
3567 aEvent.mnDelta = -120;
3568 aEvent.mnNotchDelta = -1;
3569 aEvent.mnScrollLines = 3;
3570 aEvent.mbHorz = true;
3571 break;
3572 default:
3573 break;
3576 return aEvent;
3579 gboolean GtkSalFrame::signalScroll(GtkWidget*, GdkEvent* pInEvent, gpointer frame)
3581 GdkEventScroll& rEvent = pInEvent->scroll;
3583 UpdateLastInputEventTime(rEvent.time);
3585 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3587 if (rEvent.direction == GDK_SCROLL_SMOOTH)
3589 pThis->LaunchAsyncScroll(pInEvent);
3590 return true;
3593 //if we have smooth scrolling previous pending states, flush that queue now
3594 if (!pThis->m_aPendingScrollEvents.empty())
3596 pThis->m_aSmoothScrollIdle.Stop();
3597 pThis->m_aSmoothScrollIdle.Invoke();
3598 assert(pThis->m_aPendingScrollEvents.empty());
3601 SalWheelMouseEvent aEvent(GetWheelEvent(rEvent));
3603 // --- RTL --- (mirror mouse pos)
3604 if (AllSettings::GetLayoutRTL())
3605 aEvent.mnX = pThis->maGeometry.width() - 1 - aEvent.mnX;
3607 pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
3609 return true;
3611 #else
3612 gboolean GtkSalFrame::signalScroll(GtkEventControllerScroll* pController, double delta_x, double delta_y, gpointer frame)
3614 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3616 GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
3617 GdkModifierType eState = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
3619 auto nTime = gdk_event_get_time(pEvent);
3621 UpdateLastInputEventTime(nTime);
3623 double nEventX(0.0), nEventY(0.0);
3624 gdk_event_get_position(pEvent, &nEventX, &nEventY);
3626 pThis->DrawingAreaScroll(delta_x, delta_y, nEventX, nEventY, nTime, eState);
3628 return true;
3631 gboolean GtkSalFrame::event_controller_scroll_forward(GtkEventControllerScroll* pController, double delta_x, double delta_y)
3633 return GtkSalFrame::signalScroll(pController, delta_x, delta_y, this);
3636 #endif
3638 void GtkSalFrame::gestureSwipe(GtkGestureSwipe* gesture, gdouble velocity_x, gdouble velocity_y, gpointer frame)
3640 gdouble x, y;
3641 GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
3642 //I feel I want the first point of the sequence, not the last point which
3643 //the docs say this gives, but for the moment assume we start and end
3644 //within the same vcl window
3645 if (gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y))
3647 SalGestureSwipeEvent aEvent;
3648 aEvent.mnVelocityX = velocity_x;
3649 aEvent.mnVelocityY = velocity_y;
3650 aEvent.mnX = x;
3651 aEvent.mnY = y;
3653 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3654 pThis->CallCallbackExc(SalEvent::GestureSwipe, &aEvent);
3658 void GtkSalFrame::gestureLongPress(GtkGestureLongPress* gesture, gdouble x, gdouble y, gpointer frame)
3660 GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
3661 if (gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y))
3663 SalGestureLongPressEvent aEvent;
3664 aEvent.mnX = x;
3665 aEvent.mnY = y;
3667 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3668 pThis->CallCallbackExc(SalEvent::GestureLongPress, &aEvent);
3672 void GtkSalFrame::DrawingAreaMotion(int nEventX, int nEventY, guint32 nTime, guint nState)
3674 UpdateLastInputEventTime(nTime);
3676 SalMouseEvent aEvent;
3677 aEvent.mnTime = nTime;
3678 aEvent.mnX = nEventX;
3679 aEvent.mnY = nEventY;
3680 aEvent.mnCode = GetMouseModCode(nState);
3681 aEvent.mnButton = 0;
3683 if( AllSettings::GetLayoutRTL() )
3684 aEvent.mnX = maGeometry.width() - 1 - aEvent.mnX;
3686 CallCallbackExc(SalEvent::MouseMove, &aEvent);
3689 #if GTK_CHECK_VERSION(4, 0, 0)
3690 void GtkSalFrame::signalMotion(GtkEventControllerMotion *pController, double x, double y, gpointer frame)
3692 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3693 GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
3694 GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
3695 pThis->DrawingAreaMotion(x, y, gdk_event_get_time(pEvent), eType);
3697 #else
3698 gboolean GtkSalFrame::signalMotion( GtkWidget*, GdkEventMotion* pEvent, gpointer frame )
3700 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3701 GtkWidget* pEventWidget = pThis->getMouseEventWidget();
3702 bool bDifferentEventWindow = pEvent->window != widget_get_surface(pEventWidget);
3704 //If a menu, e.g. font name dropdown, is open, then under wayland moving the
3705 //mouse in the top left corner of the toplevel window in a
3706 //0,0,float-width,float-height area generates motion events which are
3707 //delivered to the dropdown
3708 if (pThis->isFloatGrabWindow() && bDifferentEventWindow)
3709 return true;
3711 vcl::DeletionListener aDel( pThis );
3713 int nEventX = pEvent->x;
3714 int nEventY = pEvent->y;
3716 if (bDifferentEventWindow)
3717 translate_coords(pEvent->window, pEventWidget, nEventX, nEventY);
3719 pThis->UpdateGeometryFromEvent(pEvent->x_root, pEvent->y_root, nEventX, nEventY);
3721 if (!aDel.isDeleted())
3722 pThis->DrawingAreaMotion(nEventX, nEventY, pEvent->time, pEvent->state);
3724 if (!aDel.isDeleted())
3726 // ask for the next hint
3727 gint x, y;
3728 GdkModifierType mask;
3729 gdk_window_get_pointer( widget_get_surface(GTK_WIDGET(pThis->m_pWindow)), &x, &y, &mask );
3732 return true;
3734 #endif
3736 void GtkSalFrame::DrawingAreaCrossing(SalEvent nEventType, int nEventX, int nEventY, guint32 nTime, guint nState)
3738 UpdateLastInputEventTime(nTime);
3740 SalMouseEvent aEvent;
3741 aEvent.mnTime = nTime;
3742 aEvent.mnX = nEventX;
3743 aEvent.mnY = nEventY;
3744 aEvent.mnCode = GetMouseModCode(nState);
3745 aEvent.mnButton = 0;
3747 if (AllSettings::GetLayoutRTL())
3748 aEvent.mnX = maGeometry.width()-1-aEvent.mnX;
3750 CallCallbackExc(nEventType, &aEvent);
3753 #if GTK_CHECK_VERSION(4, 0, 0)
3754 void GtkSalFrame::signalEnter(GtkEventControllerMotion *pController, double x, double y, gpointer frame)
3756 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3757 GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
3758 GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
3759 pThis->DrawingAreaCrossing(SalEvent::MouseMove, x, y, pEvent ? gdk_event_get_time(pEvent) : GDK_CURRENT_TIME, eType);
3762 void GtkSalFrame::signalLeave(GtkEventControllerMotion *pController, gpointer frame)
3764 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3765 GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
3766 GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
3767 pThis->DrawingAreaCrossing(SalEvent::MouseLeave, -1, -1, pEvent ? gdk_event_get_time(pEvent) : GDK_CURRENT_TIME, eType);
3769 #else
3770 gboolean GtkSalFrame::signalCrossing( GtkWidget*, GdkEventCrossing* pEvent, gpointer frame )
3772 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3773 pThis->DrawingAreaCrossing((pEvent->type == GDK_ENTER_NOTIFY) ? SalEvent::MouseMove : SalEvent::MouseLeave,
3774 pEvent->x,
3775 pEvent->y,
3776 pEvent->time,
3777 pEvent->state);
3778 return true;
3780 #endif
3782 cairo_t* GtkSalFrame::getCairoContext() const
3784 cairo_t* cr = cairo_create(m_pSurface);
3785 assert(cr);
3786 return cr;
3789 void GtkSalFrame::damaged(sal_Int32 nExtentsX, sal_Int32 nExtentsY,
3790 sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight) const
3792 #if OSL_DEBUG_LEVEL > 0
3793 if (dumpframes)
3795 static int frame;
3796 OString tmp("/tmp/frame" + OString::number(frame++) + ".png");
3797 cairo_t* cr = getCairoContext();
3798 cairo_surface_write_to_png(cairo_get_target(cr), tmp.getStr());
3799 cairo_destroy(cr);
3801 #endif
3803 // quite a bit of noise in RTL mode with negative widths
3804 if (nExtentsWidth <= 0 || nExtentsHeight <= 0)
3805 return;
3807 #if !GTK_CHECK_VERSION(4, 0, 0)
3808 gtk_widget_queue_draw_area(GTK_WIDGET(m_pDrawingArea),
3809 nExtentsX, nExtentsY,
3810 nExtentsWidth, nExtentsHeight);
3811 #else
3812 gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
3813 (void)nExtentsX;
3814 (void)nExtentsY;
3815 #endif
3818 // blit our backing cairo surface to the target cairo context
3819 void GtkSalFrame::DrawingAreaDraw(cairo_t *cr)
3821 cairo_set_source_surface(cr, m_pSurface, 0, 0);
3822 cairo_paint(cr);
3825 #if !GTK_CHECK_VERSION(4, 0, 0)
3826 gboolean GtkSalFrame::signalDraw(GtkWidget*, cairo_t *cr, gpointer frame)
3828 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3829 pThis->DrawingAreaDraw(cr);
3830 return false;
3832 #else
3833 void GtkSalFrame::signalDraw(GtkDrawingArea*, cairo_t *cr, int /*width*/, int /*height*/, gpointer frame)
3835 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3836 pThis->DrawingAreaDraw(cr);
3838 #endif
3840 void GtkSalFrame::DrawingAreaResized(GtkWidget* pWidget, int nWidth, int nHeight)
3842 // ignore size-allocations that occur during configuring an embedded SalObject
3843 if (m_bSalObjectSetPosSize)
3844 return;
3845 maGeometry.setSize({ nWidth, nHeight });
3846 bool bRealized = gtk_widget_get_realized(pWidget);
3847 if (bRealized)
3848 AllocateFrame();
3849 CallCallbackExc( SalEvent::Resize, nullptr );
3850 if (bRealized)
3851 TriggerPaintEvent();
3854 #if !GTK_CHECK_VERSION(4, 0, 0)
3855 void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, GdkRectangle *pAllocation, gpointer frame)
3857 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3858 pThis->DrawingAreaResized(pWidget, pAllocation->width, pAllocation->height);
3860 #else
3861 void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, int nWidth, int nHeight, gpointer frame)
3863 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3864 pThis->DrawingAreaResized(pWidget, nWidth, nHeight);
3866 #endif
3868 #if !GTK_CHECK_VERSION(4, 0, 0)
3869 namespace {
3871 void swapDirection(GdkGravity& gravity)
3873 if (gravity == GDK_GRAVITY_NORTH_WEST)
3874 gravity = GDK_GRAVITY_NORTH_EAST;
3875 else if (gravity == GDK_GRAVITY_NORTH_EAST)
3876 gravity = GDK_GRAVITY_NORTH_WEST;
3877 else if (gravity == GDK_GRAVITY_SOUTH_WEST)
3878 gravity = GDK_GRAVITY_SOUTH_EAST;
3879 else if (gravity == GDK_GRAVITY_SOUTH_EAST)
3880 gravity = GDK_GRAVITY_SOUTH_WEST;
3884 #endif
3886 void GtkSalFrame::signalRealize(GtkWidget*, gpointer frame)
3888 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3889 pThis->AllocateFrame();
3890 if (pThis->m_bSalObjectSetPosSize)
3891 return;
3892 pThis->TriggerPaintEvent();
3894 #if !GTK_CHECK_VERSION(4, 0, 0)
3895 if (!pThis->m_bFloatPositioned)
3896 return;
3898 static auto window_move_to_rect = reinterpret_cast<void (*) (GdkWindow*, const GdkRectangle*, GdkGravity,
3899 GdkGravity, GdkAnchorHints, gint, gint)>(
3900 dlsym(nullptr, "gdk_window_move_to_rect"));
3901 if (!window_move_to_rect)
3902 return;
3904 GdkGravity rect_anchor = GDK_GRAVITY_SOUTH_WEST, menu_anchor = GDK_GRAVITY_NORTH_WEST;
3906 if (pThis->m_nFloatFlags & FloatWinPopupFlags::Left)
3908 rect_anchor = GDK_GRAVITY_NORTH_WEST;
3909 menu_anchor = GDK_GRAVITY_NORTH_EAST;
3911 else if (pThis->m_nFloatFlags & FloatWinPopupFlags::Up)
3913 rect_anchor = GDK_GRAVITY_NORTH_WEST;
3914 menu_anchor = GDK_GRAVITY_SOUTH_WEST;
3916 else if (pThis->m_nFloatFlags & FloatWinPopupFlags::Right)
3918 rect_anchor = GDK_GRAVITY_NORTH_EAST;
3921 VclPtr<vcl::Window> pVclParent = pThis->GetWindow()->GetParent();
3922 if (pVclParent->GetOutDev()->HasMirroredGraphics() && pVclParent->IsRTLEnabled())
3924 swapDirection(rect_anchor);
3925 swapDirection(menu_anchor);
3928 tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pVclParent, pThis->m_aFloatRect);
3929 if (gdk_window_get_window_type(widget_get_surface(pThis->m_pParent->m_pWindow)) != GDK_WINDOW_TOPLEVEL)
3931 // See tdf#152155 for an example
3932 gtk_coord nX(0), nY(0.0);
3933 gtk_widget_translate_coordinates(pThis->m_pParent->m_pWindow, widget_get_toplevel(pThis->m_pParent->m_pWindow), 0, 0, &nX, &nY);
3934 aFloatRect.Move(nX, nY);
3937 GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
3938 static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
3940 GdkSurface* gdkWindow = widget_get_surface(pThis->m_pWindow);
3941 window_move_to_rect(gdkWindow, &rect, rect_anchor, menu_anchor, static_cast<GdkAnchorHints>(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE), 0, 0);
3942 #endif
3945 #if !GTK_CHECK_VERSION(4, 0, 0)
3946 gboolean GtkSalFrame::signalConfigure(GtkWidget*, GdkEventConfigure* pEvent, gpointer frame)
3948 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3950 bool bMoved = false;
3951 int x = pEvent->x, y = pEvent->y;
3953 /* #i31785# claims we cannot trust the x,y members of the event;
3954 * they are e.g. not set correctly on maximize/demaximize;
3955 * yet the gdkdisplay-x11.c code handling configure_events has
3956 * done this XTranslateCoordinates work since the day ~zero.
3958 if (pThis->m_bGeometryIsProvisional || x != pThis->maGeometry.x() || y != pThis->maGeometry.y() )
3960 bMoved = true;
3961 pThis->m_bGeometryIsProvisional = false;
3962 pThis->maGeometry.setPos({ x, y });
3965 // update decoration hints
3966 GdkRectangle aRect;
3967 gdk_window_get_frame_extents( widget_get_surface(GTK_WIDGET(pThis->m_pWindow)), &aRect );
3968 pThis->maGeometry.setTopDecoration(y - aRect.y);
3969 pThis->maGeometry.setBottomDecoration(aRect.y + aRect.height - y - pEvent->height);
3970 pThis->maGeometry.setLeftDecoration(x - aRect.x);
3971 pThis->maGeometry.setRightDecoration(aRect.x + aRect.width - x - pEvent->width);
3972 pThis->updateScreenNumber();
3974 if (bMoved)
3976 ImplSVData* pSVData = ImplGetSVData();
3977 if (pSVData->maNWFData.mbCanDetermineWindowPosition)
3978 pThis->CallCallbackExc(SalEvent::Move, nullptr);
3981 return false;
3983 #endif
3985 void GtkSalFrame::queue_draw()
3987 gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
3990 void GtkSalFrame::TriggerPaintEvent()
3992 //Under gtk2 we can basically paint directly into the XWindow and on
3993 //additional "expose-event" events we can re-render the missing pieces
3995 //Under gtk3 we have to keep our own buffer up to date and flush it into
3996 //the given cairo context on "draw". So we emit a paint event on
3997 //opportune resize trigger events to initially fill our backbuffer and then
3998 //keep it up to date with our direct paints and tell gtk those regions
3999 //have changed and then blit them into the provided cairo context when
4000 //we get the "draw"
4002 //The other alternative was to always paint everything on "draw", but
4003 //that duplicates the amount of drawing and is hideously slow
4004 SAL_INFO("vcl.gtk3", "force painting" << 0 << "," << 0 << " " << maGeometry.width() << "x" << maGeometry.height());
4005 SalPaintEvent aPaintEvt(0, 0, maGeometry.width(), maGeometry.height(), true);
4006 CallCallbackExc(SalEvent::Paint, &aPaintEvt);
4007 queue_draw();
4010 void GtkSalFrame::DrawingAreaFocusInOut(SalEvent nEventType)
4012 SalGenericInstance* pSalInstance = GetGenericInstance();
4014 // check if printers have changed (analogous to salframe focus handler)
4015 pSalInstance->updatePrinterUpdate();
4017 if (nEventType == SalEvent::LoseFocus)
4018 m_nKeyModifiers = ModKeyFlags::NONE;
4020 if (m_pIMHandler)
4022 bool bFocusInAnotherGtkWidget = false;
4023 if (GTK_IS_WINDOW(m_pWindow))
4025 GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(m_pWindow));
4026 bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(m_pFixedContainer);
4028 if (!bFocusInAnotherGtkWidget)
4029 m_pIMHandler->focusChanged(nEventType == SalEvent::GetFocus);
4032 // ask for changed printers like generic implementation
4033 if (nEventType == SalEvent::GetFocus && pSalInstance->isPrinterInit())
4034 pSalInstance->updatePrinterUpdate();
4036 CallCallbackExc(nEventType, nullptr);
4039 #if !GTK_CHECK_VERSION(4, 0, 0)
4040 gboolean GtkSalFrame::signalFocus( GtkWidget*, GdkEventFocus* pEvent, gpointer frame )
4042 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4044 SalGenericInstance *pSalInstance = GetGenericInstance();
4046 // check if printers have changed (analogous to salframe focus handler)
4047 pSalInstance->updatePrinterUpdate();
4049 if( !pEvent->in )
4050 pThis->m_nKeyModifiers = ModKeyFlags::NONE;
4052 if( pThis->m_pIMHandler )
4054 bool bFocusInAnotherGtkWidget = false;
4055 if (GTK_IS_WINDOW(pThis->m_pWindow))
4057 GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
4058 bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(pThis->m_pFixedContainer);
4060 if (!bFocusInAnotherGtkWidget)
4061 pThis->m_pIMHandler->focusChanged( pEvent->in != 0 );
4064 // ask for changed printers like generic implementation
4065 if( pEvent->in && pSalInstance->isPrinterInit() )
4066 pSalInstance->updatePrinterUpdate();
4068 // FIXME: find out who the hell steals the focus from our frame
4069 // while we have the pointer grabbed, this should not come from
4070 // the window manager. Is this an event that was still queued ?
4071 // The focus does not seem to get set inside our process
4072 // in the meantime do not propagate focus get/lose if floats are open
4073 if( m_nFloats == 0 )
4075 GtkWidget* pGrabWidget;
4076 if (GTK_IS_EVENT_BOX(pThis->m_pWindow))
4077 pGrabWidget = GTK_WIDGET(pThis->m_pWindow);
4078 else
4079 pGrabWidget = GTK_WIDGET(pThis->m_pFixedContainer);
4080 bool bHasFocus = gtk_widget_has_focus(pGrabWidget);
4081 pThis->CallCallbackExc(bHasFocus ? SalEvent::GetFocus : SalEvent::LoseFocus, nullptr);
4084 return false;
4086 #else
4087 void GtkSalFrame::signalFocusEnter(GtkEventControllerFocus*, gpointer frame)
4089 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4090 pThis->DrawingAreaFocusInOut(SalEvent::GetFocus);
4093 void GtkSalFrame::signalFocusLeave(GtkEventControllerFocus*, gpointer frame)
4095 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4096 pThis->DrawingAreaFocusInOut(SalEvent::LoseFocus);
4098 #endif
4100 // change of focus between native widgets within the toplevel
4101 #if !GTK_CHECK_VERSION(4, 0, 0)
4102 void GtkSalFrame::signalSetFocus(GtkWindow*, GtkWidget* pWidget, gpointer frame)
4103 #else
4104 void GtkSalFrame::signalSetFocus(GtkWindow*, GParamSpec*, gpointer frame)
4105 #endif
4107 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4109 GtkWidget* pGrabWidget = GTK_WIDGET(pThis->m_pFixedContainer);
4111 GtkWidget* pTopLevel = widget_get_toplevel(pGrabWidget);
4112 // see commentary in GtkSalObjectWidgetClip::Show
4113 if (pTopLevel && g_object_get_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange"))
4114 return;
4116 #if GTK_CHECK_VERSION(4, 0, 0)
4117 GtkWidget* pWidget = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
4118 #endif
4120 // tdf#129634 interpret losing focus as focus passing explicitly to another widget
4121 bool bLoseFocus = pWidget && pWidget != pGrabWidget;
4123 // do not propagate focus get/lose if floats are open
4124 pThis->CallCallbackExc(bLoseFocus ? SalEvent::LoseFocus : SalEvent::GetFocus, nullptr);
4126 #if !GTK_CHECK_VERSION(4, 0, 0)
4127 gtk_widget_set_can_focus(GTK_WIDGET(pThis->m_pFixedContainer), !bLoseFocus);
4128 #endif
4131 void GtkSalFrame::WindowMap()
4133 if (m_bIconSetWhileUnmapped)
4134 SetIcon(gtk_window_get_icon_name(GTK_WINDOW(m_pWindow)));
4136 CallCallbackExc( SalEvent::Resize, nullptr );
4137 TriggerPaintEvent();
4140 void GtkSalFrame::WindowUnmap()
4142 CallCallbackExc( SalEvent::Resize, nullptr );
4144 if (m_bFloatPositioned)
4146 // Unrealize is needed for cases where we reuse the same popup
4147 // (e.g. the font name control), making the realize signal fire
4148 // again on next show.
4149 gtk_widget_unrealize(m_pWindow);
4150 m_bFloatPositioned = false;
4154 #if GTK_CHECK_VERSION(4, 0, 0)
4155 void GtkSalFrame::signalMap(GtkWidget*, gpointer frame)
4157 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4158 pThis->WindowMap();
4161 void GtkSalFrame::signalUnmap(GtkWidget*, gpointer frame)
4163 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4164 pThis->WindowUnmap();
4166 #else
4167 gboolean GtkSalFrame::signalMap(GtkWidget*, GdkEvent*, gpointer frame)
4169 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4170 pThis->WindowMap();
4171 return false;
4174 gboolean GtkSalFrame::signalUnmap(GtkWidget*, GdkEvent*, gpointer frame)
4176 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4177 pThis->WindowUnmap();
4178 return false;
4180 #endif
4182 #if !GTK_CHECK_VERSION(4, 0, 0)
4184 static bool key_forward(GdkEventKey* pEvent, GtkWindow* pDest)
4186 gpointer pClass = g_type_class_ref(GTK_TYPE_WINDOW);
4187 GtkWidgetClass* pWindowClass = GTK_WIDGET_CLASS(pClass);
4188 bool bHandled = pEvent->type == GDK_KEY_PRESS
4189 ? pWindowClass->key_press_event(GTK_WIDGET(pDest), pEvent)
4190 : pWindowClass->key_release_event(GTK_WIDGET(pDest), pEvent);
4191 g_type_class_unref(pClass);
4192 return bHandled;
4195 static bool activate_menubar_mnemonic(GtkWidget* pWidget, guint nKeyval)
4197 const char* pLabel = gtk_menu_item_get_label(GTK_MENU_ITEM(pWidget));
4198 gunichar cAccelChar = 0;
4199 if (!pango_parse_markup(pLabel, -1, '_', nullptr, nullptr, &cAccelChar, nullptr))
4200 return false;
4201 if (!cAccelChar)
4202 return false;
4203 auto nMnemonicKeyval = gdk_keyval_to_lower(gdk_unicode_to_keyval(cAccelChar));
4204 if (nKeyval == nMnemonicKeyval)
4205 return gtk_widget_mnemonic_activate(pWidget, false);
4206 return false;
4209 bool GtkSalFrame::HandleMenubarMnemonic(guint eState, guint nKeyval)
4211 bool bUsedInMenuBar = false;
4212 if (eState & GDK_ALT_MASK)
4214 if (GtkWidget* pMenuBar = m_pSalMenu ? m_pSalMenu->GetMenuBarWidget() : nullptr)
4216 GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pMenuBar));
4217 for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
4219 bUsedInMenuBar = activate_menubar_mnemonic(static_cast<GtkWidget*>(pChild->data), nKeyval);
4220 if (bUsedInMenuBar)
4221 break;
4223 g_list_free(pChildren);
4226 return bUsedInMenuBar;
4229 gboolean GtkSalFrame::signalKey(GtkWidget* pWidget, GdkEventKey* pEvent, gpointer frame)
4231 UpdateLastInputEventTime(pEvent->time);
4233 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4235 bool bFocusInAnotherGtkWidget = false;
4237 VclPtr<vcl::Window> xTopLevelInterimWindow;
4239 if (GTK_IS_WINDOW(pThis->m_pWindow))
4241 GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
4242 bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(pThis->m_pFixedContainer);
4243 if (bFocusInAnotherGtkWidget)
4245 if (!gtk_widget_get_realized(pFocusWindow))
4246 return true;
4248 // if the focus is not in our main widget, see if there is a handler
4249 // for this key stroke in GtkWindow first
4250 if (key_forward(pEvent, GTK_WINDOW(pThis->m_pWindow)))
4251 return true;
4253 // Is focus inside an InterimItemWindow? In which case find that
4254 // InterimItemWindow and send unconsumed keystrokes to it to
4255 // support ctrl-q etc shortcuts. Only bother to search for the
4256 // InterimItemWindow if it is a toplevel that fills its frame, or
4257 // the keystroke is sufficiently special its worth passing on,
4258 // e.g. F6 to switch between task-panels or F5 to close a navigator
4259 if (pThis->IsCycleFocusOutDisallowed() || IsFunctionKeyVal(pEvent->keyval))
4261 GtkWidget* pSearch = pFocusWindow;
4262 while (pSearch)
4264 void* pData = g_object_get_data(G_OBJECT(pSearch), "InterimWindowGlue");
4265 if (pData)
4267 xTopLevelInterimWindow = static_cast<vcl::Window*>(pData);
4268 break;
4270 pSearch = gtk_widget_get_parent(pSearch);
4276 if (pThis->isFloatGrabWindow())
4277 return signalKey(pWidget, pEvent, pThis->m_pParent);
4279 vcl::DeletionListener aDel( pThis );
4281 if (!bFocusInAnotherGtkWidget && pThis->m_pIMHandler && pThis->m_pIMHandler->handleKeyEvent(pEvent))
4282 return true;
4284 bool bStopProcessingKey = false;
4286 // handle modifiers
4287 if( pEvent->keyval == GDK_KEY_Shift_L || pEvent->keyval == GDK_KEY_Shift_R ||
4288 pEvent->keyval == GDK_KEY_Control_L || pEvent->keyval == GDK_KEY_Control_R ||
4289 pEvent->keyval == GDK_KEY_Alt_L || pEvent->keyval == GDK_KEY_Alt_R ||
4290 pEvent->keyval == GDK_KEY_Meta_L || pEvent->keyval == GDK_KEY_Meta_R ||
4291 pEvent->keyval == GDK_KEY_Super_L || pEvent->keyval == GDK_KEY_Super_R )
4293 sal_uInt16 nModCode = GetKeyModCode( pEvent->state );
4294 ModKeyFlags nExtModMask = ModKeyFlags::NONE;
4295 sal_uInt16 nModMask = 0;
4296 // pressing just the ctrl key leads to a keysym of XK_Control but
4297 // the event state does not contain ControlMask. In the release
4298 // event it's the other way round: it does contain the Control mask.
4299 // The modifier mode therefore has to be adapted manually.
4300 switch( pEvent->keyval )
4302 case GDK_KEY_Control_L:
4303 nExtModMask = ModKeyFlags::LeftMod1;
4304 nModMask = KEY_MOD1;
4305 break;
4306 case GDK_KEY_Control_R:
4307 nExtModMask = ModKeyFlags::RightMod1;
4308 nModMask = KEY_MOD1;
4309 break;
4310 case GDK_KEY_Alt_L:
4311 nExtModMask = ModKeyFlags::LeftMod2;
4312 nModMask = KEY_MOD2;
4313 break;
4314 case GDK_KEY_Alt_R:
4315 nExtModMask = ModKeyFlags::RightMod2;
4316 nModMask = KEY_MOD2;
4317 break;
4318 case GDK_KEY_Shift_L:
4319 nExtModMask = ModKeyFlags::LeftShift;
4320 nModMask = KEY_SHIFT;
4321 break;
4322 case GDK_KEY_Shift_R:
4323 nExtModMask = ModKeyFlags::RightShift;
4324 nModMask = KEY_SHIFT;
4325 break;
4326 // Map Meta/Super to MOD3 modifier on all Unix systems
4327 // except macOS
4328 case GDK_KEY_Meta_L:
4329 case GDK_KEY_Super_L:
4330 nExtModMask = ModKeyFlags::LeftMod3;
4331 nModMask = KEY_MOD3;
4332 break;
4333 case GDK_KEY_Meta_R:
4334 case GDK_KEY_Super_R:
4335 nExtModMask = ModKeyFlags::RightMod3;
4336 nModMask = KEY_MOD3;
4337 break;
4340 SalKeyModEvent aModEvt;
4341 aModEvt.mbDown = pEvent->type == GDK_KEY_PRESS;
4343 if( pEvent->type == GDK_KEY_RELEASE )
4345 aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
4346 aModEvt.mnCode = nModCode & ~nModMask;
4347 pThis->m_nKeyModifiers &= ~nExtModMask;
4349 else
4351 aModEvt.mnCode = nModCode | nModMask;
4352 pThis->m_nKeyModifiers |= nExtModMask;
4353 aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
4356 pThis->CallCallbackExc( SalEvent::KeyModChange, &aModEvt );
4358 else
4360 bool bRestoreDisallowCycleFocusOut = false;
4362 VclPtr<vcl::Window> xOrigFrameFocusWin;
4363 VclPtr<vcl::Window> xOrigFocusWin;
4364 if (xTopLevelInterimWindow)
4366 // Focus is inside an InterimItemWindow so send unconsumed
4367 // keystrokes to by setting it as the mpFocusWin
4368 VclPtr<vcl::Window> xVclWindow = pThis->GetWindow();
4369 ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
4370 xOrigFrameFocusWin = pFrameData->mpFocusWin;
4371 pFrameData->mpFocusWin = xTopLevelInterimWindow;
4373 ImplSVData* pSVData = ImplGetSVData();
4374 xOrigFocusWin = pSVData->mpWinData->mpFocusWin;
4375 pSVData->mpWinData->mpFocusWin = xTopLevelInterimWindow;
4377 if (pEvent->keyval == GDK_KEY_F6 && pThis->IsCycleFocusOutDisallowed())
4379 // For F6, allow the focus to leave the InterimItemWindow
4380 pThis->AllowCycleFocusOut();
4381 bRestoreDisallowCycleFocusOut = true;
4385 bStopProcessingKey = pThis->doKeyCallback(pEvent->state,
4386 pEvent->keyval,
4387 pEvent->hardware_keycode,
4388 pEvent->group,
4389 sal_Unicode(gdk_keyval_to_unicode( pEvent->keyval )),
4390 (pEvent->type == GDK_KEY_PRESS),
4391 false);
4393 // tdf#144846 If this is registered as a menubar mnemonic then ensure
4394 // that any other widget won't be considered as a candidate by taking
4395 // over the task of launch the menubar menu outself
4396 // The code was moved here from its original position at beginning
4397 // of this function in order to resolve tdf#146174.
4398 if (!bStopProcessingKey && // module key handler did not process key
4399 pEvent->type == GDK_KEY_PRESS && // module key handler handles only GDK_KEY_PRESS
4400 GTK_IS_WINDOW(pThis->m_pWindow) &&
4401 pThis->HandleMenubarMnemonic(pEvent->state, pEvent->keyval))
4403 return true;
4406 if (!aDel.isDeleted())
4408 pThis->m_nKeyModifiers = ModKeyFlags::NONE;
4410 if (xTopLevelInterimWindow)
4412 // Focus was inside an InterimItemWindow, restore the original
4413 // focus win, unless the focus was changed away from the
4414 // InterimItemWindow which should only be possible with F6
4415 VclPtr<vcl::Window> xVclWindow = pThis->GetWindow();
4416 ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
4417 if (pFrameData->mpFocusWin == xTopLevelInterimWindow)
4418 pFrameData->mpFocusWin = xOrigFrameFocusWin;
4420 ImplSVData* pSVData = ImplGetSVData();
4421 if (pSVData->mpWinData->mpFocusWin == xTopLevelInterimWindow)
4422 pSVData->mpWinData->mpFocusWin = xOrigFocusWin;
4424 if (bRestoreDisallowCycleFocusOut)
4426 // undo the above AllowCycleFocusOut for F6
4427 pThis->DisallowCycleFocusOut();
4434 if (!bFocusInAnotherGtkWidget && !aDel.isDeleted() && pThis->m_pIMHandler)
4435 pThis->m_pIMHandler->updateIMSpotLocation();
4437 return bStopProcessingKey;
4439 #else
4441 bool GtkSalFrame::DrawingAreaKey(GtkEventControllerKey* pController, SalEvent nEventType, guint keyval, guint keycode, guint state)
4443 guint32 nTime = gdk_event_get_time(gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController)));
4444 UpdateLastInputEventTime(nTime);
4446 bool bFocusInAnotherGtkWidget = false;
4448 VclPtr<vcl::Window> xTopLevelInterimWindow;
4450 if (GTK_IS_WINDOW(m_pWindow))
4452 GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(m_pWindow));
4453 bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(m_pFixedContainer);
4454 if (bFocusInAnotherGtkWidget)
4456 if (!gtk_widget_get_realized(pFocusWindow))
4457 return true;
4458 // if the focus is not in our main widget, see if there is a handler
4459 // for this key stroke in GtkWindow first
4460 bool bHandled = gtk_event_controller_key_forward(pController, m_pWindow);
4461 if (bHandled)
4462 return true;
4464 // Is focus inside an InterimItemWindow? In which case find that
4465 // InterimItemWindow and send unconsumed keystrokes to it to
4466 // support ctrl-q etc shortcuts. Only bother to search for the
4467 // InterimItemWindow if it is a toplevel that fills its frame, or
4468 // the keystroke is sufficiently special its worth passing on,
4469 // e.g. F6 to switch between task-panels or F5 to close a navigator
4470 if (IsCycleFocusOutDisallowed() || IsFunctionKeyVal(keyval))
4472 GtkWidget* pSearch = pFocusWindow;
4473 while (pSearch)
4475 void* pData = g_object_get_data(G_OBJECT(pSearch), "InterimWindowGlue");
4476 if (pData)
4478 xTopLevelInterimWindow = static_cast<vcl::Window*>(pData);
4479 break;
4481 pSearch = gtk_widget_get_parent(pSearch);
4487 vcl::DeletionListener aDel(this);
4489 bool bStopProcessingKey = false;
4491 // handle modifiers
4492 if( keyval == GDK_KEY_Shift_L || keyval == GDK_KEY_Shift_R ||
4493 keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R ||
4494 keyval == GDK_KEY_Alt_L || keyval == GDK_KEY_Alt_R ||
4495 keyval == GDK_KEY_Meta_L || keyval == GDK_KEY_Meta_R ||
4496 keyval == GDK_KEY_Super_L || keyval == GDK_KEY_Super_R )
4498 sal_uInt16 nModCode = GetKeyModCode(state);
4499 ModKeyFlags nExtModMask = ModKeyFlags::NONE;
4500 sal_uInt16 nModMask = 0;
4501 // pressing just the ctrl key leads to a keysym of XK_Control but
4502 // the event state does not contain ControlMask. In the release
4503 // event it's the other way round: it does contain the Control mask.
4504 // The modifier mode therefore has to be adapted manually.
4505 switch (keyval)
4507 case GDK_KEY_Control_L:
4508 nExtModMask = ModKeyFlags::LeftMod1;
4509 nModMask = KEY_MOD1;
4510 break;
4511 case GDK_KEY_Control_R:
4512 nExtModMask = ModKeyFlags::RightMod1;
4513 nModMask = KEY_MOD1;
4514 break;
4515 case GDK_KEY_Alt_L:
4516 nExtModMask = ModKeyFlags::LeftMod2;
4517 nModMask = KEY_MOD2;
4518 break;
4519 case GDK_KEY_Alt_R:
4520 nExtModMask = ModKeyFlags::RightMod2;
4521 nModMask = KEY_MOD2;
4522 break;
4523 case GDK_KEY_Shift_L:
4524 nExtModMask = ModKeyFlags::LeftShift;
4525 nModMask = KEY_SHIFT;
4526 break;
4527 case GDK_KEY_Shift_R:
4528 nExtModMask = ModKeyFlags::RightShift;
4529 nModMask = KEY_SHIFT;
4530 break;
4531 // Map Meta/Super to MOD3 modifier on all Unix systems
4532 // except macOS
4533 case GDK_KEY_Meta_L:
4534 case GDK_KEY_Super_L:
4535 nExtModMask = ModKeyFlags::LeftMod3;
4536 nModMask = KEY_MOD3;
4537 break;
4538 case GDK_KEY_Meta_R:
4539 case GDK_KEY_Super_R:
4540 nExtModMask = ModKeyFlags::RightMod3;
4541 nModMask = KEY_MOD3;
4542 break;
4545 SalKeyModEvent aModEvt;
4546 aModEvt.mbDown = nEventType == SalEvent::KeyInput;
4548 if (!aModEvt.mbDown)
4550 aModEvt.mnModKeyCode = m_nKeyModifiers;
4551 aModEvt.mnCode = nModCode & ~nModMask;
4552 m_nKeyModifiers &= ~nExtModMask;
4554 else
4556 aModEvt.mnCode = nModCode | nModMask;
4557 m_nKeyModifiers |= nExtModMask;
4558 aModEvt.mnModKeyCode = m_nKeyModifiers;
4561 CallCallbackExc(SalEvent::KeyModChange, &aModEvt);
4563 else
4565 bool bRestoreDisallowCycleFocusOut = false;
4567 VclPtr<vcl::Window> xOrigFrameFocusWin;
4568 VclPtr<vcl::Window> xOrigFocusWin;
4569 if (xTopLevelInterimWindow)
4571 // Focus is inside an InterimItemWindow so send unconsumed
4572 // keystrokes to by setting it as the mpFocusWin
4573 VclPtr<vcl::Window> xVclWindow = GetWindow();
4574 ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
4575 xOrigFrameFocusWin = pFrameData->mpFocusWin;
4576 pFrameData->mpFocusWin = xTopLevelInterimWindow;
4578 ImplSVData* pSVData = ImplGetSVData();
4579 xOrigFocusWin = pSVData->mpWinData->mpFocusWin;
4580 pSVData->mpWinData->mpFocusWin = xTopLevelInterimWindow;
4582 if (keyval == GDK_KEY_F6 && IsCycleFocusOutDisallowed())
4584 // For F6, allow the focus to leave the InterimItemWindow
4585 AllowCycleFocusOut();
4586 bRestoreDisallowCycleFocusOut = true;
4591 bStopProcessingKey = doKeyCallback(state,
4592 keyval,
4593 keycode,
4594 0, // group
4595 sal_Unicode(gdk_keyval_to_unicode(keyval)),
4596 nEventType == SalEvent::KeyInput,
4597 false);
4599 if (!aDel.isDeleted())
4601 m_nKeyModifiers = ModKeyFlags::NONE;
4603 if (xTopLevelInterimWindow)
4605 // Focus was inside an InterimItemWindow, restore the original
4606 // focus win, unless the focus was changed away from the
4607 // InterimItemWindow which should only be possible with F6
4608 VclPtr<vcl::Window> xVclWindow = GetWindow();
4609 ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
4610 if (pFrameData->mpFocusWin == xTopLevelInterimWindow)
4611 pFrameData->mpFocusWin = xOrigFrameFocusWin;
4613 ImplSVData* pSVData = ImplGetSVData();
4614 if (pSVData->mpWinData->mpFocusWin == xTopLevelInterimWindow)
4615 pSVData->mpWinData->mpFocusWin = xOrigFocusWin;
4617 if (bRestoreDisallowCycleFocusOut)
4619 // undo the above AllowCycleFocusOut for F6
4620 DisallowCycleFocusOut();
4626 if (m_pIMHandler)
4627 m_pIMHandler->updateIMSpotLocation();
4629 return bStopProcessingKey;
4632 gboolean GtkSalFrame::signalKeyPressed(GtkEventControllerKey* pController, guint keyval, guint keycode, GdkModifierType state, gpointer frame)
4634 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4635 return pThis->DrawingAreaKey(pController, SalEvent::KeyInput, keyval, keycode, state);
4638 gboolean GtkSalFrame::signalKeyReleased(GtkEventControllerKey* pController, guint keyval, guint keycode, GdkModifierType state, gpointer frame)
4640 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4641 return pThis->DrawingAreaKey(pController, SalEvent::KeyUp, keyval, keycode, state);
4643 #endif
4645 bool GtkSalFrame::WindowCloseRequest()
4647 CallCallbackExc(SalEvent::Close, nullptr);
4648 return true;
4651 #if GTK_CHECK_VERSION(4, 0, 0)
4652 gboolean GtkSalFrame::signalDelete(GtkWidget*, gpointer frame)
4654 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4655 return pThis->WindowCloseRequest();
4657 #else
4658 gboolean GtkSalFrame::signalDelete(GtkWidget*, GdkEvent*, gpointer frame)
4660 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4661 return pThis->WindowCloseRequest();
4663 #endif
4665 const cairo_font_options_t* GtkSalFrame::get_font_options()
4667 GtkWidget* pWidget = getMouseEventWidget();
4668 #if GTK_CHECK_VERSION(4, 0, 0)
4669 PangoContext* pContext = gtk_widget_get_pango_context(pWidget);
4670 assert(pContext);
4671 return pango_cairo_context_get_font_options(pContext);
4672 #else
4673 return gdk_screen_get_font_options(gtk_widget_get_screen(pWidget));
4674 #endif
4677 #if GTK_CHECK_VERSION(4, 0, 0)
4678 void GtkSalFrame::signalStyleUpdated(GtkWidget*, const gchar* /*pSetting*/, gpointer frame)
4679 #else
4680 void GtkSalFrame::signalStyleUpdated(GtkWidget*, gpointer frame)
4681 #endif
4683 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4685 // note: settings changed for multiple frames is avoided in winproc.cxx ImplHandleSettings
4686 GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::SettingsChanged );
4688 // a plausible alternative might be to send SalEvent::FontChanged if pSetting starts with "gtk-xft"
4690 // fire off font-changed when the system cairo font hints change
4691 GtkInstance *pInstance = GetGtkInstance();
4692 const cairo_font_options_t* pLastCairoFontOptions = pInstance->GetLastSeenCairoFontOptions();
4693 const cairo_font_options_t* pCurrentCairoFontOptions = pThis->get_font_options();
4694 bool bFontSettingsChanged = true;
4695 if (pLastCairoFontOptions && pCurrentCairoFontOptions)
4696 bFontSettingsChanged = !cairo_font_options_equal(pLastCairoFontOptions, pCurrentCairoFontOptions);
4697 else if (!pLastCairoFontOptions && !pCurrentCairoFontOptions)
4698 bFontSettingsChanged = false;
4699 if (bFontSettingsChanged)
4701 pInstance->ResetLastSeenCairoFontOptions(pCurrentCairoFontOptions);
4702 GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::FontChanged );
4706 #if !GTK_CHECK_VERSION(4, 0, 0)
4707 gboolean GtkSalFrame::signalWindowState( GtkWidget*, GdkEvent* pEvent, gpointer frame )
4709 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4710 if( (pThis->m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) != (pEvent->window_state.new_window_state & GDK_TOPLEVEL_STATE_MINIMIZED) )
4712 GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize );
4713 pThis->TriggerPaintEvent();
4716 if ((pEvent->window_state.new_window_state & GDK_TOPLEVEL_STATE_MAXIMIZED) &&
4717 !(pThis->m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED))
4719 pThis->m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(pThis->m_pWindow));
4722 if ((pEvent->window_state.new_window_state & GDK_WINDOW_STATE_WITHDRAWN) &&
4723 !(pThis->m_nState & GDK_WINDOW_STATE_WITHDRAWN))
4725 if (pThis->isFloatGrabWindow())
4726 pThis->closePopup();
4729 pThis->m_nState = pEvent->window_state.new_window_state;
4731 return false;
4733 #else
4734 void GtkSalFrame::signalWindowState(GdkToplevel* pSurface, GParamSpec*, gpointer frame)
4736 GdkToplevelState eNewWindowState = gdk_toplevel_get_state(pSurface);
4738 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4739 if( (pThis->m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) != (eNewWindowState & GDK_TOPLEVEL_STATE_MINIMIZED) )
4741 GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize );
4742 pThis->TriggerPaintEvent();
4745 if ((eNewWindowState & GDK_TOPLEVEL_STATE_MAXIMIZED) &&
4746 !(pThis->m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED))
4748 pThis->m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(pThis->m_pWindow));
4751 pThis->m_nState = eNewWindowState;
4753 #endif
4755 namespace
4757 bool handleSignalZoom(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame,
4758 GestureEventZoomType eEventType)
4760 gdouble x = 0;
4761 gdouble y = 0;
4762 gtk_gesture_get_point(gesture, sequence, &x, &y);
4764 SalGestureZoomEvent aEvent;
4765 aEvent.meEventType = eEventType;
4766 aEvent.mnX = x;
4767 aEvent.mnY = y;
4768 aEvent.mfScaleDelta = gtk_gesture_zoom_get_scale_delta(GTK_GESTURE_ZOOM(gesture));
4769 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4770 pThis->CallCallbackExc(SalEvent::GestureZoom, &aEvent);
4771 return true;
4774 bool handleSignalRotate(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame,
4775 GestureEventRotateType eEventType)
4777 gdouble x = 0;
4778 gdouble y = 0;
4779 gtk_gesture_get_point(gesture, sequence, &x, &y);
4781 SalGestureRotateEvent aEvent;
4782 aEvent.meEventType = eEventType;
4783 aEvent.mnX = x;
4784 aEvent.mnY = y;
4785 aEvent.mfAngleDelta = gtk_gesture_rotate_get_angle_delta(GTK_GESTURE_ROTATE(gesture));
4786 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4787 pThis->CallCallbackExc(SalEvent::GestureRotate, &aEvent);
4788 return true;
4792 bool GtkSalFrame::signalZoomBegin(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame)
4794 return handleSignalZoom(gesture, sequence, frame, GestureEventZoomType::Begin);
4797 bool GtkSalFrame::signalZoomUpdate(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame)
4799 return handleSignalZoom(gesture, sequence, frame, GestureEventZoomType::Update);
4802 bool GtkSalFrame::signalZoomEnd(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame)
4804 return handleSignalZoom(gesture, sequence, frame, GestureEventZoomType::End);
4807 bool GtkSalFrame::signalRotateBegin(GtkGesture* gesture, GdkEventSequence* sequence,
4808 gpointer frame)
4810 return handleSignalRotate(gesture, sequence, frame, GestureEventRotateType::Begin);
4813 bool GtkSalFrame::signalRotateUpdate(GtkGesture* gesture, GdkEventSequence* sequence,
4814 gpointer frame)
4816 return handleSignalRotate(gesture, sequence, frame, GestureEventRotateType::Update);
4819 bool GtkSalFrame::signalRotateEnd(GtkGesture* gesture, GdkEventSequence* sequence,
4820 gpointer frame)
4822 return handleSignalRotate(gesture, sequence, frame, GestureEventRotateType::End);
4825 namespace
4827 GdkDragAction VclToGdk(sal_Int8 dragOperation)
4829 GdkDragAction eRet(static_cast<GdkDragAction>(0));
4830 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
4831 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_COPY);
4832 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
4833 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_MOVE);
4834 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
4835 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_LINK);
4836 return eRet;
4839 sal_Int8 GdkToVcl(GdkDragAction dragOperation)
4841 sal_Int8 nRet(0);
4842 if (dragOperation & GDK_ACTION_COPY)
4843 nRet |= css::datatransfer::dnd::DNDConstants::ACTION_COPY;
4844 if (dragOperation & GDK_ACTION_MOVE)
4845 nRet |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
4846 if (dragOperation & GDK_ACTION_LINK)
4847 nRet |= css::datatransfer::dnd::DNDConstants::ACTION_LINK;
4848 return nRet;
4852 namespace
4854 GdkDragAction getPreferredDragAction(sal_Int8 dragOperation)
4856 GdkDragAction eAct(static_cast<GdkDragAction>(0));
4858 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
4859 eAct = GDK_ACTION_MOVE;
4860 else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
4861 eAct = GDK_ACTION_COPY;
4862 else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
4863 eAct = GDK_ACTION_LINK;
4865 return eAct;
4869 static bool g_DropSuccessSet = false;
4870 static bool g_DropSuccess = false;
4872 namespace {
4874 #if GTK_CHECK_VERSION(4, 0, 0)
4876 void read_drop_async_completed(GObject* source, GAsyncResult* res, gpointer user_data)
4878 GdkDrop* drop = GDK_DROP(source);
4879 read_transfer_result* pRes = static_cast<read_transfer_result*>(user_data);
4881 GInputStream* pResult = gdk_drop_read_finish(drop, res, nullptr, nullptr);
4883 if (!pResult)
4885 pRes->bDone = true;
4886 g_main_context_wakeup(nullptr);
4887 return;
4890 pRes->aVector.resize(read_transfer_result::BlockSize);
4892 g_input_stream_read_async(pResult,
4893 pRes->aVector.data(),
4894 pRes->aVector.size(),
4895 G_PRIORITY_DEFAULT,
4896 nullptr,
4897 read_transfer_result::read_block_async_completed,
4898 user_data);
4900 #endif
4902 class GtkDropTargetDropContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDropContext>
4904 #if !GTK_CHECK_VERSION(4, 0, 0)
4905 GdkDragContext *m_pContext;
4906 guint m_nTime;
4907 #else
4908 GdkDrop* m_pDrop;
4909 #endif
4910 public:
4911 #if !GTK_CHECK_VERSION(4, 0, 0)
4912 GtkDropTargetDropContext(GdkDragContext* pContext, guint nTime)
4913 : m_pContext(pContext)
4914 , m_nTime(nTime)
4915 #else
4916 GtkDropTargetDropContext(GdkDrop* pDrop)
4917 : m_pDrop(pDrop)
4918 #endif
4922 // XDropTargetDropContext
4923 virtual void SAL_CALL acceptDrop(sal_Int8 dragOperation) override
4925 #if !GTK_CHECK_VERSION(4, 0, 0)
4926 gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
4927 #else
4928 GdkDragAction eDragAction = getPreferredDragAction(dragOperation);
4929 gdk_drop_status(m_pDrop,
4930 static_cast<GdkDragAction>(eDragAction | gdk_drop_get_actions(m_pDrop)),
4931 eDragAction);
4932 #endif
4935 virtual void SAL_CALL rejectDrop() override
4937 #if !GTK_CHECK_VERSION(4, 0, 0)
4938 gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
4939 #else
4940 gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), static_cast<GdkDragAction>(0));
4941 #endif
4944 virtual void SAL_CALL dropComplete(sal_Bool bSuccess) override
4946 #if !GTK_CHECK_VERSION(4, 0, 0)
4947 gtk_drag_finish(m_pContext, bSuccess, false, m_nTime);
4948 #else
4949 // should we do something better here
4950 gdk_drop_finish(m_pDrop, bSuccess
4951 ? gdk_drop_get_actions(m_pDrop)
4952 : static_cast<GdkDragAction>(0));
4953 #endif
4954 if (GtkInstDragSource::g_ActiveDragSource)
4956 g_DropSuccessSet = true;
4957 g_DropSuccess = bSuccess;
4964 class GtkDnDTransferable : public GtkTransferable
4966 #if !GTK_CHECK_VERSION(4, 0, 0)
4967 GdkDragContext *m_pContext;
4968 guint m_nTime;
4969 GtkWidget *m_pWidget;
4970 GtkInstDropTarget* m_pDropTarget;
4971 #else
4972 GdkDrop* m_pDrop;
4973 #endif
4974 #if !GTK_CHECK_VERSION(4, 0, 0)
4975 GMainLoop *m_pLoop;
4976 GtkSelectionData *m_pData;
4977 #endif
4978 public:
4979 #if !GTK_CHECK_VERSION(4, 0, 0)
4980 GtkDnDTransferable(GdkDragContext *pContext, guint nTime, GtkWidget *pWidget, GtkInstDropTarget *pDropTarget)
4981 : m_pContext(pContext)
4982 , m_nTime(nTime)
4983 , m_pWidget(pWidget)
4984 , m_pDropTarget(pDropTarget)
4985 #else
4986 GtkDnDTransferable(GdkDrop *pDrop)
4987 : m_pDrop(pDrop)
4988 #endif
4989 #if !GTK_CHECK_VERSION(4, 0, 0)
4990 , m_pLoop(nullptr)
4991 , m_pData(nullptr)
4992 #endif
4996 virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override
4998 css::datatransfer::DataFlavor aFlavor(rFlavor);
4999 if (aFlavor.MimeType == "text/plain;charset=utf-16")
5000 aFlavor.MimeType = "text/plain;charset=utf-8";
5002 auto it = m_aMimeTypeToGtkType.find(aFlavor.MimeType);
5003 if (it == m_aMimeTypeToGtkType.end())
5004 return css::uno::Any();
5006 css::uno::Any aRet;
5008 #if !GTK_CHECK_VERSION(4, 0, 0)
5009 /* like gtk_clipboard_wait_for_contents run a sub loop
5010 * waiting for drag-data-received triggered from
5011 * gtk_drag_get_data
5014 m_pLoop = g_main_loop_new(nullptr, true);
5015 m_pDropTarget->SetFormatConversionRequest(this);
5017 gtk_drag_get_data(m_pWidget, m_pContext, it->second, m_nTime);
5019 if (g_main_loop_is_running(m_pLoop))
5020 main_loop_run(m_pLoop);
5022 g_main_loop_unref(m_pLoop);
5023 m_pLoop = nullptr;
5024 m_pDropTarget->SetFormatConversionRequest(nullptr);
5027 if (aFlavor.MimeType == "text/plain;charset=utf-8")
5029 OUString aStr;
5030 gchar *pText = reinterpret_cast<gchar*>(gtk_selection_data_get_text(m_pData));
5031 if (pText)
5032 aStr = OStringToOUString(pText, RTL_TEXTENCODING_UTF8);
5033 g_free(pText);
5034 aRet <<= aStr.replaceAll("\r\n", "\n");
5036 else
5038 gint length(0);
5039 const guchar *rawdata = gtk_selection_data_get_data_with_length(m_pData,
5040 &length);
5041 // seen here was rawhide == nullptr and length set to -1
5042 if (rawdata)
5044 css::uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
5045 aRet <<= aSeq;
5049 gtk_selection_data_free(m_pData);
5050 #else
5051 SalInstance* pInstance = GetSalInstance();
5052 read_transfer_result aRes;
5053 const char *mime_types[] = { it->second.getStr(), nullptr };
5055 gdk_drop_read_async(m_pDrop,
5056 mime_types,
5057 G_PRIORITY_DEFAULT,
5058 nullptr,
5059 read_drop_async_completed,
5060 &aRes);
5062 while (!aRes.bDone)
5063 pInstance->DoYield(true, false);
5065 if (aFlavor.MimeType == "text/plain;charset=utf-8")
5066 aRet <<= aRes.get_as_string();
5067 else
5068 aRet <<= aRes.get_as_sequence();
5069 #endif
5070 return aRet;
5073 virtual std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector() override
5075 #if !GTK_CHECK_VERSION(4, 0, 0)
5076 std::vector<GdkAtom> targets;
5077 for (GList* l = gdk_drag_context_list_targets(m_pContext); l; l = l->next)
5078 targets.push_back(static_cast<GdkAtom>(l->data));
5079 return GtkTransferable::getTransferDataFlavorsAsVector(targets.data(), targets.size());
5080 #else
5081 GdkContentFormats* pFormats = gdk_drop_get_formats(m_pDrop);
5082 gsize n_targets;
5083 const char * const *targets = gdk_content_formats_get_mime_types(pFormats, &n_targets);
5084 return GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
5085 #endif
5088 #if !GTK_CHECK_VERSION(4, 0, 0)
5089 void LoopEnd(GtkSelectionData *pData)
5091 m_pData = pData;
5092 g_main_loop_quit(m_pLoop);
5094 #endif
5097 // For LibreOffice internal D&D we provide the Transferable without Gtk
5098 // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
5099 GtkInstDragSource* GtkInstDragSource::g_ActiveDragSource;
5101 #if GTK_CHECK_VERSION(4, 0, 0)
5103 gboolean GtkSalFrame::signalDragDrop(GtkDropTargetAsync* context, GdkDrop* drop, double x, double y, gpointer frame)
5105 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
5106 if (!pThis->m_pDropTarget)
5107 return false;
5108 return pThis->m_pDropTarget->signalDragDrop(context, drop, x, y);
5110 #else
5111 gboolean GtkSalFrame::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time, gpointer frame)
5113 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
5114 if (!pThis->m_pDropTarget)
5115 return false;
5116 return pThis->m_pDropTarget->signalDragDrop(pWidget, context, x, y, time);
5118 #endif
5120 #if !GTK_CHECK_VERSION(4, 0, 0)
5121 gboolean GtkInstDropTarget::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time)
5122 #else
5123 gboolean GtkInstDropTarget::signalDragDrop(GtkDropTargetAsync* context, GdkDrop* drop, double x, double y)
5124 #endif
5126 // remove the deferred dragExit, as we'll do a drop
5127 #ifndef NDEBUG
5128 bool res =
5129 #endif
5130 g_idle_remove_by_data(this);
5131 #ifndef NDEBUG
5132 #if !GTK_CHECK_VERSION(4, 0, 0)
5133 assert(res);
5134 #else
5135 (void)res;
5136 #endif
5137 #endif
5139 css::datatransfer::dnd::DropTargetDropEvent aEvent;
5140 aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(this);
5141 #if !GTK_CHECK_VERSION(4, 0, 0)
5142 aEvent.Context = new GtkDropTargetDropContext(context, time);
5143 #else
5144 aEvent.Context = new GtkDropTargetDropContext(drop);
5145 #endif
5146 aEvent.LocationX = x;
5147 aEvent.LocationY = y;
5148 #if !GTK_CHECK_VERSION(4, 0, 0)
5149 aEvent.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
5150 #else
5151 aEvent.DropAction = GdkToVcl(getPreferredDragAction(GdkToVcl(gdk_drop_get_actions(drop))));
5152 #endif
5153 // ACTION_DEFAULT is documented as...
5154 // 'This means the user did not press any key during the Drag and Drop operation
5155 // and the action that was combined with ACTION_DEFAULT is the system default action'
5156 // in tdf#107031 writer won't insert a link when a heading is dragged from the
5157 // navigator unless this is set. Its unclear really what ACTION_DEFAULT means,
5158 // there is a deprecated 'GDK_ACTION_DEFAULT Means nothing, and should not be used'
5159 // possible equivalent in gtk.
5160 // So (tdf#109227) set ACTION_DEFAULT if no modifier key is held down
5161 #if !GTK_CHECK_VERSION(4,0,0)
5162 aEvent.SourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
5163 GdkModifierType mask;
5164 gdk_window_get_pointer(widget_get_surface(pWidget), nullptr, nullptr, &mask);
5165 #else
5166 aEvent.SourceActions = GdkToVcl(gdk_drop_get_actions(drop));
5167 GdkModifierType mask = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(context));
5168 #endif
5169 if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
5170 aEvent.DropAction |= css::datatransfer::dnd::DNDConstants::ACTION_DEFAULT;
5172 css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
5173 // For LibreOffice internal D&D we provide the Transferable without Gtk
5174 // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
5175 if (GtkInstDragSource::g_ActiveDragSource)
5176 xTransferable = GtkInstDragSource::g_ActiveDragSource->GetTransferable();
5177 else
5179 #if GTK_CHECK_VERSION(4,0,0)
5180 xTransferable = new GtkDnDTransferable(drop);
5181 #else
5182 xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
5183 #endif
5185 aEvent.Transferable = xTransferable;
5187 fire_drop(aEvent);
5189 return true;
5192 namespace {
5194 class GtkDropTargetDragContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDragContext>
5196 #if !GTK_CHECK_VERSION(4, 0, 0)
5197 GdkDragContext *m_pContext;
5198 guint m_nTime;
5199 #else
5200 GdkDrop* m_pDrop;
5201 #endif
5202 public:
5203 #if !GTK_CHECK_VERSION(4, 0, 0)
5204 GtkDropTargetDragContext(GdkDragContext *pContext, guint nTime)
5205 : m_pContext(pContext)
5206 , m_nTime(nTime)
5207 #else
5208 GtkDropTargetDragContext(GdkDrop* pDrop)
5209 : m_pDrop(pDrop)
5210 #endif
5214 virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) override
5216 #if !GTK_CHECK_VERSION(4, 0, 0)
5217 gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
5218 #else
5219 gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), getPreferredDragAction(dragOperation));
5220 #endif
5223 virtual void SAL_CALL rejectDrag() override
5225 #if !GTK_CHECK_VERSION(4, 0, 0)
5226 gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
5227 #else
5228 gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), static_cast<GdkDragAction>(0));
5229 #endif
5235 #if !GTK_CHECK_VERSION(4, 0, 0)
5236 void GtkSalFrame::signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time, gpointer frame)
5238 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
5239 if (!pThis->m_pDropTarget)
5240 return;
5241 pThis->m_pDropTarget->signalDragDropReceived(pWidget, context, x, y, data, ttype, time);
5244 void GtkInstDropTarget::signalDragDropReceived(GtkWidget* /*pWidget*/, GdkDragContext * /*context*/, gint /*x*/, gint /*y*/, GtkSelectionData* data, guint /*ttype*/, guint /*time*/)
5247 * If we get a drop, then we will call like gtk_clipboard_wait_for_contents
5248 * with a loop inside a loop to get the right format, so if this is the
5249 * case return to the outer loop here with a copy of the desired data
5251 * don't look at me like that.
5253 if (!m_pFormatConversionRequest)
5254 return;
5256 m_pFormatConversionRequest->LoopEnd(gtk_selection_data_copy(data));
5258 #endif
5260 #if GTK_CHECK_VERSION(4,0,0)
5261 GdkDragAction GtkSalFrame::signalDragMotion(GtkDropTargetAsync *dest, GdkDrop *drop, double x, double y, gpointer frame)
5263 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
5264 if (!pThis->m_pDropTarget)
5265 return GdkDragAction(0);
5266 return pThis->m_pDropTarget->signalDragMotion(dest, drop, x, y);
5268 #else
5269 gboolean GtkSalFrame::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time, gpointer frame)
5271 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
5272 if (!pThis->m_pDropTarget)
5273 return false;
5274 return pThis->m_pDropTarget->signalDragMotion(pWidget, context, x, y, time);
5276 #endif
5278 #if !GTK_CHECK_VERSION(4,0,0)
5279 gboolean GtkInstDropTarget::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time)
5280 #else
5281 GdkDragAction GtkInstDropTarget::signalDragMotion(GtkDropTargetAsync *context, GdkDrop *pDrop, double x, double y)
5282 #endif
5284 if (!m_bInDrag)
5286 #if !GTK_CHECK_VERSION(4,0,0)
5287 GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) : pWidget;
5288 gtk_drag_highlight(pHighlightWidget);
5289 #else
5290 GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) :
5291 gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(context));
5292 gtk_widget_set_state_flags(pHighlightWidget, GTK_STATE_FLAG_DROP_ACTIVE, false);
5293 #endif
5296 css::datatransfer::dnd::DropTargetDragEnterEvent aEvent;
5297 aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(this);
5298 #if !GTK_CHECK_VERSION(4,0,0)
5299 rtl::Reference<GtkDropTargetDragContext> pContext = new GtkDropTargetDragContext(context, time);
5300 #else
5301 rtl::Reference<GtkDropTargetDragContext> pContext = new GtkDropTargetDragContext(pDrop);
5302 #endif
5303 //preliminary accept the Drag and select the preferred action, the fire_* will
5304 //inform the original caller of our choice and the callsite can decide
5305 //to overrule this choice. i.e. typically here we default to ACTION_MOVE
5306 #if !GTK_CHECK_VERSION(4,0,0)
5307 sal_Int8 nSourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
5308 GdkModifierType mask;
5309 gdk_window_get_pointer(widget_get_surface(pWidget), nullptr, nullptr, &mask);
5310 #else
5311 sal_Int8 nSourceActions = GdkToVcl(gdk_drop_get_actions(pDrop));
5312 GdkModifierType mask = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(context));
5313 #endif
5315 // tdf#124411 default to move if drag originates within LO itself, default
5316 // to copy if it comes from outside, this is similar to srcAndDestEqual
5317 // in macosx DropTarget::determineDropAction equivalent
5318 sal_Int8 nNewDropAction = GtkInstDragSource::g_ActiveDragSource ?
5319 css::datatransfer::dnd::DNDConstants::ACTION_MOVE :
5320 css::datatransfer::dnd::DNDConstants::ACTION_COPY;
5322 // tdf#109227 if a modifier is held down, default to the matching
5323 // action for that modifier combo, otherwise pick the preferred
5324 // default from the possible source actions
5325 if ((mask & GDK_SHIFT_MASK) && !(mask & GDK_CONTROL_MASK))
5326 nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
5327 else if ((mask & GDK_CONTROL_MASK) && !(mask & GDK_SHIFT_MASK))
5328 nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
5329 else if ((mask & GDK_SHIFT_MASK) && (mask & GDK_CONTROL_MASK) )
5330 nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_LINK;
5331 nNewDropAction &= nSourceActions;
5333 GdkDragAction eAction;
5334 if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) && !nNewDropAction)
5335 eAction = getPreferredDragAction(nSourceActions);
5336 else
5337 eAction = getPreferredDragAction(nNewDropAction);
5339 #if !GTK_CHECK_VERSION(4,0,0)
5340 gdk_drag_status(context, eAction, time);
5341 #else
5342 gdk_drop_status(pDrop,
5343 static_cast<GdkDragAction>(eAction | gdk_drop_get_actions(pDrop)),
5344 eAction);
5345 #endif
5346 aEvent.Context = pContext;
5347 aEvent.LocationX = x;
5348 aEvent.LocationY = y;
5349 //under wayland at least, the action selected by gdk_drag_status on the
5350 //context is not immediately available via gdk_drag_context_get_selected_action
5351 //so here we set the DropAction from what we selected on the context, not
5352 //what the context says is selected
5353 aEvent.DropAction = GdkToVcl(eAction);
5354 aEvent.SourceActions = nSourceActions;
5356 if (!m_bInDrag)
5358 css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
5359 // For LibreOffice internal D&D we provide the Transferable without Gtk
5360 // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
5361 if (GtkInstDragSource::g_ActiveDragSource)
5362 xTransferable = GtkInstDragSource::g_ActiveDragSource->GetTransferable();
5363 else
5365 #if !GTK_CHECK_VERSION(4,0,0)
5366 xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
5367 #else
5368 xTransferable = new GtkDnDTransferable(pDrop);
5369 #endif
5371 css::uno::Sequence<css::datatransfer::DataFlavor> aFormats = xTransferable->getTransferDataFlavors();
5372 aEvent.SupportedDataFlavors = aFormats;
5373 fire_dragEnter(aEvent);
5374 m_bInDrag = true;
5376 else
5378 fire_dragOver(aEvent);
5381 #if !GTK_CHECK_VERSION(4,0,0)
5382 return true;
5383 #else
5384 return eAction;
5385 #endif
5388 #if GTK_CHECK_VERSION(4,0,0)
5389 void GtkSalFrame::signalDragLeave(GtkDropTargetAsync* pDest, GdkDrop* /*drop*/, gpointer frame)
5391 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
5392 if (!pThis->m_pDropTarget)
5393 return;
5394 pThis->m_pDropTarget->signalDragLeave(gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(pDest)));
5396 #else
5397 void GtkSalFrame::signalDragLeave(GtkWidget* pWidget, GdkDragContext* /*context*/, guint /*time*/, gpointer frame)
5399 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
5400 if (!pThis->m_pDropTarget)
5401 return;
5402 pThis->m_pDropTarget->signalDragLeave(pWidget);
5404 #endif
5406 static gboolean lcl_deferred_dragExit(gpointer user_data)
5408 GtkInstDropTarget* pThis = static_cast<GtkInstDropTarget*>(user_data);
5409 css::datatransfer::dnd::DropTargetEvent aEvent;
5410 aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(pThis);
5411 pThis->fire_dragExit(aEvent);
5412 return false;
5415 void GtkInstDropTarget::signalDragLeave(GtkWidget *pWidget)
5417 m_bInDrag = false;
5419 GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) : pWidget;
5420 #if !GTK_CHECK_VERSION(4,0,0)
5421 gtk_drag_unhighlight(pHighlightWidget);
5422 #else
5423 gtk_widget_unset_state_flags(pHighlightWidget, GTK_STATE_FLAG_DROP_ACTIVE);
5424 #endif
5426 // defer fire_dragExit, since gtk also sends a drag-leave before the drop, while
5427 // LO expect to either handle the drop or the exit... at least in Writer.
5428 // but since we don't know there will be a drop following the leave, defer the
5429 // exit handling to an idle.
5430 g_idle_add(lcl_deferred_dragExit, this);
5433 void GtkSalFrame::signalDestroy( GtkWidget* pObj, gpointer frame )
5435 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
5436 if( pObj != pThis->m_pWindow )
5437 return;
5439 pThis->m_aDamageHandler.damaged = nullptr;
5440 pThis->m_aDamageHandler.handle = nullptr;
5441 if (pThis->m_pSurface)
5442 cairo_surface_set_user_data(pThis->m_pSurface, SvpSalGraphics::getDamageKey(), nullptr, nullptr);
5443 pThis->m_pFixedContainer = nullptr;
5444 pThis->m_pDrawingArea = nullptr;
5445 #if !GTK_CHECK_VERSION(4, 0, 0)
5446 pThis->m_pEventBox = nullptr;
5447 #endif
5448 pThis->m_pTopLevelGrid = nullptr;
5449 pThis->m_pWindow = nullptr;
5450 pThis->m_xFrameWeld.reset();
5451 pThis->InvalidateGraphics();
5454 // GtkSalFrame::IMHandler
5456 GtkSalFrame::IMHandler::IMHandler( GtkSalFrame* pFrame )
5457 : m_pFrame(pFrame),
5458 m_nPrevKeyPresses( 0 ),
5459 m_pIMContext( nullptr ),
5460 m_bFocused( true ),
5461 m_bPreeditJustChanged( false )
5463 m_aInputEvent.mpTextAttr = nullptr;
5464 createIMContext();
5467 GtkSalFrame::IMHandler::~IMHandler()
5469 // cancel an eventual event posted to begin preedit again
5470 GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
5471 deleteIMContext();
5474 void GtkSalFrame::IMHandler::createIMContext()
5476 if( m_pIMContext )
5477 return;
5479 m_pIMContext = gtk_im_multicontext_new ();
5480 g_signal_connect( m_pIMContext, "commit",
5481 G_CALLBACK (signalIMCommit), this );
5482 g_signal_connect( m_pIMContext, "preedit_changed",
5483 G_CALLBACK (signalIMPreeditChanged), this );
5484 g_signal_connect( m_pIMContext, "retrieve_surrounding",
5485 G_CALLBACK (signalIMRetrieveSurrounding), this );
5486 g_signal_connect( m_pIMContext, "delete_surrounding",
5487 G_CALLBACK (signalIMDeleteSurrounding), this );
5488 g_signal_connect( m_pIMContext, "preedit_start",
5489 G_CALLBACK (signalIMPreeditStart), this );
5490 g_signal_connect( m_pIMContext, "preedit_end",
5491 G_CALLBACK (signalIMPreeditEnd), this );
5493 GetGenericUnixSalData()->ErrorTrapPush();
5494 im_context_set_client_widget(m_pIMContext, m_pFrame->getMouseEventWidget());
5495 #if GTK_CHECK_VERSION(4, 0, 0)
5496 gtk_event_controller_key_set_im_context(m_pFrame->m_pKeyController, m_pIMContext);
5497 #endif
5498 gtk_im_context_focus_in( m_pIMContext );
5499 GetGenericUnixSalData()->ErrorTrapPop();
5500 m_bFocused = true;
5504 void GtkSalFrame::IMHandler::deleteIMContext()
5506 if( !m_pIMContext )
5507 return;
5509 // first give IC a chance to deinitialize
5510 GetGenericUnixSalData()->ErrorTrapPush();
5511 #if GTK_CHECK_VERSION(4, 0, 0)
5512 gtk_event_controller_key_set_im_context(m_pFrame->m_pKeyController, nullptr);
5513 #endif
5514 im_context_set_client_widget(m_pIMContext, nullptr);
5515 GetGenericUnixSalData()->ErrorTrapPop();
5516 // destroy old IC
5517 g_object_unref( m_pIMContext );
5518 m_pIMContext = nullptr;
5521 void GtkSalFrame::IMHandler::doCallEndExtTextInput()
5523 m_aInputEvent.mpTextAttr = nullptr;
5524 m_pFrame->CallCallbackExc( SalEvent::EndExtTextInput, nullptr );
5527 void GtkSalFrame::IMHandler::updateIMSpotLocation()
5529 SalExtTextInputPosEvent aPosEvent;
5530 m_pFrame->CallCallbackExc( SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent) );
5531 GdkRectangle aArea;
5532 aArea.x = aPosEvent.mnX;
5533 aArea.y = aPosEvent.mnY;
5534 aArea.width = aPosEvent.mnWidth;
5535 aArea.height = aPosEvent.mnHeight;
5536 GetGenericUnixSalData()->ErrorTrapPush();
5537 gtk_im_context_set_cursor_location( m_pIMContext, &aArea );
5538 GetGenericUnixSalData()->ErrorTrapPop();
5541 void GtkSalFrame::IMHandler::sendEmptyCommit()
5543 vcl::DeletionListener aDel( m_pFrame );
5545 SalExtTextInputEvent aEmptyEv;
5546 aEmptyEv.mpTextAttr = nullptr;
5547 aEmptyEv.maText.clear();
5548 aEmptyEv.mnCursorPos = 0;
5549 aEmptyEv.mnCursorFlags = 0;
5550 m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&aEmptyEv) );
5551 if( ! aDel.isDeleted() )
5552 m_pFrame->CallCallbackExc( SalEvent::EndExtTextInput, nullptr );
5555 void GtkSalFrame::IMHandler::endExtTextInput( EndExtTextInputFlags /*nFlags*/ )
5557 gtk_im_context_reset ( m_pIMContext );
5559 if( !m_aInputEvent.mpTextAttr )
5560 return;
5562 vcl::DeletionListener aDel( m_pFrame );
5563 // delete preedit in sal (commit an empty string)
5564 sendEmptyCommit();
5565 if( ! aDel.isDeleted() )
5567 // mark previous preedit state again (will e.g. be sent at focus gain)
5568 m_aInputEvent.mpTextAttr = m_aInputFlags.data();
5569 if( m_bFocused )
5571 // begin preedit again
5572 GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
5577 void GtkSalFrame::IMHandler::focusChanged( bool bFocusIn )
5579 m_bFocused = bFocusIn;
5580 if( bFocusIn )
5582 GetGenericUnixSalData()->ErrorTrapPush();
5583 gtk_im_context_focus_in( m_pIMContext );
5584 GetGenericUnixSalData()->ErrorTrapPop();
5585 if( m_aInputEvent.mpTextAttr )
5587 sendEmptyCommit();
5588 // begin preedit again
5589 GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
5592 else
5594 GetGenericUnixSalData()->ErrorTrapPush();
5595 gtk_im_context_focus_out( m_pIMContext );
5596 GetGenericUnixSalData()->ErrorTrapPop();
5597 // cancel an eventual event posted to begin preedit again
5598 GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
5602 #if !GTK_CHECK_VERSION(4, 0, 0)
5603 bool GtkSalFrame::IMHandler::handleKeyEvent( GdkEventKey* pEvent )
5605 vcl::DeletionListener aDel( m_pFrame );
5607 if( pEvent->type == GDK_KEY_PRESS )
5609 // Add this key press event to the list of previous key presses
5610 // to which we compare key release events. If a later key release
5611 // event has a matching key press event in this list, we swallow
5612 // the key release because some GTK Input Methods don't swallow it
5613 // for us.
5614 m_aPrevKeyPresses.emplace_back(pEvent );
5615 m_nPrevKeyPresses++;
5617 // Also pop off the earliest key press event if there are more than 10
5618 // already.
5619 while (m_nPrevKeyPresses > 10)
5621 m_aPrevKeyPresses.pop_front();
5622 m_nPrevKeyPresses--;
5625 GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) );
5627 // #i51353# update spot location on every key input since we cannot
5628 // know which key may activate a preedit choice window
5629 updateIMSpotLocation();
5630 if( aDel.isDeleted() )
5631 return true;
5633 bool bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
5634 g_object_unref( pRef );
5636 if( aDel.isDeleted() )
5637 return true;
5639 m_bPreeditJustChanged = false;
5641 if( bResult )
5642 return true;
5643 else
5645 SAL_WARN_IF( m_nPrevKeyPresses <= 0, "vcl.gtk3", "key press has vanished !" );
5646 if( ! m_aPrevKeyPresses.empty() ) // sanity check
5648 // event was not swallowed, do not filter a following
5649 // key release event
5650 // note: this relies on gtk_im_context_filter_keypress
5651 // returning without calling a handler (in the "not swallowed"
5652 // case ) which might change the previous key press list so
5653 // we would pop the wrong event here
5654 m_aPrevKeyPresses.pop_back();
5655 m_nPrevKeyPresses--;
5660 // Determine if we got an earlier key press event corresponding to this key release
5661 if (pEvent->type == GDK_KEY_RELEASE)
5663 GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) );
5664 bool bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
5665 g_object_unref( pRef );
5667 if( aDel.isDeleted() )
5668 return true;
5670 m_bPreeditJustChanged = false;
5672 auto iter = std::find(m_aPrevKeyPresses.begin(), m_aPrevKeyPresses.end(), pEvent);
5673 // If we found a corresponding previous key press event, swallow the release
5674 // and remove the earlier key press from our list
5675 if (iter != m_aPrevKeyPresses.end())
5677 m_aPrevKeyPresses.erase(iter);
5678 m_nPrevKeyPresses--;
5679 return true;
5682 if( bResult )
5683 return true;
5686 return false;
5689 /* FIXME:
5690 * #122282# still more hacking: some IMEs never start a preedit but simply commit
5691 * in this case we cannot commit a single character. Workaround: do not do the
5692 * single key hack for enter or space if the unicode committed does not match
5695 static bool checkSingleKeyCommitHack( guint keyval, sal_Unicode cCode )
5697 bool bRet = true;
5698 switch( keyval )
5700 case GDK_KEY_KP_Enter:
5701 case GDK_KEY_Return:
5702 if( cCode != '\n' && cCode != '\r' )
5703 bRet = false;
5704 break;
5705 case GDK_KEY_space:
5706 case GDK_KEY_KP_Space:
5707 if( cCode != ' ' )
5708 bRet = false;
5709 break;
5710 default:
5711 break;
5713 return bRet;
5716 void GtkSalFrame::IMHandler::signalIMCommit( GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler )
5718 GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
5720 SolarMutexGuard aGuard;
5721 vcl::DeletionListener aDel( pThis->m_pFrame );
5723 const bool bWasPreedit =
5724 (pThis->m_aInputEvent.mpTextAttr != nullptr) ||
5725 pThis->m_bPreeditJustChanged;
5727 pThis->m_aInputEvent.mpTextAttr = nullptr;
5728 pThis->m_aInputEvent.maText = OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 );
5729 pThis->m_aInputEvent.mnCursorPos = pThis->m_aInputEvent.maText.getLength();
5730 pThis->m_aInputEvent.mnCursorFlags = 0;
5732 pThis->m_aInputFlags.clear();
5734 /* necessary HACK: all keyboard input comes in here as soon as an IMContext is set
5735 * which is logical and consequent. But since even simple input like
5736 * <space> comes through the commit signal instead of signalKey
5737 * and all kinds of windows only implement KeyInput (e.g. PushButtons,
5738 * RadioButtons and a lot of other Controls), will send a single
5739 * KeyInput/KeyUp sequence instead of an ExtText event if there
5740 * never was a preedit and the text is only one character.
5742 * In this case there the last ExtText event must have been
5743 * SalEvent::EndExtTextInput, either because of a regular commit
5744 * or because there never was a preedit.
5746 bool bSingleCommit = false;
5747 if( ! bWasPreedit
5748 && pThis->m_aInputEvent.maText.getLength() == 1
5749 && ! pThis->m_aPrevKeyPresses.empty()
5752 const PreviousKeyPress& rKP = pThis->m_aPrevKeyPresses.back();
5753 sal_Unicode aOrigCode = pThis->m_aInputEvent.maText[0];
5755 if( checkSingleKeyCommitHack( rKP.keyval, aOrigCode ) )
5757 pThis->m_pFrame->doKeyCallback( rKP.state, rKP.keyval, rKP.hardware_keycode, rKP.group, aOrigCode, true, true );
5758 bSingleCommit = true;
5761 if( ! bSingleCommit )
5763 pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
5764 if( ! aDel.isDeleted() )
5765 pThis->doCallEndExtTextInput();
5767 if( ! aDel.isDeleted() )
5769 // reset input event
5770 pThis->m_aInputEvent.maText.clear();
5771 pThis->m_aInputEvent.mnCursorPos = 0;
5772 pThis->updateIMSpotLocation();
5776 #else
5777 void GtkSalFrame::IMHandler::signalIMCommit( GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler )
5779 GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
5781 SolarMutexGuard aGuard;
5782 vcl::DeletionListener aDel( pThis->m_pFrame );
5784 #if 0
5785 const bool bWasPreedit =
5786 (pThis->m_aInputEvent.mpTextAttr != nullptr) ||
5787 pThis->m_bPreeditJustChanged;
5788 #endif
5790 pThis->m_aInputEvent.mpTextAttr = nullptr;
5791 pThis->m_aInputEvent.maText = OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 );
5792 pThis->m_aInputEvent.mnCursorPos = pThis->m_aInputEvent.maText.getLength();
5793 pThis->m_aInputEvent.mnCursorFlags = 0;
5795 pThis->m_aInputFlags.clear();
5797 /* necessary HACK: all keyboard input comes in here as soon as an IMContext is set
5798 * which is logical and consequent. But since even simple input like
5799 * <space> comes through the commit signal instead of signalKey
5800 * and all kinds of windows only implement KeyInput (e.g. PushButtons,
5801 * RadioButtons and a lot of other Controls), will send a single
5802 * KeyInput/KeyUp sequence instead of an ExtText event if there
5803 * never was a preedit and the text is only one character.
5805 * In this case there the last ExtText event must have been
5806 * SalEvent::EndExtTextInput, either because of a regular commit
5807 * or because there never was a preedit.
5809 bool bSingleCommit = false;
5810 #if 0
5811 // TODO this needs a rethink to work again if necessary
5812 if( ! bWasPreedit
5813 && pThis->m_aInputEvent.maText.getLength() == 1
5814 && ! pThis->m_aPrevKeyPresses.empty()
5817 const PreviousKeyPress& rKP = pThis->m_aPrevKeyPresses.back();
5818 sal_Unicode aOrigCode = pThis->m_aInputEvent.maText[0];
5820 if( checkSingleKeyCommitHack( rKP.keyval, aOrigCode ) )
5822 pThis->m_pFrame->doKeyCallback( rKP.state, rKP.keyval, rKP.hardware_keycode, rKP.group, aOrigCode, true, true );
5823 bSingleCommit = true;
5826 #endif
5827 if( ! bSingleCommit )
5829 pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
5830 if( ! aDel.isDeleted() )
5831 pThis->doCallEndExtTextInput();
5833 if( ! aDel.isDeleted() )
5835 // reset input event
5836 pThis->m_aInputEvent.maText.clear();
5837 pThis->m_aInputEvent.mnCursorPos = 0;
5838 pThis->updateIMSpotLocation();
5842 #endif
5844 OUString GtkSalFrame::GetPreeditDetails(GtkIMContext* pIMContext, std::vector<ExtTextInputAttr>& rInputFlags, sal_Int32& rCursorPos, sal_uInt8& rCursorFlags)
5846 char* pText = nullptr;
5847 PangoAttrList* pAttrs = nullptr;
5848 gint nCursorPos = 0;
5850 gtk_im_context_get_preedit_string( pIMContext,
5851 &pText,
5852 &pAttrs,
5853 &nCursorPos );
5855 gint nUtf8Len = pText ? strlen(pText) : 0;
5856 OUString sText = pText ? OUString(pText, nUtf8Len, RTL_TEXTENCODING_UTF8) : OUString();
5858 std::vector<sal_Int32> aUtf16Offsets;
5859 for (sal_Int32 nUtf16Offset = 0; nUtf16Offset < sText.getLength(); sText.iterateCodePoints(&nUtf16Offset))
5860 aUtf16Offsets.push_back(nUtf16Offset);
5862 sal_Int32 nUtf32Len = aUtf16Offsets.size();
5863 // from the above loop filling aUtf16Offsets, we know that its size() fits into sal_Int32
5864 aUtf16Offsets.push_back(sText.getLength());
5866 // sanitize the CurPos which is in utf-32
5867 if (nCursorPos < 0)
5868 nCursorPos = 0;
5869 else if (nCursorPos > nUtf32Len)
5870 nCursorPos = nUtf32Len;
5872 rCursorPos = aUtf16Offsets[nCursorPos];
5873 rCursorFlags = 0;
5875 rInputFlags.resize(std::max(1, static_cast<int>(sText.getLength())), ExtTextInputAttr::NONE);
5877 PangoAttrIterator *iter = pango_attr_list_get_iterator(pAttrs);
5880 GSList *attr_list = nullptr;
5881 GSList *tmp_list = nullptr;
5882 gint nUtf8Start, nUtf8End;
5883 ExtTextInputAttr sal_attr = ExtTextInputAttr::NONE;
5885 // docs say... "Get the range of the current segment ... the stored
5886 // return values are signed, not unsigned like the values in
5887 // PangoAttribute", which implies that the units are otherwise the same
5888 // as that of PangoAttribute whose docs state these units are "in
5889 // bytes"
5890 // so this is the utf8 range
5891 pango_attr_iterator_range(iter, &nUtf8Start, &nUtf8End);
5893 // sanitize the utf8 range
5894 nUtf8Start = std::min(nUtf8Start, nUtf8Len);
5895 nUtf8End = std::min(nUtf8End, nUtf8Len);
5896 if (nUtf8Start >= nUtf8End)
5897 continue;
5899 // get the utf32 range
5900 sal_Int32 nUtf32Start = g_utf8_pointer_to_offset(pText, pText + nUtf8Start);
5901 sal_Int32 nUtf32End = g_utf8_pointer_to_offset(pText, pText + nUtf8End);
5903 // sanitize the utf32 range
5904 nUtf32Start = std::min(nUtf32Start, nUtf32Len);
5905 nUtf32End = std::min(nUtf32End, nUtf32Len);
5906 if (nUtf32Start >= nUtf32End)
5907 continue;
5909 tmp_list = attr_list = pango_attr_iterator_get_attrs (iter);
5910 while (tmp_list)
5912 PangoAttribute *pango_attr = static_cast<PangoAttribute *>(tmp_list->data);
5914 switch (pango_attr->klass->type)
5916 case PANGO_ATTR_BACKGROUND:
5917 sal_attr |= ExtTextInputAttr::Highlight;
5918 rCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
5919 break;
5920 case PANGO_ATTR_UNDERLINE:
5922 PangoAttrInt* pango_underline = reinterpret_cast<PangoAttrInt*>(pango_attr);
5923 switch (pango_underline->value)
5925 case PANGO_UNDERLINE_NONE:
5926 break;
5927 case PANGO_UNDERLINE_DOUBLE:
5928 sal_attr |= ExtTextInputAttr::DoubleUnderline;
5929 break;
5930 default:
5931 sal_attr |= ExtTextInputAttr::Underline;
5932 break;
5934 break;
5936 case PANGO_ATTR_STRIKETHROUGH:
5937 sal_attr |= ExtTextInputAttr::RedText;
5938 break;
5939 default:
5940 break;
5942 pango_attribute_destroy (pango_attr);
5943 tmp_list = tmp_list->next;
5945 if (!attr_list)
5946 sal_attr |= ExtTextInputAttr::Underline;
5947 g_slist_free (attr_list);
5949 // Set the sal attributes on our text
5950 // rhbz#1648281 apply over our utf-16 range derived from the input utf-32 range
5951 for (sal_Int32 i = aUtf16Offsets[nUtf32Start]; i < aUtf16Offsets[nUtf32End]; ++i)
5953 SAL_WARN_IF(i >= static_cast<int>(rInputFlags.size()),
5954 "vcl.gtk3", "pango attrib out of range. Broken range: "
5955 << aUtf16Offsets[nUtf32Start] << "," << aUtf16Offsets[nUtf32End] << " Legal range: 0,"
5956 << rInputFlags.size());
5957 if (i >= static_cast<int>(rInputFlags.size()))
5958 continue;
5959 rInputFlags[i] |= sal_attr;
5961 } while (pango_attr_iterator_next (iter));
5962 pango_attr_iterator_destroy(iter);
5964 g_free( pText );
5965 pango_attr_list_unref( pAttrs );
5967 return sText;
5970 void GtkSalFrame::IMHandler::signalIMPreeditChanged( GtkIMContext* pIMContext, gpointer im_handler )
5972 GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
5974 sal_Int32 nCursorPos(0);
5975 sal_uInt8 nCursorFlags(0);
5976 std::vector<ExtTextInputAttr> aInputFlags;
5977 OUString sText = GtkSalFrame::GetPreeditDetails(pIMContext, aInputFlags, nCursorPos, nCursorFlags);
5978 if (sText.isEmpty() && pThis->m_aInputEvent.maText.isEmpty())
5980 // change from nothing to nothing -> do not start preedit
5981 // e.g. this will activate input into a calc cell without
5982 // user input
5983 return;
5986 pThis->m_bPreeditJustChanged = true;
5988 bool bEndPreedit = sText.isEmpty() && pThis->m_aInputEvent.mpTextAttr != nullptr;
5989 pThis->m_aInputEvent.maText = sText;
5990 pThis->m_aInputEvent.mnCursorPos = nCursorPos;
5991 pThis->m_aInputEvent.mnCursorFlags = nCursorFlags;
5992 pThis->m_aInputFlags = aInputFlags;
5993 pThis->m_aInputEvent.mpTextAttr = pThis->m_aInputFlags.data();
5995 SolarMutexGuard aGuard;
5996 vcl::DeletionListener aDel( pThis->m_pFrame );
5998 pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
5999 if( bEndPreedit && ! aDel.isDeleted() )
6000 pThis->doCallEndExtTextInput();
6001 if( ! aDel.isDeleted() )
6002 pThis->updateIMSpotLocation();
6005 void GtkSalFrame::IMHandler::signalIMPreeditStart( GtkIMContext*, gpointer /*im_handler*/ )
6009 void GtkSalFrame::IMHandler::signalIMPreeditEnd( GtkIMContext*, gpointer im_handler )
6011 GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
6013 pThis->m_bPreeditJustChanged = true;
6015 SolarMutexGuard aGuard;
6016 vcl::DeletionListener aDel( pThis->m_pFrame );
6017 pThis->doCallEndExtTextInput();
6018 if( ! aDel.isDeleted() )
6019 pThis->updateIMSpotLocation();
6022 gboolean GtkSalFrame::IMHandler::signalIMRetrieveSurrounding( GtkIMContext* pContext, gpointer im_handler )
6024 GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
6026 SalSurroundingTextRequestEvent aEvt;
6027 aEvt.maText.clear();
6028 aEvt.mnStart = aEvt.mnEnd = 0;
6030 SolarMutexGuard aGuard;
6031 pThis->m_pFrame->CallCallback(SalEvent::SurroundingTextRequest, &aEvt);
6033 OString sUTF = OUStringToOString(aEvt.maText, RTL_TEXTENCODING_UTF8);
6034 std::u16string_view sCursorText(aEvt.maText.subView(0, aEvt.mnStart));
6035 gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
6036 OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
6037 return true;
6040 gboolean GtkSalFrame::IMHandler::signalIMDeleteSurrounding( GtkIMContext*, gint offset, gint nchars,
6041 gpointer im_handler )
6043 GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
6045 // First get the surrounding text
6046 SalSurroundingTextRequestEvent aSurroundingTextEvt;
6047 aSurroundingTextEvt.maText.clear();
6048 aSurroundingTextEvt.mnStart = aSurroundingTextEvt.mnEnd = 0;
6050 SolarMutexGuard aGuard;
6051 pThis->m_pFrame->CallCallback(SalEvent::SurroundingTextRequest, &aSurroundingTextEvt);
6053 // Turn offset, nchars into a utf-16 selection
6054 Selection aSelection = SalFrame::CalcDeleteSurroundingSelection(aSurroundingTextEvt.maText,
6055 aSurroundingTextEvt.mnStart,
6056 offset, nchars);
6057 Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32);
6058 if (aSelection == aInvalid)
6059 return false;
6061 SalSurroundingTextSelectionChangeEvent aEvt;
6062 aEvt.mnStart = aSelection.Min();
6063 aEvt.mnEnd = aSelection.Max();
6065 pThis->m_pFrame->CallCallback(SalEvent::DeleteSurroundingTextRequest, &aEvt);
6067 aSelection = Selection(aEvt.mnStart, aEvt.mnEnd);
6068 if (aSelection == aInvalid)
6069 return false;
6071 return true;
6074 Size GtkSalDisplay::GetScreenSize( int nDisplayScreen )
6076 tools::Rectangle aRect = m_pSys->GetDisplayScreenPosSizePixel( nDisplayScreen );
6077 return Size( aRect.GetWidth(), aRect.GetHeight() );
6080 sal_uIntPtr GtkSalFrame::GetNativeWindowHandle(GtkWidget *pWidget)
6082 GdkSurface* pSurface = widget_get_surface(pWidget);
6083 GdkDisplay *pDisplay = getGdkDisplay();
6085 #if defined(GDK_WINDOWING_X11)
6086 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
6088 return gdk_x11_surface_get_xid(pSurface);
6090 #endif
6092 #if defined(GDK_WINDOWING_WAYLAND)
6093 if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
6095 return reinterpret_cast<sal_uIntPtr>(gdk_wayland_surface_get_wl_surface(pSurface));
6097 #endif
6099 return 0;
6102 void GtkInstDragSource::set_datatransfer(const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
6103 const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener)
6105 m_xListener = rListener;
6106 m_xTrans = rTrans;
6109 void GtkInstDragSource::setActiveDragSource()
6111 // For LibreOffice internal D&D we provide the Transferable without Gtk
6112 // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
6113 g_ActiveDragSource = this;
6114 g_DropSuccessSet = false;
6115 g_DropSuccess = false;
6118 #if !GTK_CHECK_VERSION(4, 0, 0)
6119 std::vector<GtkTargetEntry> GtkInstDragSource::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
6121 return m_aConversionHelper.FormatsToGtk(rFormats);
6123 #endif
6125 void GtkInstDragSource::startDrag(const datatransfer::dnd::DragGestureEvent& rEvent,
6126 sal_Int8 sourceActions, sal_Int32 /*cursor*/, sal_Int32 /*image*/,
6127 const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
6128 const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener)
6130 set_datatransfer(rTrans, rListener);
6132 if (m_pFrame)
6134 setActiveDragSource();
6136 m_pFrame->startDrag(rEvent, rTrans, m_aConversionHelper ,VclToGdk(sourceActions));
6138 else
6139 dragFailed();
6142 void GtkSalFrame::startDrag(const css::datatransfer::dnd::DragGestureEvent& rEvent,
6143 const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
6144 VclToGtkHelper& rConversionHelper,
6145 GdkDragAction sourceActions)
6147 SolarMutexGuard aGuard;
6149 assert(m_pDragSource);
6151 #if GTK_CHECK_VERSION(4, 0, 0)
6153 GdkSeat *pSeat = gdk_display_get_default_seat(getGdkDisplay());
6154 GdkDrag* pDrag = gdk_drag_begin(widget_get_surface(getMouseEventWidget()),
6155 gdk_seat_get_pointer(pSeat),
6156 transerable_content_new(&rConversionHelper, rTrans.get()),
6157 sourceActions,
6158 rEvent.DragOriginX, rEvent.DragOriginY);
6160 g_signal_connect(G_OBJECT(pDrag), "drop-performed", G_CALLBACK(signalDragEnd), this);
6161 g_signal_connect(G_OBJECT(pDrag), "cancel", G_CALLBACK(signalDragFailed), this);
6162 g_signal_connect(G_OBJECT(pDrag), "dnd-finished", G_CALLBACK(signalDragDelete), this);
6164 #else
6166 auto aFormats = rTrans->getTransferDataFlavors();
6167 auto aGtkTargets = rConversionHelper.FormatsToGtk(aFormats);
6169 GtkTargetList *pTargetList = gtk_target_list_new(aGtkTargets.data(), aGtkTargets.size());
6171 gint nDragButton = 1; // default to left button
6172 css::awt::MouseEvent aEvent;
6173 if (rEvent.Event >>= aEvent)
6175 if (aEvent.Buttons & css::awt::MouseButton::LEFT )
6176 nDragButton = 1;
6177 else if (aEvent.Buttons & css::awt::MouseButton::RIGHT)
6178 nDragButton = 3;
6179 else if (aEvent.Buttons & css::awt::MouseButton::MIDDLE)
6180 nDragButton = 2;
6183 GdkEvent aFakeEvent;
6184 memset(&aFakeEvent, 0, sizeof(GdkEvent));
6185 aFakeEvent.type = GDK_BUTTON_PRESS;
6186 aFakeEvent.button.window = widget_get_surface(getMouseEventWidget());
6187 aFakeEvent.button.time = GDK_CURRENT_TIME;
6189 aFakeEvent.button.device = gtk_get_current_event_device();
6190 // if no current event to determine device, or (tdf#140272) the device will be unsuitable then find an
6191 // appropriate device to use.
6192 if (!aFakeEvent.button.device || !gdk_device_get_window_at_position(aFakeEvent.button.device, nullptr, nullptr))
6194 GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(getGdkDisplay());
6195 GList* pDevices = gdk_device_manager_list_devices(pDeviceManager, GDK_DEVICE_TYPE_MASTER);
6196 for (GList* pEntry = pDevices; pEntry; pEntry = pEntry->next)
6198 GdkDevice* pDevice = static_cast<GdkDevice*>(pEntry->data);
6199 if (gdk_device_get_source(pDevice) == GDK_SOURCE_KEYBOARD)
6200 continue;
6201 if (gdk_device_get_window_at_position(pDevice, nullptr, nullptr))
6203 aFakeEvent.button.device = pDevice;
6204 break;
6207 g_list_free(pDevices);
6210 GdkDragContext *pDrag;
6211 if (!aFakeEvent.button.device || !gdk_device_get_window_at_position(aFakeEvent.button.device, nullptr, nullptr))
6212 pDrag = nullptr;
6213 else
6214 pDrag = gtk_drag_begin_with_coordinates(getMouseEventWidget(),
6215 pTargetList,
6216 sourceActions,
6217 nDragButton,
6218 &aFakeEvent,
6219 rEvent.DragOriginX,
6220 rEvent.DragOriginY);
6222 gtk_target_list_unref(pTargetList);
6224 for (auto &a : aGtkTargets)
6225 g_free(a.target);
6226 #endif
6228 if (!pDrag)
6229 m_pDragSource->dragFailed();
6232 void GtkInstDragSource::dragFailed()
6234 if (m_xListener.is())
6236 datatransfer::dnd::DragSourceDropEvent aEv;
6237 aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_NONE;
6238 aEv.DropSuccess = false;
6239 auto xListener = m_xListener;
6240 m_xListener.clear();
6241 xListener->dragDropEnd(aEv);
6245 #if GTK_CHECK_VERSION(4, 0, 0)
6246 void GtkSalFrame::signalDragFailed(GdkDrag* /*drag*/, GdkDragCancelReason /*reason*/, gpointer frame)
6247 #else
6248 gboolean GtkSalFrame::signalDragFailed(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkDragResult /*result*/, gpointer frame)
6249 #endif
6251 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
6252 if (pThis->m_pDragSource)
6253 pThis->m_pDragSource->dragFailed();
6254 #if !GTK_CHECK_VERSION(4, 0, 0)
6255 return false;
6256 #endif
6259 void GtkInstDragSource::dragDelete()
6261 if (m_xListener.is())
6263 datatransfer::dnd::DragSourceDropEvent aEv;
6264 aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_MOVE;
6265 aEv.DropSuccess = true;
6266 auto xListener = m_xListener;
6267 m_xListener.clear();
6268 xListener->dragDropEnd(aEv);
6272 #if GTK_CHECK_VERSION(4, 0, 0)
6273 void GtkSalFrame::signalDragDelete(GdkDrag* /*context*/, gpointer frame)
6274 #else
6275 void GtkSalFrame::signalDragDelete(GtkWidget* /*widget*/, GdkDragContext* /*context*/, gpointer frame)
6276 #endif
6278 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
6279 if (!pThis->m_pDragSource)
6280 return;
6281 pThis->m_pDragSource->dragDelete();
6284 #if GTK_CHECK_VERSION(4, 0, 0)
6285 void GtkInstDragSource::dragEnd(GdkDrag* context)
6286 #else
6287 void GtkInstDragSource::dragEnd(GdkDragContext* context)
6288 #endif
6290 if (m_xListener.is())
6292 datatransfer::dnd::DragSourceDropEvent aEv;
6293 #if GTK_CHECK_VERSION(4, 0, 0)
6294 aEv.DropAction = GdkToVcl(gdk_drag_get_selected_action(context));
6295 #else
6296 aEv.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
6297 #endif
6298 // an internal drop can accept the drop but fail with dropComplete( false )
6299 // this is different than the GTK API
6300 if (g_DropSuccessSet)
6301 aEv.DropSuccess = g_DropSuccess;
6302 else
6303 aEv.DropSuccess = true;
6304 auto xListener = m_xListener;
6305 m_xListener.clear();
6306 xListener->dragDropEnd(aEv);
6308 g_ActiveDragSource = nullptr;
6311 #if GTK_CHECK_VERSION(4, 0, 0)
6312 void GtkSalFrame::signalDragEnd(GdkDrag* context, gpointer frame)
6313 #else
6314 void GtkSalFrame::signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer frame)
6315 #endif
6317 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
6318 if (!pThis->m_pDragSource)
6319 return;
6320 pThis->m_pDragSource->dragEnd(context);
6323 #if !GTK_CHECK_VERSION(4, 0, 0)
6324 void GtkInstDragSource::dragDataGet(GtkSelectionData *data, guint info)
6326 m_aConversionHelper.setSelectionData(m_xTrans, data, info);
6329 void GtkSalFrame::signalDragDataGet(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkSelectionData *data, guint info,
6330 guint /*time*/, gpointer frame)
6332 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
6333 if (!pThis->m_pDragSource)
6334 return;
6335 pThis->m_pDragSource->dragDataGet(data, info);
6337 #endif
6339 bool GtkSalFrame::CallCallbackExc(SalEvent nEvent, const void* pEvent) const
6341 SolarMutexGuard aGuard;
6342 bool nRet = false;
6345 nRet = CallCallback(nEvent, pEvent);
6347 catch (...)
6349 GetGtkSalData()->setException(std::current_exception());
6351 return nRet;
6354 #if !GTK_CHECK_VERSION(4, 0, 0)
6355 void GtkSalFrame::nopaint_container_resize_children(GtkContainer *pContainer)
6357 bool bOrigSalObjectSetPosSize = m_bSalObjectSetPosSize;
6358 m_bSalObjectSetPosSize = true;
6359 gtk_container_resize_children(pContainer);
6360 m_bSalObjectSetPosSize = bOrigSalObjectSetPosSize;
6362 #endif
6364 GdkEvent* GtkSalFrame::makeFakeKeyPress(GtkWidget* pWidget)
6366 #if !GTK_CHECK_VERSION(4, 0, 0)
6367 GdkEvent *event = gdk_event_new(GDK_KEY_PRESS);
6368 event->key.window = GDK_WINDOW(g_object_ref(widget_get_surface(pWidget)));
6370 GdkSeat *seat = gdk_display_get_default_seat(gtk_widget_get_display(pWidget));
6371 gdk_event_set_device(event, gdk_seat_get_keyboard(seat));
6373 event->key.send_event = 1 /* TRUE */;
6374 event->key.time = gtk_get_current_event_time();
6375 event->key.state = 0;
6376 event->key.keyval = 0;
6377 event->key.length = 0;
6378 event->key.string = nullptr;
6379 event->key.hardware_keycode = 0;
6380 event->key.group = 0;
6381 event->key.is_modifier = false;
6382 return event;
6383 #else
6384 (void)pWidget;
6385 return nullptr;
6386 #endif
6389 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */