nss: upgrade to release 3.73
[LibreOffice.git] / vcl / unx / gtk3 / gtk3gtkframe.cxx
blob8637acd77684ce8cbaeb9eb9e3667d01b0d97f45
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/keycodes.hxx>
28 #include <unx/geninst.h>
29 #include <headless/svpgdi.hxx>
30 #include <sal/log.hxx>
31 #include <tools/diagnose_ex.h>
32 #include <vcl/floatwin.hxx>
33 #include <vcl/svapp.hxx>
34 #include <vcl/weld.hxx>
35 #include <vcl/window.hxx>
36 #include <vcl/settings.hxx>
38 #include <gtk/gtk.h>
40 #include <X11/Xlib.h>
41 #include <X11/Xutil.h>
42 #include <unx/gtk/gtkbackend.hxx>
44 #include <window.h>
46 #include <basegfx/vector/b2ivector.hxx>
48 #include <dlfcn.h>
50 #include <algorithm>
52 #if OSL_DEBUG_LEVEL > 1
53 # include <cstdio>
54 #endif
56 #include <i18nlangtag/mslangid.hxx>
58 #include <cstdlib>
59 #include <cmath>
61 #include <com/sun/star/awt/MouseButton.hpp>
62 #include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
64 using namespace com::sun::star;
66 int GtkSalFrame::m_nFloats = 0;
68 static GDBusConnection* pSessionBus = nullptr;
70 sal_uInt16 GtkSalFrame::GetKeyModCode( guint state )
72 sal_uInt16 nCode = 0;
73 if( state & GDK_SHIFT_MASK )
74 nCode |= KEY_SHIFT;
75 if( state & GDK_CONTROL_MASK )
76 nCode |= KEY_MOD1;
77 if( state & GDK_MOD1_MASK )
78 nCode |= KEY_MOD2;
79 if( state & GDK_SUPER_MASK )
80 nCode |= KEY_MOD3;
81 return nCode;
84 sal_uInt16 GtkSalFrame::GetMouseModCode( guint state )
86 sal_uInt16 nCode = GetKeyModCode( state );
87 if( state & GDK_BUTTON1_MASK )
88 nCode |= MOUSE_LEFT;
89 if( state & GDK_BUTTON2_MASK )
90 nCode |= MOUSE_MIDDLE;
91 if( state & GDK_BUTTON3_MASK )
92 nCode |= MOUSE_RIGHT;
94 return nCode;
97 // KEY_F26 is the last function key known to keycodes.hxx
98 static bool IsFunctionKeyVal(guint keyval)
100 return keyval >= GDK_KEY_F1 && keyval <= GDK_KEY_F26;
103 sal_uInt16 GtkSalFrame::GetKeyCode(guint keyval)
105 sal_uInt16 nCode = 0;
106 if( keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9 )
107 nCode = KEY_0 + (keyval-GDK_KEY_0);
108 else if( keyval >= GDK_KEY_KP_0 && keyval <= GDK_KEY_KP_9 )
109 nCode = KEY_0 + (keyval-GDK_KEY_KP_0);
110 else if( keyval >= GDK_KEY_A && keyval <= GDK_KEY_Z )
111 nCode = KEY_A + (keyval-GDK_KEY_A );
112 else if( keyval >= GDK_KEY_a && keyval <= GDK_KEY_z )
113 nCode = KEY_A + (keyval-GDK_KEY_a );
114 else if (IsFunctionKeyVal(keyval))
116 switch( keyval )
118 // - - - - - Sun keyboard, see vcl/unx/source/app/saldisp.cxx
119 // although GDK_KEY_F1 ... GDK_KEY_L10 are known to
120 // gdk/gdkkeysyms.h, they are unlikely to be generated
121 // except possibly by Solaris systems
122 // this whole section needs review
123 case GDK_KEY_L2:
124 nCode = KEY_F12;
125 break;
126 case GDK_KEY_L3: nCode = KEY_PROPERTIES; break;
127 case GDK_KEY_L4: nCode = KEY_UNDO; break;
128 case GDK_KEY_L6: nCode = KEY_COPY; break; // KEY_F16
129 case GDK_KEY_L8: nCode = KEY_PASTE; break; // KEY_F18
130 case GDK_KEY_L10: nCode = KEY_CUT; break; // KEY_F20
131 default:
132 nCode = KEY_F1 + (keyval-GDK_KEY_F1); break;
135 else
137 switch( keyval )
139 case GDK_KEY_KP_Down:
140 case GDK_KEY_Down: nCode = KEY_DOWN; break;
141 case GDK_KEY_KP_Up:
142 case GDK_KEY_Up: nCode = KEY_UP; break;
143 case GDK_KEY_KP_Left:
144 case GDK_KEY_Left: nCode = KEY_LEFT; break;
145 case GDK_KEY_KP_Right:
146 case GDK_KEY_Right: nCode = KEY_RIGHT; break;
147 case GDK_KEY_KP_Begin:
148 case GDK_KEY_KP_Home:
149 case GDK_KEY_Begin:
150 case GDK_KEY_Home: nCode = KEY_HOME; break;
151 case GDK_KEY_KP_End:
152 case GDK_KEY_End: nCode = KEY_END; break;
153 case GDK_KEY_KP_Page_Up:
154 case GDK_KEY_Page_Up: nCode = KEY_PAGEUP; break;
155 case GDK_KEY_KP_Page_Down:
156 case GDK_KEY_Page_Down: nCode = KEY_PAGEDOWN; break;
157 case GDK_KEY_KP_Enter:
158 case GDK_KEY_Return: nCode = KEY_RETURN; break;
159 case GDK_KEY_Escape: nCode = KEY_ESCAPE; break;
160 case GDK_KEY_ISO_Left_Tab:
161 case GDK_KEY_KP_Tab:
162 case GDK_KEY_Tab: nCode = KEY_TAB; break;
163 case GDK_KEY_BackSpace: nCode = KEY_BACKSPACE; break;
164 case GDK_KEY_KP_Space:
165 case GDK_KEY_space: nCode = KEY_SPACE; break;
166 case GDK_KEY_KP_Insert:
167 case GDK_KEY_Insert: nCode = KEY_INSERT; break;
168 case GDK_KEY_KP_Delete:
169 case GDK_KEY_Delete: nCode = KEY_DELETE; break;
170 case GDK_KEY_plus:
171 case GDK_KEY_KP_Add: nCode = KEY_ADD; break;
172 case GDK_KEY_minus:
173 case GDK_KEY_KP_Subtract: nCode = KEY_SUBTRACT; break;
174 case GDK_KEY_asterisk:
175 case GDK_KEY_KP_Multiply: nCode = KEY_MULTIPLY; break;
176 case GDK_KEY_slash:
177 case GDK_KEY_KP_Divide: nCode = KEY_DIVIDE; break;
178 case GDK_KEY_period: nCode = KEY_POINT; break;
179 case GDK_KEY_decimalpoint: nCode = KEY_POINT; break;
180 case GDK_KEY_comma: nCode = KEY_COMMA; break;
181 case GDK_KEY_less: nCode = KEY_LESS; break;
182 case GDK_KEY_greater: nCode = KEY_GREATER; break;
183 case GDK_KEY_KP_Equal:
184 case GDK_KEY_equal: nCode = KEY_EQUAL; break;
185 case GDK_KEY_Find: nCode = KEY_FIND; break;
186 case GDK_KEY_Menu: nCode = KEY_CONTEXTMENU;break;
187 case GDK_KEY_Help: nCode = KEY_HELP; break;
188 case GDK_KEY_Undo: nCode = KEY_UNDO; break;
189 case GDK_KEY_Redo: nCode = KEY_REPEAT; break;
190 // on a sun keyboard this actually is usually SunXK_Stop = 0x0000FF69 (XK_Cancel),
191 // but VCL doesn't have a key definition for that
192 case GDK_KEY_Cancel: nCode = KEY_F11; break;
193 case GDK_KEY_KP_Decimal:
194 case GDK_KEY_KP_Separator: nCode = KEY_DECIMAL; break;
195 case GDK_KEY_asciitilde: nCode = KEY_TILDE; break;
196 case GDK_KEY_leftsinglequotemark:
197 case GDK_KEY_quoteleft: nCode = KEY_QUOTELEFT; break;
198 case GDK_KEY_bracketleft: nCode = KEY_BRACKETLEFT; break;
199 case GDK_KEY_bracketright: nCode = KEY_BRACKETRIGHT; break;
200 case GDK_KEY_semicolon: nCode = KEY_SEMICOLON; break;
201 case GDK_KEY_quoteright: nCode = KEY_QUOTERIGHT; break;
202 // some special cases, also see saldisp.cxx
203 // - - - - - - - - - - - - - Apollo - - - - - - - - - - - - - 0x1000
204 // These can be found in ap_keysym.h
205 case 0x1000FF02: // apXK_Copy
206 nCode = KEY_COPY;
207 break;
208 case 0x1000FF03: // apXK_Cut
209 nCode = KEY_CUT;
210 break;
211 case 0x1000FF04: // apXK_Paste
212 nCode = KEY_PASTE;
213 break;
214 case 0x1000FF14: // apXK_Repeat
215 nCode = KEY_REPEAT;
216 break;
217 // Exit, Save
218 // - - - - - - - - - - - - - - D E C - - - - - - - - - - - - - 0x1000
219 // These can be found in DECkeysym.h
220 case 0x1000FF00:
221 nCode = KEY_DELETE;
222 break;
223 // - - - - - - - - - - - - - - H P - - - - - - - - - - - - - 0x1000
224 // These can be found in HPkeysym.h
225 case 0x1000FF73: // hpXK_DeleteChar
226 nCode = KEY_DELETE;
227 break;
228 case 0x1000FF74: // hpXK_BackTab
229 case 0x1000FF75: // hpXK_KP_BackTab
230 nCode = KEY_TAB;
231 break;
232 // - - - - - - - - - - - - - - I B M - - - - - - - - - - - - -
233 // - - - - - - - - - - - - - - O S F - - - - - - - - - - - - - 0x1004
234 // These also can be found in HPkeysym.h
235 case 0x1004FF02: // osfXK_Copy
236 nCode = KEY_COPY;
237 break;
238 case 0x1004FF03: // osfXK_Cut
239 nCode = KEY_CUT;
240 break;
241 case 0x1004FF04: // osfXK_Paste
242 nCode = KEY_PASTE;
243 break;
244 case 0x1004FF07: // osfXK_BackTab
245 nCode = KEY_TAB;
246 break;
247 case 0x1004FF08: // osfXK_BackSpace
248 nCode = KEY_BACKSPACE;
249 break;
250 case 0x1004FF1B: // osfXK_Escape
251 nCode = KEY_ESCAPE;
252 break;
253 // Up, Down, Left, Right, PageUp, PageDown
254 // - - - - - - - - - - - - - - S C O - - - - - - - - - - - - -
255 // - - - - - - - - - - - - - - S G I - - - - - - - - - - - - - 0x1007
256 // - - - - - - - - - - - - - - S N I - - - - - - - - - - - - -
257 // - - - - - - - - - - - - - - S U N - - - - - - - - - - - - - 0x1005
258 // These can be found in Sunkeysym.h
259 case 0x1005FF10: // SunXK_F36
260 nCode = KEY_F11;
261 break;
262 case 0x1005FF11: // SunXK_F37
263 nCode = KEY_F12;
264 break;
265 case 0x1005FF70: // SunXK_Props
266 nCode = KEY_PROPERTIES;
267 break;
268 case 0x1005FF71: // SunXK_Front
269 nCode = KEY_FRONT;
270 break;
271 case 0x1005FF72: // SunXK_Copy
272 nCode = KEY_COPY;
273 break;
274 case 0x1005FF73: // SunXK_Open
275 nCode = KEY_OPEN;
276 break;
277 case 0x1005FF74: // SunXK_Paste
278 nCode = KEY_PASTE;
279 break;
280 case 0x1005FF75: // SunXK_Cut
281 nCode = KEY_CUT;
282 break;
283 // - - - - - - - - - - - - - X F 8 6 - - - - - - - - - - - - - 0x1008
284 // These can be found in XF86keysym.h
285 // but more importantly they are also available GTK/Gdk version 3
286 // and hence are already provided in gdk/gdkkeysyms.h, and hence
287 // in gdk/gdk.h
288 case GDK_KEY_Copy: nCode = KEY_COPY; break; // 0x1008ff57
289 case GDK_KEY_Cut: nCode = KEY_CUT; break; // 0x1008ff58
290 case GDK_KEY_Open: nCode = KEY_OPEN; break; // 0x1008ff6b
291 case GDK_KEY_Paste: nCode = KEY_PASTE; break; // 0x1008ff6d
295 return nCode;
298 guint GtkSalFrame::GetKeyValFor(GdkKeymap* pKeyMap, guint16 hardware_keycode, guint8 group)
300 guint updated_keyval = 0;
301 gdk_keymap_translate_keyboard_state(pKeyMap, hardware_keycode,
302 GdkModifierType(0), group, &updated_keyval, nullptr, nullptr, nullptr);
303 return updated_keyval;
306 namespace {
308 // F10 means either KEY_F10 or KEY_MENU, which has to be decided
309 // in the independent part.
310 struct KeyAlternate
312 sal_uInt16 nKeyCode;
313 sal_Unicode nCharCode;
314 KeyAlternate() : nKeyCode( 0 ), nCharCode( 0 ) {}
315 KeyAlternate( sal_uInt16 nKey, sal_Unicode nChar = 0 ) : nKeyCode( nKey ), nCharCode( nChar ) {}
320 static KeyAlternate
321 GetAlternateKeyCode( const sal_uInt16 nKeyCode )
323 KeyAlternate aAlternate;
325 switch( nKeyCode )
327 case KEY_F10: aAlternate = KeyAlternate( KEY_MENU );break;
328 case KEY_F24: aAlternate = KeyAlternate( KEY_SUBTRACT, '-' );break;
331 return aAlternate;
334 #if OSL_DEBUG_LEVEL > 0
335 static bool dumpframes = false;
336 #endif
338 bool GtkSalFrame::doKeyCallback( guint state,
339 guint keyval,
340 guint16 hardware_keycode,
341 guint8 group,
342 sal_Unicode aOrigCode,
343 bool bDown,
344 bool bSendRelease
347 SalKeyEvent aEvent;
349 aEvent.mnCharCode = aOrigCode;
350 aEvent.mnRepeat = 0;
352 vcl::DeletionListener aDel( this );
354 #if OSL_DEBUG_LEVEL > 0
355 const char* pKeyDebug = getenv("VCL_GTK3_PAINTDEBUG");
357 if (pKeyDebug && *pKeyDebug == '1')
359 if (bDown)
361 // shift-zero forces a re-draw and event is swallowed
362 if (keyval == GDK_KEY_0)
364 SAL_INFO("vcl.gtk3", "force widget_queue_draw.");
365 gtk_widget_queue_draw(GTK_WIDGET(m_pFixedContainer));
366 return false;
368 else if (keyval == GDK_KEY_1)
370 SAL_INFO("vcl.gtk3", "force repaint all.");
371 TriggerPaintEvent();
372 return false;
374 else if (keyval == GDK_KEY_2)
376 dumpframes = !dumpframes;
377 SAL_INFO("vcl.gtk3", "toggle dump frames to " << dumpframes);
378 return false;
382 #endif
385 * #i42122# translate all keys with Ctrl and/or Alt to group 0 else
386 * shortcuts (e.g. Ctrl-o) will not work but be inserted by the
387 * application
389 * #i52338# do this for all keys that the independent part has no key code
390 * for
392 * fdo#41169 rather than use group 0, detect if there is a group which can
393 * be used to input Latin text and use that if possible
395 aEvent.mnCode = GetKeyCode( keyval );
396 if( aEvent.mnCode == 0 )
398 gint best_group = SAL_MAX_INT32;
400 // Try and find Latin layout
401 GdkKeymap* keymap = gdk_keymap_get_default();
402 GdkKeymapKey *keys;
403 gint n_keys;
404 if (gdk_keymap_get_entries_for_keyval(keymap, GDK_KEY_A, &keys, &n_keys))
406 // Find the lowest group that supports Latin layout
407 for (gint i = 0; i < n_keys; ++i)
409 if (keys[i].level != 0 && keys[i].level != 1)
410 continue;
411 best_group = std::min(best_group, keys[i].group);
412 if (best_group == 0)
413 break;
415 g_free(keys);
418 //Unavailable, go with original group then I suppose
419 if (best_group == SAL_MAX_INT32)
420 best_group = group;
422 guint updated_keyval = GetKeyValFor(keymap, hardware_keycode, best_group);
423 aEvent.mnCode = GetKeyCode(updated_keyval);
426 aEvent.mnCode |= GetKeyModCode( state );
428 bool bStopProcessingKey;
429 if (bDown)
431 bStopProcessingKey = CallCallbackExc(SalEvent::KeyInput, &aEvent);
432 // #i46889# copy AlternateKeyCode handling from generic plugin
433 if (!bStopProcessingKey)
435 KeyAlternate aAlternate = GetAlternateKeyCode( aEvent.mnCode );
436 if( aAlternate.nKeyCode )
438 aEvent.mnCode = aAlternate.nKeyCode;
439 if( aAlternate.nCharCode )
440 aEvent.mnCharCode = aAlternate.nCharCode;
441 bStopProcessingKey = CallCallbackExc(SalEvent::KeyInput, &aEvent);
444 if( bSendRelease && ! aDel.isDeleted() )
446 CallCallbackExc(SalEvent::KeyUp, &aEvent);
449 else
450 bStopProcessingKey = CallCallbackExc(SalEvent::KeyUp, &aEvent);
451 return bStopProcessingKey;
454 GtkSalFrame::GtkSalFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
455 : m_nXScreen( getDisplay()->GetDefaultXScreen() )
456 , m_pHeaderBar(nullptr)
457 , m_bGraphics(false)
458 , m_nSetFocusSignalId(0)
460 getDisplay()->registerFrame( this );
461 m_bDefaultPos = true;
462 m_bDefaultSize = ( (nStyle & SalFrameStyleFlags::SIZEABLE) && ! pParent );
463 Init( pParent, nStyle );
466 GtkSalFrame::GtkSalFrame( SystemParentData* pSysData )
467 : m_nXScreen( getDisplay()->GetDefaultXScreen() )
468 , m_pHeaderBar(nullptr)
469 , m_bGraphics(false)
470 , m_nSetFocusSignalId(0)
472 getDisplay()->registerFrame( this );
473 // permanently ignore errors from our unruly children ...
474 GetGenericUnixSalData()->ErrorTrapPush();
475 m_bDefaultPos = true;
476 m_bDefaultSize = true;
477 Init( pSysData );
480 // AppMenu watch functions.
482 static void ObjectDestroyedNotify( gpointer data )
484 if ( data ) {
485 g_object_unref( data );
489 static void hud_activated( gboolean hud_active, gpointer user_data )
491 if ( hud_active )
493 SolarMutexGuard aGuard;
494 GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
495 GtkSalMenu* pSalMenu = reinterpret_cast< GtkSalMenu* >( pSalFrame->GetMenu() );
497 if ( pSalMenu )
498 pSalMenu->UpdateFull();
502 static bool ensure_dbus_setup( gpointer data )
504 GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( data );
505 GdkWindow* gdkWindow = gtk_widget_get_window( pSalFrame->getWindow() );
507 if ( gdkWindow != nullptr && g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) == nullptr )
509 // Get a DBus session connection.
510 if(!pSessionBus)
511 pSessionBus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, nullptr);
512 if( !pSessionBus )
514 return false;
517 // Create menu model and action group attached to this frame.
518 GMenuModel* pMenuModel = G_MENU_MODEL( g_lo_menu_new() );
519 GActionGroup* pActionGroup = reinterpret_cast<GActionGroup*>(g_lo_action_group_new());
521 // Generate menu paths.
522 sal_uIntPtr windowId = pSalFrame->GetNativeWindowHandle(pSalFrame->getWindow());
523 gchar* aDBusWindowPath = g_strdup_printf( "/org/libreoffice/window/%lu", windowId );
524 gchar* aDBusMenubarPath = g_strdup_printf( "/org/libreoffice/window/%lu/menus/menubar", windowId );
526 // Set window properties.
527 g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-menubar", pMenuModel, ObjectDestroyedNotify );
528 g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-action-group", pActionGroup, ObjectDestroyedNotify );
530 GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
531 #if defined(GDK_WINDOWING_X11)
532 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
534 gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_ID", "org.libreoffice" );
535 gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_MENUBAR_OBJECT_PATH", aDBusMenubarPath );
536 gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_WINDOW_OBJECT_PATH", aDBusWindowPath );
537 gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_OBJECT_PATH", "/org/libreoffice" );
538 gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_UNIQUE_BUS_NAME", g_dbus_connection_get_unique_name( pSessionBus ) );
540 #endif
541 #if defined(GDK_WINDOWING_WAYLAND)
542 if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
544 gdk_wayland_window_set_dbus_properties_libgtk_only(gdkWindow, "org.libreoffice",
545 nullptr,
546 aDBusMenubarPath,
547 aDBusWindowPath,
548 "/org/libreoffice",
549 g_dbus_connection_get_unique_name( pSessionBus ));
551 #endif
552 // Publish the menu model and the action group.
553 SAL_INFO("vcl.unity", "exporting menu model at " << pMenuModel << " for window " << windowId);
554 pSalFrame->m_nMenuExportId = g_dbus_connection_export_menu_model (pSessionBus, aDBusMenubarPath, pMenuModel, nullptr);
555 SAL_INFO("vcl.unity", "exporting action group at " << pActionGroup << " for window " << windowId);
556 pSalFrame->m_nActionGroupExportId = g_dbus_connection_export_action_group( pSessionBus, aDBusWindowPath, pActionGroup, nullptr);
557 pSalFrame->m_nHudAwarenessId = hud_awareness_register( pSessionBus, aDBusMenubarPath, hud_activated, pSalFrame, nullptr, nullptr );
559 g_free( aDBusWindowPath );
560 g_free( aDBusMenubarPath );
563 return false;
566 void on_registrar_available( GDBusConnection * /*connection*/,
567 const gchar * /*name*/,
568 const gchar * /*name_owner*/,
569 gpointer user_data )
571 SolarMutexGuard aGuard;
573 GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
575 SalMenu* pSalMenu = pSalFrame->GetMenu();
577 if ( pSalMenu != nullptr )
579 GtkSalMenu* pGtkSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
580 pGtkSalMenu->EnableUnity(true);
584 // This is called when the registrar becomes unavailable. It shows the menubar.
585 void on_registrar_unavailable( GDBusConnection * /*connection*/,
586 const gchar * /*name*/,
587 gpointer user_data )
589 SolarMutexGuard aGuard;
591 SAL_INFO("vcl.unity", "on_registrar_unavailable");
593 //pSessionBus = NULL;
594 GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
596 SalMenu* pSalMenu = pSalFrame->GetMenu();
598 if ( pSalMenu ) {
599 GtkSalMenu* pGtkSalMenu = static_cast< GtkSalMenu* >( pSalMenu );
600 pGtkSalMenu->EnableUnity(false);
604 void GtkSalFrame::EnsureAppMenuWatch()
606 if ( m_nWatcherId )
607 return;
609 // Get a DBus session connection.
610 if ( pSessionBus == nullptr )
612 pSessionBus = g_bus_get_sync( G_BUS_TYPE_SESSION, nullptr, nullptr );
614 if ( pSessionBus == nullptr )
615 return;
618 // Publish the menu only if AppMenu registrar is available.
619 m_nWatcherId = g_bus_watch_name_on_connection( pSessionBus,
620 "com.canonical.AppMenu.Registrar",
621 G_BUS_NAME_WATCHER_FLAGS_NONE,
622 on_registrar_available,
623 on_registrar_unavailable,
624 this,
625 nullptr );
628 void GtkSalFrame::InvalidateGraphics()
630 if( m_pGraphics )
632 m_bGraphics = false;
636 GtkSalFrame::~GtkSalFrame()
638 m_aSmoothScrollIdle.Stop();
639 m_aSmoothScrollIdle.ClearInvokeHandler();
641 if (m_pDropTarget)
643 m_pDropTarget->deinitialize();
644 m_pDropTarget = nullptr;
647 if (m_pDragSource)
649 m_pDragSource->deinitialize();
650 m_pDragSource= nullptr;
653 InvalidateGraphics();
655 if (m_pParent)
657 m_pParent->m_aChildren.remove( this );
660 getDisplay()->deregisterFrame( this );
662 if( m_pRegion )
664 cairo_region_destroy( m_pRegion );
667 m_pIMHandler.reset();
669 //tdf#108705 remove grabs on event widget before
670 //destroying event widget
671 while (m_nGrabLevel)
672 removeGrabLevel();
674 GtkWidget *pEventWidget = getMouseEventWidget();
675 for (auto handler_id : m_aMouseSignalIds)
676 g_signal_handler_disconnect(G_OBJECT(pEventWidget), handler_id);
677 if( m_pFixedContainer )
678 gtk_widget_destroy( GTK_WIDGET( m_pFixedContainer ) );
679 if( m_pEventBox )
680 gtk_widget_destroy( GTK_WIDGET(m_pEventBox) );
681 if( m_pTopLevelGrid )
682 gtk_widget_destroy( GTK_WIDGET(m_pTopLevelGrid) );
684 SolarMutexGuard aGuard;
686 if(m_nWatcherId)
687 g_bus_unwatch_name(m_nWatcherId);
689 if( m_pWindow )
691 g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", nullptr );
693 if ( pSessionBus )
695 if ( m_nHudAwarenessId )
696 hud_awareness_unregister( pSessionBus, m_nHudAwarenessId );
697 if ( m_nMenuExportId )
698 g_dbus_connection_unexport_menu_model( pSessionBus, m_nMenuExportId );
699 if ( m_nActionGroupExportId )
700 g_dbus_connection_unexport_action_group( pSessionBus, m_nActionGroupExportId );
702 gtk_widget_destroy( m_pWindow );
705 if( m_pForeignParent )
706 g_object_unref( G_OBJECT( m_pForeignParent ) );
707 if( m_pForeignTopLevel )
708 g_object_unref( G_OBJECT( m_pForeignTopLevel) );
710 m_pGraphics.reset();
712 if (m_pSurface)
713 cairo_surface_destroy(m_pSurface);
716 void GtkSalFrame::moveWindow( tools::Long nX, tools::Long nY )
718 if( isChild( false ) )
720 GtkWidget* pParent = m_pParent ? gtk_widget_get_parent(m_pWindow) : nullptr;
721 // tdf#130414 it's possible that we were reparented and are no longer inside
722 // our original GtkFixed parent
723 if (pParent && GTK_IS_FIXED(pParent))
725 gtk_fixed_move( GTK_FIXED(pParent),
726 m_pWindow,
727 nX - m_pParent->maGeometry.nX, nY - m_pParent->maGeometry.nY );
730 else
731 gtk_window_move( GTK_WINDOW(m_pWindow), nX, nY );
734 void GtkSalFrame::widget_set_size_request(tools::Long nWidth, tools::Long nHeight)
736 gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), nWidth, nHeight );
739 void GtkSalFrame::window_resize(tools::Long nWidth, tools::Long nHeight)
741 m_nWidthRequest = nWidth;
742 m_nHeightRequest = nHeight;
743 gtk_window_set_default_size(GTK_WINDOW(m_pWindow), nWidth, nHeight);
744 if (gtk_widget_get_visible(m_pWindow))
745 gtk_window_resize(GTK_WINDOW(m_pWindow), nWidth, nHeight);
748 void GtkSalFrame::resizeWindow( tools::Long nWidth, tools::Long nHeight )
750 if( isChild( false ) )
752 widget_set_size_request(nWidth, nHeight);
754 else if( ! isChild( true, false ) )
755 window_resize(nWidth, nHeight);
758 // tdf#124694 GtkFixed takes the max size of all its children as its
759 // preferred size, causing it to not clip its child, but grow instead.
761 static void
762 ooo_fixed_get_preferred_height(GtkWidget*, gint *minimum, gint *natural)
764 *minimum = 0;
765 *natural = 0;
768 static void
769 ooo_fixed_get_preferred_width(GtkWidget*, gint *minimum, gint *natural)
771 *minimum = 0;
772 *natural = 0;
775 static void
776 ooo_fixed_class_init(GtkFixedClass *klass)
778 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
779 widget_class->get_accessible = ooo_fixed_get_accessible;
780 widget_class->get_preferred_height = ooo_fixed_get_preferred_height;
781 widget_class->get_preferred_width = ooo_fixed_get_preferred_width;
785 * Always use a sub-class of GtkFixed we can tag for a11y. This allows us to
786 * utilize GAIL for the toplevel window and toolkit implementation incl.
787 * key event listener support ..
790 GType
791 ooo_fixed_get_type()
793 static GType type = 0;
795 if (!type) {
796 static const GTypeInfo tinfo =
798 sizeof (GtkFixedClass),
799 nullptr, /* base init */
800 nullptr, /* base finalize */
801 reinterpret_cast<GClassInitFunc>(ooo_fixed_class_init), /* class init */
802 nullptr, /* class finalize */
803 nullptr, /* class data */
804 sizeof (GtkFixed), /* instance size */
805 0, /* nb preallocs */
806 nullptr, /* instance init */
807 nullptr /* value table */
810 type = g_type_register_static( GTK_TYPE_FIXED, "OOoFixed",
811 &tinfo, GTypeFlags(0));
814 return type;
817 void GtkSalFrame::updateScreenNumber()
819 int nScreen = 0;
820 GdkScreen *pScreen = gtk_widget_get_screen( m_pWindow );
821 if( pScreen )
822 nScreen = getDisplay()->getSystem()->getScreenMonitorIdx( pScreen, maGeometry.nX, maGeometry.nY );
823 maGeometry.nDisplayScreenNumber = nScreen;
826 GtkWidget *GtkSalFrame::getMouseEventWidget() const
828 return GTK_WIDGET(m_pEventBox);
831 static void damaged(void *handle,
832 sal_Int32 nExtentsX, sal_Int32 nExtentsY,
833 sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight)
835 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(handle);
836 pThis->damaged(nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight);
839 void GtkSalFrame::InitCommon()
841 m_pSurface = nullptr;
842 m_nGrabLevel = 0;
843 m_bSalObjectSetPosSize = false;
845 m_aDamageHandler.handle = this;
846 m_aDamageHandler.damaged = ::damaged;
848 m_aSmoothScrollIdle.SetInvokeHandler(LINK(this, GtkSalFrame, AsyncScroll));
850 m_pTopLevelGrid = GTK_GRID(gtk_grid_new());
851 gtk_container_add(GTK_CONTAINER(m_pWindow), GTK_WIDGET(m_pTopLevelGrid));
853 m_pEventBox = GTK_EVENT_BOX(gtk_event_box_new());
854 gtk_widget_add_events( GTK_WIDGET(m_pEventBox),
855 GDK_ALL_EVENTS_MASK );
856 gtk_widget_set_vexpand(GTK_WIDGET(m_pEventBox), true);
857 gtk_widget_set_hexpand(GTK_WIDGET(m_pEventBox), true);
858 gtk_grid_attach(m_pTopLevelGrid, GTK_WIDGET(m_pEventBox), 0, 0, 1, 1);
860 // add the fixed container child,
861 // fixed is needed since we have to position plugin windows
862 m_pFixedContainer = GTK_FIXED(g_object_new( ooo_fixed_get_type(), nullptr ));
863 gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), true);
864 gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), 1, 1);
865 gtk_container_add( GTK_CONTAINER(m_pEventBox), GTK_WIDGET(m_pFixedContainer) );
867 GtkWidget *pEventWidget = getMouseEventWidget();
869 gtk_widget_set_app_paintable(GTK_WIDGET(m_pFixedContainer), true);
870 gtk_widget_set_redraw_on_allocate(GTK_WIDGET(m_pFixedContainer), false);
872 // connect signals
873 // use pEventWidget instead of m_pWindow to avoid infinite event loop under Linux Mint Mate 18.3
874 g_signal_connect( G_OBJECT(pEventWidget), "style-updated", G_CALLBACK(signalStyleUpdated), this );
875 gtk_widget_set_has_tooltip(pEventWidget, true);
876 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "query-tooltip", G_CALLBACK(signalTooltipQuery), this ));
877 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-press-event", G_CALLBACK(signalButton), this ));
878 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "motion-notify-event", G_CALLBACK(signalMotion), this ));
879 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-release-event", G_CALLBACK(signalButton), this ));
880 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "leave-notify-event", G_CALLBACK(signalCrossing), this ));
881 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "enter-notify-event", G_CALLBACK(signalCrossing), this ));
884 //Drop Target Stuff
885 gtk_drag_dest_set(GTK_WIDGET(pEventWidget), GtkDestDefaults(0), nullptr, 0, GdkDragAction(0));
886 gtk_drag_dest_set_track_motion(GTK_WIDGET(pEventWidget), true);
887 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-motion", G_CALLBACK(signalDragMotion), this ));
888 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-drop", G_CALLBACK(signalDragDrop), this ));
889 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-received", G_CALLBACK(signalDragDropReceived), this ));
890 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-leave", G_CALLBACK(signalDragLeave), this ));
892 //Drag Source Stuff
893 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-end", G_CALLBACK(signalDragEnd), this ));
894 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-failed", G_CALLBACK(signalDragFailed), this ));
895 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-delete", G_CALLBACK(signalDragDelete), this ));
896 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-get", G_CALLBACK(signalDragDataGet), this ));
897 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "scroll-event", G_CALLBACK(signalScroll), this ));
899 g_signal_connect( G_OBJECT(m_pFixedContainer), "draw", G_CALLBACK(signalDraw), this );
900 g_signal_connect( G_OBJECT(m_pFixedContainer), "realize", G_CALLBACK(signalRealize), this );
901 g_signal_connect( G_OBJECT(m_pFixedContainer), "size-allocate", G_CALLBACK(sizeAllocated), this );
903 GtkGesture *pSwipe = gtk_gesture_swipe_new(pEventWidget);
904 g_signal_connect(pSwipe, "swipe", G_CALLBACK(gestureSwipe), this);
905 gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pSwipe), GTK_PHASE_TARGET);
906 g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pSwipe);
908 GtkGesture *pLongPress = gtk_gesture_long_press_new(pEventWidget);
909 g_signal_connect(pLongPress, "pressed", G_CALLBACK(gestureLongPress), this);
910 gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pLongPress), GTK_PHASE_TARGET);
911 g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pLongPress);
913 g_signal_connect_after( G_OBJECT(m_pWindow), "focus-in-event", G_CALLBACK(signalFocus), this );
914 g_signal_connect_after( G_OBJECT(m_pWindow), "focus-out-event", G_CALLBACK(signalFocus), this );
915 if (GTK_IS_WINDOW(m_pWindow)) // i.e. not if it's a GtkEventBox which doesn't have the signal
916 m_nSetFocusSignalId = g_signal_connect( G_OBJECT(m_pWindow), "set-focus", G_CALLBACK(signalSetFocus), this );
917 g_signal_connect( G_OBJECT(m_pWindow), "map-event", G_CALLBACK(signalMap), this );
918 g_signal_connect( G_OBJECT(m_pWindow), "unmap-event", G_CALLBACK(signalUnmap), this );
919 g_signal_connect( G_OBJECT(m_pWindow), "configure-event", G_CALLBACK(signalConfigure), this );
920 g_signal_connect( G_OBJECT(m_pWindow), "key-press-event", G_CALLBACK(signalKey), this );
921 g_signal_connect( G_OBJECT(m_pWindow), "key-release-event", G_CALLBACK(signalKey), this );
922 g_signal_connect( G_OBJECT(m_pWindow), "delete-event", G_CALLBACK(signalDelete), this );
923 g_signal_connect( G_OBJECT(m_pWindow), "window-state-event", G_CALLBACK(signalWindowState), this );
924 g_signal_connect( G_OBJECT(m_pWindow), "visibility-notify-event", G_CALLBACK(signalVisibility), this );
925 g_signal_connect( G_OBJECT(m_pWindow), "destroy", G_CALLBACK(signalDestroy), this );
927 // init members
928 m_nKeyModifiers = ModKeyFlags::NONE;
929 m_bFullscreen = false;
930 m_bSpanMonitorsWhenFullscreen = false;
931 m_nState = GDK_WINDOW_STATE_WITHDRAWN;
932 m_pIMHandler = nullptr;
933 m_pRegion = nullptr;
934 m_pDropTarget = nullptr;
935 m_pDragSource = nullptr;
936 m_bGeometryIsProvisional = false;
937 m_bIconSetWhileUnmapped = false;
938 m_bTooltipBlocked = false;
939 m_ePointerStyle = static_cast<PointerStyle>(0xffff);
940 m_pSalMenu = nullptr;
941 m_nWatcherId = 0;
942 m_nMenuExportId = 0;
943 m_nActionGroupExportId = 0;
944 m_nHudAwarenessId = 0;
946 gtk_widget_add_events( m_pWindow,
947 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
948 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
949 GDK_VISIBILITY_NOTIFY_MASK | GDK_SCROLL_MASK
952 // show the widgets
953 gtk_widget_show_all(GTK_WIDGET(m_pTopLevelGrid));
955 // realize the window, we need an XWindow id
956 gtk_widget_realize( m_pWindow );
958 //system data
959 m_aSystemData.SetWindowHandle(GetNativeWindowHandle(m_pWindow));
960 m_aSystemData.aShellWindow = reinterpret_cast<sal_IntPtr>(this);
961 m_aSystemData.pSalFrame = this;
962 m_aSystemData.pWidget = m_pWindow;
963 m_aSystemData.nScreen = m_nXScreen.getXScreen();
964 m_aSystemData.toolkit = SystemEnvData::Toolkit::Gtk3;
966 #if defined(GDK_WINDOWING_X11)
967 GdkDisplay *pDisplay = getGdkDisplay();
968 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
970 m_aSystemData.pDisplay = gdk_x11_display_get_xdisplay(pDisplay);
971 m_aSystemData.platform = SystemEnvData::Platform::Xcb;
972 GdkScreen* pScreen = gtk_widget_get_screen(m_pWindow);
973 GdkVisual* pVisual = gdk_screen_get_system_visual(pScreen);
974 m_aSystemData.pVisual = gdk_x11_visual_get_xvisual(pVisual);
976 #endif
977 #if defined(GDK_WINDOWING_WAYLAND)
978 if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
980 m_aSystemData.pDisplay = gdk_wayland_display_get_wl_display(pDisplay);
981 m_aSystemData.platform = SystemEnvData::Platform::Wayland;
983 #endif
985 m_bGraphics = false;
986 m_pGraphics = nullptr;
988 m_nFloatFlags = FloatWinPopupFlags::NONE;
989 m_bFloatPositioned = false;
991 m_nWidthRequest = 0;
992 m_nHeightRequest = 0;
994 // fake an initial geometry, gets updated via configure event or SetPosSize
995 if (m_bDefaultPos || m_bDefaultSize)
997 Size aDefSize = calcDefaultSize();
998 maGeometry.nX = -1;
999 maGeometry.nY = -1;
1000 maGeometry.nWidth = aDefSize.Width();
1001 maGeometry.nHeight = aDefSize.Height();
1002 maGeometry.nTopDecoration = 0;
1003 maGeometry.nBottomDecoration = 0;
1004 maGeometry.nLeftDecoration = 0;
1005 maGeometry.nRightDecoration = 0;
1007 updateScreenNumber();
1009 SetIcon(SV_ICON_ID_OFFICE);
1012 GtkSalFrame *GtkSalFrame::getFromWindow( GtkWidget *pWindow )
1014 return static_cast<GtkSalFrame *>(g_object_get_data( G_OBJECT( pWindow ), "SalFrame" ));
1017 void GtkSalFrame::DisallowCycleFocusOut()
1019 if (!m_nSetFocusSignalId)
1020 return;
1021 // don't enable/disable can-focus as control enters and leaves
1022 // embedded native gtk widgets
1023 g_signal_handler_disconnect(G_OBJECT(m_pWindow), m_nSetFocusSignalId);
1024 m_nSetFocusSignalId = 0;
1026 // set container without can-focus and focus will tab between
1027 // the native embedded widgets using the default gtk handling for
1028 // that
1029 gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), false);
1032 bool GtkSalFrame::IsCycleFocusOutDisallowed() const
1034 return m_nSetFocusSignalId == 0;
1037 void GtkSalFrame::AllowCycleFocusOut()
1039 if (m_nSetFocusSignalId)
1040 return;
1041 // enable/disable can-focus as control enters and leaves
1042 // embedded native gtk widgets
1043 m_nSetFocusSignalId = g_signal_connect(G_OBJECT(m_pWindow), "set-focus", G_CALLBACK(signalSetFocus), this);
1045 // set container without can-focus and focus will tab between
1046 // the native embedded widgets using the default gtk handling for
1047 // that
1048 gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), true);
1052 void GtkSalFrame::Init( SalFrame* pParent, SalFrameStyleFlags nStyle )
1054 if( nStyle & SalFrameStyleFlags::DEFAULT ) // ensure default style
1056 nStyle |= SalFrameStyleFlags::MOVEABLE | SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::CLOSEABLE;
1057 nStyle &= ~SalFrameStyleFlags::FLOAT;
1060 m_pParent = static_cast<GtkSalFrame*>(pParent);
1061 m_pForeignParent = nullptr;
1062 m_aForeignParentWindow = None;
1063 m_pForeignTopLevel = nullptr;
1064 m_aForeignTopLevelWindow = None;
1065 m_nStyle = nStyle;
1067 GtkWindowType eWinType = ( (nStyle & SalFrameStyleFlags::FLOAT) &&
1068 ! (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION)
1070 ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL;
1072 if( nStyle & SalFrameStyleFlags::SYSTEMCHILD )
1074 m_pWindow = gtk_event_box_new();
1075 if( m_pParent )
1077 // insert into container
1078 gtk_fixed_put( m_pParent->getFixedContainer(),
1079 m_pWindow, 0, 0 );
1082 else
1084 m_pWindow = gtk_window_new(eWinType);
1086 // hook up F1 to show help for embedded native gtk widgets
1087 GtkAccelGroup *pGroup = gtk_accel_group_new();
1088 GClosure* closure = g_cclosure_new(G_CALLBACK(GtkSalFrame::NativeWidgetHelpPressed), GTK_WINDOW(m_pWindow), nullptr);
1089 gtk_accel_group_connect(pGroup, GDK_KEY_F1, static_cast<GdkModifierType>(0), GTK_ACCEL_LOCKED, closure);
1090 gtk_window_add_accel_group(GTK_WINDOW(m_pWindow), pGroup);
1093 g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", this );
1094 g_object_set_data( G_OBJECT( m_pWindow ), "libo-version", const_cast<char *>(LIBO_VERSION_DOTTED));
1096 // force wm class hint
1097 if (!isChild())
1099 if (m_pParent)
1100 m_sWMClass = m_pParent->m_sWMClass;
1101 updateWMClass();
1104 if (GTK_IS_WINDOW(m_pWindow))
1106 if (m_pParent)
1108 GtkWidget* pTopLevel = gtk_widget_get_toplevel(m_pParent->m_pWindow);
1109 if (!isChild())
1110 gtk_window_set_screen(GTK_WINDOW(m_pWindow), gtk_widget_get_screen(pTopLevel));
1112 if (!(m_pParent->m_nStyle & SalFrameStyleFlags::PLUG))
1113 gtk_window_set_transient_for(GTK_WINDOW(m_pWindow), GTK_WINDOW(pTopLevel));
1114 m_pParent->m_aChildren.push_back( this );
1115 gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pTopLevel)), GTK_WINDOW(m_pWindow));
1117 else
1119 gtk_window_group_add_window(gtk_window_group_new(), GTK_WINDOW(m_pWindow));
1120 g_object_unref(gtk_window_get_group(GTK_WINDOW(m_pWindow)));
1124 // set window type
1125 bool bDecoHandling =
1126 ! isChild() &&
1127 ( ! (nStyle & SalFrameStyleFlags::FLOAT) ||
1128 (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) );
1130 if( bDecoHandling )
1132 GdkWindowTypeHint eType = GDK_WINDOW_TYPE_HINT_NORMAL;
1133 if( (nStyle & SalFrameStyleFlags::DIALOG) && m_pParent != nullptr )
1134 eType = GDK_WINDOW_TYPE_HINT_DIALOG;
1135 if( nStyle & SalFrameStyleFlags::INTRO )
1137 gtk_window_set_role( GTK_WINDOW(m_pWindow), "splashscreen" );
1138 eType = GDK_WINDOW_TYPE_HINT_SPLASHSCREEN;
1140 else if( nStyle & SalFrameStyleFlags::TOOLWINDOW )
1142 eType = GDK_WINDOW_TYPE_HINT_DIALOG;
1143 gtk_window_set_skip_taskbar_hint( GTK_WINDOW(m_pWindow), true );
1145 else if( nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
1147 eType = GDK_WINDOW_TYPE_HINT_TOOLBAR;
1148 gtk_window_set_focus_on_map(GTK_WINDOW(m_pWindow), false);
1149 gtk_window_set_decorated(GTK_WINDOW(m_pWindow), false);
1151 gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), eType );
1152 gtk_window_set_gravity( GTK_WINDOW(m_pWindow), GDK_GRAVITY_STATIC );
1153 gtk_window_set_resizable( GTK_WINDOW(m_pWindow), bool(nStyle & SalFrameStyleFlags::SIZEABLE) );
1155 #if defined(GDK_WINDOWING_WAYLAND)
1156 //rhbz#1392145 under wayland/csd if we've overridden the default widget direction in order to set LibreOffice's
1157 //UI to the configured ui language but the system ui locale is a different text direction, then the toplevel
1158 //built-in close button of the titlebar follows the overridden direction rather than continue in the same
1159 //direction as every other titlebar on the user's desktop. So if they don't match set an explicit
1160 //header bar with the desired 'outside' direction
1161 if ((eType == GDK_WINDOW_TYPE_HINT_NORMAL || eType == GDK_WINDOW_TYPE_HINT_DIALOG) && DLSYM_GDK_IS_WAYLAND_DISPLAY(GtkSalFrame::getGdkDisplay()))
1163 const bool bDesktopIsRTL = MsLangId::isRightToLeft(MsLangId::getSystemUILanguage());
1164 const bool bAppIsRTL = gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL;
1165 if (bDesktopIsRTL != bAppIsRTL)
1167 m_pHeaderBar = GTK_HEADER_BAR(gtk_header_bar_new());
1168 gtk_widget_set_direction(GTK_WIDGET(m_pHeaderBar), bDesktopIsRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
1169 gtk_header_bar_set_show_close_button(m_pHeaderBar, true);
1170 gtk_window_set_titlebar(GTK_WINDOW(m_pWindow), GTK_WIDGET(m_pHeaderBar));
1171 gtk_widget_show(GTK_WIDGET(m_pHeaderBar));
1174 #endif
1176 else if( nStyle & SalFrameStyleFlags::FLOAT )
1177 gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), GDK_WINDOW_TYPE_HINT_POPUP_MENU );
1179 InitCommon();
1181 if( eWinType == GTK_WINDOW_TOPLEVEL )
1183 // Enable DBus native menu if available.
1184 ensure_dbus_setup( this );
1189 GdkNativeWindow GtkSalFrame::findTopLevelSystemWindow( GdkNativeWindow )
1191 //FIXME: no findToplevelSystemWindow
1192 return 0;
1195 void GtkSalFrame::Init( SystemParentData* pSysData )
1197 m_pParent = nullptr;
1198 m_aForeignParentWindow = pSysData->aWindow;
1199 m_pForeignParent = nullptr;
1200 m_aForeignTopLevelWindow = findTopLevelSystemWindow(pSysData->aWindow);
1201 m_pForeignTopLevel = gdk_window_foreign_new_for_display( getGdkDisplay(), m_aForeignTopLevelWindow );
1202 gdk_window_set_events( m_pForeignTopLevel, GDK_STRUCTURE_MASK );
1204 if( pSysData->nSize > sizeof(pSysData->nSize)+sizeof(pSysData->aWindow) && pSysData->bXEmbedSupport )
1206 m_pWindow = gtk_plug_new_for_display( getGdkDisplay(), pSysData->aWindow );
1207 gtk_widget_set_can_default(m_pWindow, true);
1208 gtk_widget_set_can_focus(m_pWindow, true);
1209 gtk_widget_set_sensitive(m_pWindow, true);
1211 else
1213 m_pWindow = gtk_window_new( GTK_WINDOW_POPUP );
1215 m_nStyle = SalFrameStyleFlags::PLUG;
1216 InitCommon();
1218 m_pForeignParent = gdk_window_foreign_new_for_display( getGdkDisplay(), m_aForeignParentWindow );
1219 gdk_window_set_events( m_pForeignParent, GDK_STRUCTURE_MASK );
1221 //FIXME: Handling embedded windows, is going to be fun ...
1224 void GtkSalFrame::SetExtendedFrameStyle(SalExtStyle)
1228 SalGraphics* GtkSalFrame::AcquireGraphics()
1230 if( m_bGraphics )
1231 return nullptr;
1233 if( !m_pGraphics )
1235 m_pGraphics.reset( new GtkSalGraphics( this, m_pWindow ) );
1236 if (!m_pSurface)
1238 AllocateFrame();
1239 TriggerPaintEvent();
1241 m_pGraphics->setSurface(m_pSurface, m_aFrameSize);
1243 m_bGraphics = true;
1244 return m_pGraphics.get();
1247 void GtkSalFrame::ReleaseGraphics( SalGraphics* pGraphics )
1249 (void) pGraphics;
1250 assert( pGraphics == m_pGraphics.get() );
1251 m_bGraphics = false;
1254 bool GtkSalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
1256 getDisplay()->SendInternalEvent( this, pData.release() );
1257 return true;
1260 void GtkSalFrame::SetTitle( const OUString& rTitle )
1262 if( m_pWindow && ! isChild() )
1264 OString sTitle(OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8));
1265 gtk_window_set_title(GTK_WINDOW(m_pWindow), sTitle.getStr());
1266 if (m_pHeaderBar)
1267 gtk_header_bar_set_title(m_pHeaderBar, sTitle.getStr());
1271 void GtkSalFrame::SetIcon(const char* appicon)
1273 gtk_window_set_icon_name(GTK_WINDOW(m_pWindow), appicon);
1275 #if defined(GDK_WINDOWING_WAYLAND)
1276 if (DLSYM_GDK_IS_WAYLAND_DISPLAY(getGdkDisplay()))
1278 static auto set_application_id = reinterpret_cast<void (*) (GdkWindow*, const char*)>(
1279 dlsym(nullptr, "gdk_wayland_window_set_application_id"));
1280 if (set_application_id)
1282 GdkWindow* gdkWindow = gtk_widget_get_window(m_pWindow);
1283 set_application_id(gdkWindow, appicon);
1285 // gdk_wayland_window_set_application_id doesn't seem to work before
1286 // the window is mapped, so set this for real when/if we are mapped
1287 m_bIconSetWhileUnmapped = !gtk_widget_get_mapped(m_pWindow);
1290 #endif
1293 void GtkSalFrame::SetIcon( sal_uInt16 nIcon )
1295 if( (m_nStyle & (SalFrameStyleFlags::PLUG|SalFrameStyleFlags::SYSTEMCHILD|SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::INTRO|SalFrameStyleFlags::OWNERDRAWDECORATION))
1296 || ! m_pWindow )
1297 return;
1299 gchar* appicon;
1301 if (nIcon == SV_ICON_ID_TEXT)
1302 appicon = g_strdup ("libreoffice-writer");
1303 else if (nIcon == SV_ICON_ID_SPREADSHEET)
1304 appicon = g_strdup ("libreoffice-calc");
1305 else if (nIcon == SV_ICON_ID_DRAWING)
1306 appicon = g_strdup ("libreoffice-draw");
1307 else if (nIcon == SV_ICON_ID_PRESENTATION)
1308 appicon = g_strdup ("libreoffice-impress");
1309 else if (nIcon == SV_ICON_ID_DATABASE)
1310 appicon = g_strdup ("libreoffice-base");
1311 else if (nIcon == SV_ICON_ID_FORMULA)
1312 appicon = g_strdup ("libreoffice-math");
1313 else
1314 appicon = g_strdup ("libreoffice-startcenter");
1316 SetIcon(appicon);
1318 g_free (appicon);
1321 void GtkSalFrame::SetMenu( SalMenu* pSalMenu )
1323 m_pSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
1326 SalMenu* GtkSalFrame::GetMenu()
1328 return m_pSalMenu;
1331 void GtkSalFrame::DrawMenuBar()
1335 void GtkSalFrame::Center()
1337 if (!GTK_IS_WINDOW(m_pWindow))
1338 return;
1339 if (m_pParent)
1340 gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER_ON_PARENT);
1341 else
1342 gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER);
1345 Size GtkSalFrame::calcDefaultSize()
1347 Size aScreenSize(getDisplay()->GetScreenSize(GetDisplayScreen()));
1348 int scale = gtk_widget_get_scale_factor(m_pWindow);
1349 aScreenSize.setWidth( aScreenSize.Width() / scale );
1350 aScreenSize.setHeight( aScreenSize.Height() / scale );
1351 return bestmaxFrameSizeForScreenSize(aScreenSize);
1354 void GtkSalFrame::SetDefaultSize()
1356 Size aDefSize = calcDefaultSize();
1358 SetPosSize( 0, 0, aDefSize.Width(), aDefSize.Height(),
1359 SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
1361 if( (m_nStyle & SalFrameStyleFlags::DEFAULT) && m_pWindow )
1362 gtk_window_maximize( GTK_WINDOW(m_pWindow) );
1365 void GtkSalFrame::Show( bool bVisible, bool /*bNoActivate*/ )
1367 if( !m_pWindow )
1368 return;
1370 if( bVisible )
1372 getDisplay()->startupNotificationCompleted();
1374 if( m_bDefaultPos )
1375 Center();
1376 if( m_bDefaultSize )
1377 SetDefaultSize();
1378 setMinMaxSize();
1380 if (isFloatGrabWindow() && !getDisplay()->GetCaptureFrame())
1382 m_pParent->grabPointer(true, true, true);
1383 m_pParent->addGrabLevel();
1386 #if defined(GDK_WINDOWING_WAYLAND)
1388 rhbz#1334915, gnome#779143, tdf#100158
1389 https://gitlab.gnome.org/GNOME/gtk/-/issues/767
1391 before gdk_wayland_window_set_application_id was available gtk
1392 under wayland lacked a way to change the app_id of a window, so
1393 brute force everything as a startcenter when initially shown to at
1394 least get the default LibreOffice icon and not the broken app icon
1396 static bool bAppIdImmutable = DLSYM_GDK_IS_WAYLAND_DISPLAY(getGdkDisplay()) &&
1397 !dlsym(nullptr, "gdk_wayland_window_set_application_id");
1398 if (bAppIdImmutable)
1400 OString sOrigName(g_get_prgname());
1401 g_set_prgname("libreoffice-startcenter");
1402 gtk_widget_show(m_pWindow);
1403 g_set_prgname(sOrigName.getStr());
1405 else
1407 gtk_widget_show(m_pWindow);
1409 #else
1410 gtk_widget_show(m_pWindow);
1411 #endif
1413 if( isFloatGrabWindow() )
1415 m_nFloats++;
1416 if (!getDisplay()->GetCaptureFrame())
1418 grabPointer(true, true, true);
1419 addGrabLevel();
1421 // #i44068# reset parent's IM context
1422 if( m_pParent )
1423 m_pParent->EndExtTextInput(EndExtTextInputFlags::NONE);
1426 else
1428 if( isFloatGrabWindow() )
1430 m_nFloats--;
1431 if (!getDisplay()->GetCaptureFrame())
1433 removeGrabLevel();
1434 grabPointer(false, true, false);
1435 m_pParent->removeGrabLevel();
1436 bool bParentIsFloatGrabWindow = m_pParent->isFloatGrabWindow();
1437 m_pParent->grabPointer(bParentIsFloatGrabWindow, true, bParentIsFloatGrabWindow);
1440 gtk_widget_hide( m_pWindow );
1441 if( m_pIMHandler )
1442 m_pIMHandler->focusChanged( false );
1446 void GtkSalFrame::setMinMaxSize()
1448 /* #i34504# metacity (and possibly others) do not treat
1449 * _NET_WM_STATE_FULLSCREEN and max_width/height independently;
1450 * whether they should is undefined. So don't set the max size hint
1451 * for a full screen window.
1453 if( !m_pWindow || isChild() )
1454 return;
1456 GdkGeometry aGeo;
1457 int aHints = 0;
1458 if( m_nStyle & SalFrameStyleFlags::SIZEABLE )
1460 if( m_aMinSize.Width() && m_aMinSize.Height() && ! m_bFullscreen )
1462 aGeo.min_width = m_aMinSize.Width();
1463 aGeo.min_height = m_aMinSize.Height();
1464 aHints |= GDK_HINT_MIN_SIZE;
1466 if( m_aMaxSize.Width() && m_aMaxSize.Height() && ! m_bFullscreen )
1468 aGeo.max_width = m_aMaxSize.Width();
1469 aGeo.max_height = m_aMaxSize.Height();
1470 aHints |= GDK_HINT_MAX_SIZE;
1473 else
1475 if (!m_bFullscreen && m_nWidthRequest && m_nHeightRequest)
1477 aGeo.min_width = m_nWidthRequest;
1478 aGeo.min_height = m_nHeightRequest;
1479 aHints |= GDK_HINT_MIN_SIZE;
1481 aGeo.max_width = m_nWidthRequest;
1482 aGeo.max_height = m_nHeightRequest;
1483 aHints |= GDK_HINT_MAX_SIZE;
1487 if( m_bFullscreen && m_aMaxSize.Width() && m_aMaxSize.Height() )
1489 aGeo.max_width = m_aMaxSize.Width();
1490 aGeo.max_height = m_aMaxSize.Height();
1491 aHints |= GDK_HINT_MAX_SIZE;
1493 if( aHints )
1495 gtk_window_set_geometry_hints( GTK_WINDOW(m_pWindow),
1496 nullptr,
1497 &aGeo,
1498 GdkWindowHints( aHints ) );
1502 void GtkSalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight )
1504 if( ! isChild() )
1506 m_aMaxSize = Size( nWidth, nHeight );
1507 setMinMaxSize();
1510 void GtkSalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight )
1512 if( ! isChild() )
1514 m_aMinSize = Size( nWidth, nHeight );
1515 if( m_pWindow )
1517 widget_set_size_request(nWidth, nHeight);
1518 setMinMaxSize();
1523 void GtkSalFrame::AllocateFrame()
1525 basegfx::B2IVector aFrameSize( maGeometry.nWidth, maGeometry.nHeight );
1526 if (m_pSurface && m_aFrameSize.getX() == aFrameSize.getX() &&
1527 m_aFrameSize.getY() == aFrameSize.getY() )
1528 return;
1530 if( aFrameSize.getX() == 0 )
1531 aFrameSize.setX( 1 );
1532 if( aFrameSize.getY() == 0 )
1533 aFrameSize.setY( 1 );
1535 if (m_pSurface)
1536 cairo_surface_destroy(m_pSurface);
1538 m_pSurface = gdk_window_create_similar_surface(gtk_widget_get_window(m_pWindow),
1539 CAIRO_CONTENT_COLOR_ALPHA,
1540 aFrameSize.getX(),
1541 aFrameSize.getY());
1542 m_aFrameSize = aFrameSize;
1544 cairo_surface_set_user_data(m_pSurface, SvpSalGraphics::getDamageKey(), &m_aDamageHandler, nullptr);
1545 SAL_INFO("vcl.gtk3", "allocated Frame size of " << maGeometry.nWidth << " x " << maGeometry.nHeight);
1547 if (m_pGraphics)
1548 m_pGraphics->setSurface(m_pSurface, m_aFrameSize);
1551 void GtkSalFrame::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags )
1553 if( !m_pWindow || isChild( true, false ) )
1554 return;
1556 if( (nFlags & ( SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT )) &&
1557 (nWidth > 0 && nHeight > 0 ) // sometimes stupid things happen
1560 m_bDefaultSize = false;
1562 // tdf#131031 Just setting maGeometry.nWidth/nHeight will delete
1563 // the evtl. implicitely existing space at top for the gtk native MenuBar,
1564 // will make the Window too big and the StatusBar at the bottom vanish
1565 // and thus breaks the fix in tdf#130841.
1566 const int nImplicitMenuBarHeight(m_pSalMenu ? m_pSalMenu->GetMenuBarHeight() : 0);
1567 maGeometry.nWidth = nWidth;
1568 maGeometry.nHeight = nHeight - nImplicitMenuBarHeight;
1570 if( isChild( false ) )
1571 widget_set_size_request(nWidth, nHeight);
1572 else if( ! ( m_nState & GDK_WINDOW_STATE_MAXIMIZED ) )
1573 window_resize(nWidth, nHeight);
1575 setMinMaxSize();
1577 else if( m_bDefaultSize )
1578 SetDefaultSize();
1580 m_bDefaultSize = false;
1582 if( nFlags & ( SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ) )
1584 if( m_pParent )
1586 if( AllSettings::GetLayoutRTL() )
1587 nX = m_pParent->maGeometry.nWidth-m_nWidthRequest-1-nX;
1588 nX += m_pParent->maGeometry.nX;
1589 nY += m_pParent->maGeometry.nY;
1592 if (nFlags & SAL_FRAME_POSSIZE_X)
1593 maGeometry.nX = nX;
1594 if (nFlags & SAL_FRAME_POSSIZE_Y)
1595 maGeometry.nY = nY;
1596 m_bGeometryIsProvisional = true;
1598 m_bDefaultPos = false;
1600 moveWindow(maGeometry.nX, maGeometry.nY);
1602 updateScreenNumber();
1604 else if( m_bDefaultPos )
1605 Center();
1607 m_bDefaultPos = false;
1610 void GtkSalFrame::GetClientSize( tools::Long& rWidth, tools::Long& rHeight )
1612 if( m_pWindow && !(m_nState & GDK_WINDOW_STATE_ICONIFIED) )
1614 rWidth = maGeometry.nWidth;
1615 rHeight = maGeometry.nHeight;
1617 else
1618 rWidth = rHeight = 0;
1621 void GtkSalFrame::GetWorkArea( tools::Rectangle& rRect )
1623 GdkScreen *pScreen = gtk_widget_get_screen(m_pWindow);
1624 tools::Rectangle aRetRect;
1625 int max = gdk_screen_get_n_monitors (pScreen);
1626 for (int i = 0; i < max; ++i)
1628 GdkRectangle aRect;
1629 gdk_screen_get_monitor_workarea(pScreen, i, &aRect);
1630 tools::Rectangle aMonitorRect(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height);
1631 aRetRect.Union(aMonitorRect);
1633 rRect = aRetRect;
1636 SalFrame* GtkSalFrame::GetParent() const
1638 return m_pParent;
1641 void GtkSalFrame::SetWindowState( const SalFrameState* pState )
1643 if( ! m_pWindow || ! pState || isChild( true, false ) )
1644 return;
1646 const WindowStateMask nMaxGeometryMask =
1647 WindowStateMask::X | WindowStateMask::Y |
1648 WindowStateMask::Width | WindowStateMask::Height |
1649 WindowStateMask::MaximizedX | WindowStateMask::MaximizedY |
1650 WindowStateMask::MaximizedWidth | WindowStateMask::MaximizedHeight;
1652 if( (pState->mnMask & WindowStateMask::State) &&
1653 ! ( m_nState & GDK_WINDOW_STATE_MAXIMIZED ) &&
1654 (pState->mnState & WindowStateState::Maximized) &&
1655 (pState->mnMask & nMaxGeometryMask) == nMaxGeometryMask )
1657 resizeWindow( pState->mnWidth, pState->mnHeight );
1658 moveWindow( pState->mnX, pState->mnY );
1659 m_bDefaultPos = m_bDefaultSize = false;
1661 updateScreenNumber();
1663 m_nState = GdkWindowState( m_nState | GDK_WINDOW_STATE_MAXIMIZED );
1664 m_aRestorePosSize = tools::Rectangle( Point( pState->mnX, pState->mnY ),
1665 Size( pState->mnWidth, pState->mnHeight ) );
1667 else if( pState->mnMask & (WindowStateMask::X | WindowStateMask::Y |
1668 WindowStateMask::Width | WindowStateMask::Height ) )
1670 sal_uInt16 nPosSizeFlags = 0;
1671 tools::Long nX = pState->mnX - (m_pParent ? m_pParent->maGeometry.nX : 0);
1672 tools::Long nY = pState->mnY - (m_pParent ? m_pParent->maGeometry.nY : 0);
1673 if( pState->mnMask & WindowStateMask::X )
1674 nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
1675 else
1676 nX = maGeometry.nX - (m_pParent ? m_pParent->maGeometry.nX : 0);
1677 if( pState->mnMask & WindowStateMask::Y )
1678 nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
1679 else
1680 nY = maGeometry.nY - (m_pParent ? m_pParent->maGeometry.nY : 0);
1681 if( pState->mnMask & WindowStateMask::Width )
1682 nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
1683 if( pState->mnMask & WindowStateMask::Height )
1684 nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
1685 SetPosSize( nX, nY, pState->mnWidth, pState->mnHeight, nPosSizeFlags );
1687 if( pState->mnMask & WindowStateMask::State && ! isChild() )
1689 if( pState->mnState & WindowStateState::Maximized )
1690 gtk_window_maximize( GTK_WINDOW(m_pWindow) );
1691 else
1692 gtk_window_unmaximize( GTK_WINDOW(m_pWindow) );
1693 /* #i42379# there is no rollup state in GDK; and rolled up windows are
1694 * (probably depending on the WM) reported as iconified. If we iconify a
1695 * window here that was e.g. a dialog, then it will be unmapped but still
1696 * not be displayed in the task list, so it's an iconified window that
1697 * the user cannot get out of this state. So do not set the iconified state
1698 * on windows with a parent (that is transient frames) since these tend
1699 * to not be represented in an icon task list.
1701 if( (pState->mnState & WindowStateState::Minimized)
1702 && ! m_pParent )
1703 gtk_window_iconify( GTK_WINDOW(m_pWindow) );
1704 else
1705 gtk_window_deiconify( GTK_WINDOW(m_pWindow) );
1707 TriggerPaintEvent();
1710 namespace
1712 void GetPosAndSize(GtkWindow *pWindow, tools::Long& rX, tools::Long &rY, tools::Long &rWidth, tools::Long &rHeight)
1714 gint root_x, root_y;
1715 gtk_window_get_position(GTK_WINDOW(pWindow), &root_x, &root_y);
1716 rX = root_x;
1717 rY = root_y;
1718 gint width, height;
1719 gtk_window_get_size(GTK_WINDOW(pWindow), &width, &height);
1720 rWidth = width;
1721 rHeight = height;
1724 tools::Rectangle GetPosAndSize(GtkWindow *pWindow)
1726 tools::Long nX, nY, nWidth, nHeight;
1727 GetPosAndSize(pWindow, nX, nY, nWidth, nHeight);
1728 return tools::Rectangle(nX, nY, nX + nWidth, nY + nHeight);
1732 bool GtkSalFrame::GetWindowState( SalFrameState* pState )
1734 pState->mnState = WindowStateState::Normal;
1735 pState->mnMask = WindowStateMask::State;
1736 // rollup ? gtk 2.2 does not seem to support the shaded state
1737 if( m_nState & GDK_WINDOW_STATE_ICONIFIED )
1738 pState->mnState |= WindowStateState::Minimized;
1739 if( m_nState & GDK_WINDOW_STATE_MAXIMIZED )
1741 pState->mnState |= WindowStateState::Maximized;
1742 pState->mnX = m_aRestorePosSize.Left();
1743 pState->mnY = m_aRestorePosSize.Top();
1744 pState->mnWidth = m_aRestorePosSize.GetWidth();
1745 pState->mnHeight = m_aRestorePosSize.GetHeight();
1746 GetPosAndSize(GTK_WINDOW(m_pWindow), pState->mnMaximizedX, pState->mnMaximizedY,
1747 pState->mnMaximizedWidth, pState->mnMaximizedHeight);
1748 pState->mnMask |= WindowStateMask::MaximizedX |
1749 WindowStateMask::MaximizedY |
1750 WindowStateMask::MaximizedWidth |
1751 WindowStateMask::MaximizedHeight;
1753 else
1755 GetPosAndSize(GTK_WINDOW(m_pWindow), pState->mnX, pState->mnY,
1756 pState->mnWidth, pState->mnHeight);
1758 pState->mnMask |= WindowStateMask::X |
1759 WindowStateMask::Y |
1760 WindowStateMask::Width |
1761 WindowStateMask::Height;
1763 return true;
1766 void GtkSalFrame::SetScreen( unsigned int nNewScreen, SetType eType, tools::Rectangle const *pSize )
1768 if( !m_pWindow )
1769 return;
1771 if (maGeometry.nDisplayScreenNumber == nNewScreen && eType == SetType::RetainSize)
1772 return;
1774 int nX = maGeometry.nX, nY = maGeometry.nY,
1775 nWidth = maGeometry.nWidth, nHeight = maGeometry.nHeight;
1776 GdkScreen *pScreen = nullptr;
1777 GdkRectangle aNewMonitor;
1779 bool bSpanAllScreens = nNewScreen == static_cast<unsigned int>(-1);
1780 m_bSpanMonitorsWhenFullscreen = bSpanAllScreens && getDisplay()->getSystem()->GetDisplayScreenCount() > 1;
1781 gint nMonitor = -1;
1782 if (m_bSpanMonitorsWhenFullscreen) //span all screens
1784 pScreen = gtk_widget_get_screen( m_pWindow );
1785 aNewMonitor.x = 0;
1786 aNewMonitor.y = 0;
1787 aNewMonitor.width = gdk_screen_get_width(pScreen);
1788 aNewMonitor.height = gdk_screen_get_height(pScreen);
1790 else
1792 bool bSameMonitor = false;
1794 if (!bSpanAllScreens)
1796 pScreen = getDisplay()->getSystem()->getScreenMonitorFromIdx( nNewScreen, nMonitor );
1797 if (!pScreen)
1799 g_warning ("Attempt to move GtkSalFrame to invalid screen %d => "
1800 "fallback to current\n", nNewScreen);
1804 if (!pScreen)
1806 pScreen = gtk_widget_get_screen( m_pWindow );
1807 bSameMonitor = true;
1810 // Heavy lifting, need to move screen ...
1811 if( pScreen != gtk_widget_get_screen( m_pWindow ))
1812 gtk_window_set_screen( GTK_WINDOW( m_pWindow ), pScreen );
1814 gint nOldMonitor = gdk_screen_get_monitor_at_window(
1815 pScreen, gtk_widget_get_window( m_pWindow ) );
1816 if (bSameMonitor)
1817 nMonitor = nOldMonitor;
1819 #if OSL_DEBUG_LEVEL > 1
1820 if( nMonitor == nOldMonitor )
1821 g_warning( "An apparently pointless SetScreen - should we elide it ?" );
1822 #endif
1824 GdkRectangle aOldMonitor;
1825 gdk_screen_get_monitor_geometry( pScreen, nOldMonitor, &aOldMonitor );
1826 gdk_screen_get_monitor_geometry( pScreen, nMonitor, &aNewMonitor );
1828 nX = aNewMonitor.x + nX - aOldMonitor.x;
1829 nY = aNewMonitor.y + nY - aOldMonitor.y;
1832 bool bResize = false;
1833 bool bVisible = gtk_widget_get_mapped( m_pWindow );
1834 if( bVisible )
1835 Show( false );
1837 if( eType == SetType::Fullscreen )
1839 nX = aNewMonitor.x;
1840 nY = aNewMonitor.y;
1841 nWidth = aNewMonitor.width;
1842 nHeight = aNewMonitor.height;
1843 m_nStyle |= SalFrameStyleFlags::PARTIAL_FULLSCREEN;
1844 bResize = true;
1846 // #i110881# for the benefit of compiz set a max size here
1847 // else setting to fullscreen fails for unknown reasons
1848 m_aMaxSize.setWidth( aNewMonitor.width );
1849 m_aMaxSize.setHeight( aNewMonitor.height );
1852 if( pSize && eType == SetType::UnFullscreen )
1854 nX = pSize->Left();
1855 nY = pSize->Top();
1856 nWidth = pSize->GetWidth();
1857 nHeight = pSize->GetHeight();
1858 m_nStyle &= ~SalFrameStyleFlags::PARTIAL_FULLSCREEN;
1859 bResize = true;
1862 if (bResize)
1864 // temporarily re-sizeable
1865 if( !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
1866 gtk_window_set_resizable( GTK_WINDOW(m_pWindow), true );
1867 window_resize(nWidth, nHeight);
1870 gtk_window_move(GTK_WINDOW(m_pWindow), nX, nY);
1872 gdk_window_set_fullscreen_mode( gtk_widget_get_window(m_pWindow), m_bSpanMonitorsWhenFullscreen
1873 ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR );
1875 GtkWidget* pMenuBarContainerWidget = m_pSalMenu ? m_pSalMenu->GetMenuBarContainerWidget() : nullptr;
1876 if( eType == SetType::Fullscreen )
1878 if (pMenuBarContainerWidget)
1879 gtk_widget_hide(pMenuBarContainerWidget);
1880 if (m_bSpanMonitorsWhenFullscreen)
1881 gtk_window_fullscreen(GTK_WINDOW(m_pWindow));
1882 else
1884 gtk_window_fullscreen_on_monitor(GTK_WINDOW(m_pWindow), pScreen, nMonitor);
1888 else if( eType == SetType::UnFullscreen )
1890 if (pMenuBarContainerWidget)
1891 gtk_widget_show(pMenuBarContainerWidget);
1892 gtk_window_unfullscreen( GTK_WINDOW( m_pWindow ) );
1895 if( eType == SetType::UnFullscreen &&
1896 !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
1897 gtk_window_set_resizable( GTK_WINDOW( m_pWindow ), FALSE );
1899 // FIXME: we should really let gtk+ handle our widget hierarchy ...
1900 if( m_pParent && gtk_widget_get_screen( m_pParent->m_pWindow ) != pScreen )
1901 SetParent( nullptr );
1902 std::list< GtkSalFrame* > aChildren = m_aChildren;
1903 for (auto const& child : aChildren)
1904 child->SetScreen( nNewScreen, SetType::RetainSize );
1906 m_bDefaultPos = m_bDefaultSize = false;
1907 updateScreenNumber();
1909 if( bVisible )
1910 Show( true );
1913 void GtkSalFrame::SetScreenNumber( unsigned int nNewScreen )
1915 SetScreen( nNewScreen, SetType::RetainSize );
1918 void GtkSalFrame::updateWMClass()
1920 OString aResClass = OUStringToOString(m_sWMClass, RTL_TEXTENCODING_ASCII_US);
1921 const char *pResClass = !aResClass.isEmpty() ? aResClass.getStr() :
1922 SalGenericSystem::getFrameClassName();
1923 Display *display;
1925 if (!getDisplay()->IsX11Display())
1926 return;
1928 display = GDK_DISPLAY_XDISPLAY(getGdkDisplay());
1930 if( gtk_widget_get_realized( m_pWindow ) )
1932 XClassHint* pClass = XAllocClassHint();
1933 OString aResName = SalGenericSystem::getFrameResName();
1934 pClass->res_name = const_cast<char*>(aResName.getStr());
1935 pClass->res_class = const_cast<char*>(pResClass);
1936 XSetClassHint( display,
1937 widget_get_xid(m_pWindow),
1938 pClass );
1939 XFree( pClass );
1943 void GtkSalFrame::SetApplicationID( const OUString &rWMClass )
1945 if( rWMClass != m_sWMClass && ! isChild() )
1947 m_sWMClass = rWMClass;
1948 updateWMClass();
1950 for (auto const& child : m_aChildren)
1951 child->SetApplicationID(rWMClass);
1955 void GtkSalFrame::ShowFullScreen( bool bFullScreen, sal_Int32 nScreen )
1957 m_bFullscreen = bFullScreen;
1959 if( !m_pWindow || isChild() )
1960 return;
1962 if( bFullScreen )
1964 m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(m_pWindow));
1965 SetScreen( nScreen, SetType::Fullscreen );
1967 else
1969 SetScreen( nScreen, SetType::UnFullscreen,
1970 !m_aRestorePosSize.IsEmpty() ? &m_aRestorePosSize : nullptr );
1971 m_aRestorePosSize = tools::Rectangle();
1975 void GtkSalFrame::StartPresentation( bool bStart )
1977 std::optional<guint> aWindow;
1978 std::optional<Display*> aDisplay;
1979 if( getDisplay()->IsX11Display() )
1981 aWindow = widget_get_xid(m_pWindow);
1982 aDisplay = GDK_DISPLAY_XDISPLAY( getGdkDisplay() );
1985 m_ScreenSaverInhibitor.inhibit( bStart,
1986 "presentation",
1987 getDisplay()->IsX11Display(),
1988 aWindow,
1989 aDisplay );
1992 void GtkSalFrame::SetAlwaysOnTop( bool bOnTop )
1994 if( m_pWindow )
1995 gtk_window_set_keep_above( GTK_WINDOW( m_pWindow ), bOnTop );
1998 static guint32 nLastUserInputTime = GDK_CURRENT_TIME;
2000 guint32 GtkSalFrame::GetLastInputEventTime()
2002 return nLastUserInputTime;
2005 void GtkSalFrame::UpdateLastInputEventTime(guint32 nUserInputTime)
2007 //gtk3 can generate a synthetic crossing event with a useless 0
2008 //(GDK_CURRENT_TIME) timestamp on showing a menu from the main
2009 //menubar, which is unhelpful, so ignore the 0 timestamps
2010 if (nUserInputTime == GDK_CURRENT_TIME)
2011 return;
2012 nLastUserInputTime = nUserInputTime;
2015 void GtkSalFrame::ToTop( SalFrameToTop nFlags )
2017 if( !m_pWindow )
2018 return;
2020 if( isChild( false ) )
2021 GrabFocus();
2022 else if( gtk_widget_get_mapped( m_pWindow ) )
2024 auto nTimestamp = GetLastInputEventTime();
2025 #ifdef GDK_WINDOWING_X11
2026 GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
2027 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
2028 nTimestamp = gdk_x11_display_get_user_time(pDisplay);
2029 #endif
2030 if (!(nFlags & SalFrameToTop::GrabFocusOnly))
2031 gtk_window_present_with_time(GTK_WINDOW(m_pWindow), nTimestamp);
2032 else
2033 gdk_window_focus(gtk_widget_get_window(m_pWindow), nTimestamp);
2034 GrabFocus();
2036 else
2038 if( nFlags & SalFrameToTop::RestoreWhenMin )
2039 gtk_window_present( GTK_WINDOW(m_pWindow) );
2043 void GtkSalFrame::SetPointer( PointerStyle ePointerStyle )
2045 if( !m_pWindow || ePointerStyle == m_ePointerStyle )
2046 return;
2048 m_ePointerStyle = ePointerStyle;
2049 GdkCursor *pCursor = getDisplay()->getCursor( ePointerStyle );
2050 gdk_window_set_cursor( gtk_widget_get_window(m_pWindow), pCursor );
2052 // #i80791# use grabPointer the same way as CaptureMouse, respective float grab
2053 if( getDisplay()->MouseCaptured( this ) )
2054 grabPointer( true, false, false );
2055 else if( m_nFloats > 0 )
2056 grabPointer( true, false, true );
2059 void GtkSalFrame::grabPointer( bool bGrab, bool bKeyboardAlso, bool bOwnerEvents )
2061 if (bGrab)
2063 // tdf#135779 move focus back inside usual input window out of any
2064 // other gtk widgets before grabbing the pointer
2065 GrabFocus();
2068 static const char* pEnv = getenv( "SAL_NO_MOUSEGRABS" );
2069 if (pEnv && *pEnv)
2070 return;
2072 if (!m_pWindow)
2073 return;
2075 GdkSeat* pSeat = gdk_display_get_default_seat(getGdkDisplay());
2076 if (bGrab)
2078 GdkSeatCapabilities eCapability = bKeyboardAlso ? GDK_SEAT_CAPABILITY_ALL : GDK_SEAT_CAPABILITY_ALL_POINTING;
2079 gdk_seat_grab(pSeat, gtk_widget_get_window(getMouseEventWidget()), eCapability,
2080 bOwnerEvents, nullptr, nullptr, nullptr, nullptr);
2082 else
2084 gdk_seat_ungrab(pSeat);
2088 void GtkSalFrame::CaptureMouse( bool bCapture )
2090 getDisplay()->CaptureMouse( bCapture ? this : nullptr );
2093 void GtkSalFrame::SetPointerPos( tools::Long nX, tools::Long nY )
2095 GtkSalFrame* pFrame = this;
2096 while( pFrame && pFrame->isChild( false ) )
2097 pFrame = pFrame->m_pParent;
2098 if( ! pFrame )
2099 return;
2101 GdkScreen *pScreen = gtk_widget_get_screen(pFrame->m_pWindow);
2102 GdkDisplay *pDisplay = gdk_screen_get_display( pScreen );
2104 /* when the application tries to center the mouse in the dialog the
2105 * window isn't mapped already. So use coordinates relative to the root window.
2107 unsigned int nWindowLeft = maGeometry.nX + nX;
2108 unsigned int nWindowTop = maGeometry.nY + nY;
2110 GdkDeviceManager* pManager = gdk_display_get_device_manager(pDisplay);
2111 gdk_device_warp(gdk_device_manager_get_client_pointer(pManager), pScreen, nWindowLeft, nWindowTop);
2113 // #i38648# ask for the next motion hint
2114 gint x, y;
2115 GdkModifierType mask;
2116 gdk_window_get_pointer( gtk_widget_get_window(pFrame->m_pWindow) , &x, &y, &mask );
2119 void GtkSalFrame::Flush()
2121 gdk_display_flush( getGdkDisplay() );
2124 void GtkSalFrame::KeyCodeToGdkKey(const vcl::KeyCode& rKeyCode,
2125 guint* pGdkKeyCode, GdkModifierType *pGdkModifiers)
2127 if ( pGdkKeyCode == nullptr || pGdkModifiers == nullptr )
2128 return;
2130 // Get GDK key modifiers
2131 GdkModifierType nModifiers = GdkModifierType(0);
2133 if ( rKeyCode.IsShift() )
2134 nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_SHIFT_MASK );
2136 if ( rKeyCode.IsMod1() )
2137 nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_CONTROL_MASK );
2139 if ( rKeyCode.IsMod2() )
2140 nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_MOD1_MASK );
2142 *pGdkModifiers = nModifiers;
2144 // Get GDK keycode.
2145 guint nKeyCode = 0;
2147 guint nCode = rKeyCode.GetCode();
2149 if ( nCode >= KEY_0 && nCode <= KEY_9 )
2150 nKeyCode = ( nCode - KEY_0 ) + GDK_KEY_0;
2151 else if ( nCode >= KEY_A && nCode <= KEY_Z )
2152 nKeyCode = ( nCode - KEY_A ) + GDK_KEY_A;
2153 else if ( nCode >= KEY_F1 && nCode <= KEY_F26 )
2154 nKeyCode = ( nCode - KEY_F1 ) + GDK_KEY_F1;
2155 else
2157 switch (nCode)
2159 case KEY_DOWN: nKeyCode = GDK_KEY_Down; break;
2160 case KEY_UP: nKeyCode = GDK_KEY_Up; break;
2161 case KEY_LEFT: nKeyCode = GDK_KEY_Left; break;
2162 case KEY_RIGHT: nKeyCode = GDK_KEY_Right; break;
2163 case KEY_HOME: nKeyCode = GDK_KEY_Home; break;
2164 case KEY_END: nKeyCode = GDK_KEY_End; break;
2165 case KEY_PAGEUP: nKeyCode = GDK_KEY_Page_Up; break;
2166 case KEY_PAGEDOWN: nKeyCode = GDK_KEY_Page_Down; break;
2167 case KEY_RETURN: nKeyCode = GDK_KEY_Return; break;
2168 case KEY_ESCAPE: nKeyCode = GDK_KEY_Escape; break;
2169 case KEY_TAB: nKeyCode = GDK_KEY_Tab; break;
2170 case KEY_BACKSPACE: nKeyCode = GDK_KEY_BackSpace; break;
2171 case KEY_SPACE: nKeyCode = GDK_KEY_space; break;
2172 case KEY_INSERT: nKeyCode = GDK_KEY_Insert; break;
2173 case KEY_DELETE: nKeyCode = GDK_KEY_Delete; break;
2174 case KEY_ADD: nKeyCode = GDK_KEY_plus; break;
2175 case KEY_SUBTRACT: nKeyCode = GDK_KEY_minus; break;
2176 case KEY_MULTIPLY: nKeyCode = GDK_KEY_asterisk; break;
2177 case KEY_DIVIDE: nKeyCode = GDK_KEY_slash; break;
2178 case KEY_POINT: nKeyCode = GDK_KEY_period; break;
2179 case KEY_COMMA: nKeyCode = GDK_KEY_comma; break;
2180 case KEY_LESS: nKeyCode = GDK_KEY_less; break;
2181 case KEY_GREATER: nKeyCode = GDK_KEY_greater; break;
2182 case KEY_EQUAL: nKeyCode = GDK_KEY_equal; break;
2183 case KEY_FIND: nKeyCode = GDK_KEY_Find; break;
2184 case KEY_CONTEXTMENU: nKeyCode = GDK_KEY_Menu; break;
2185 case KEY_HELP: nKeyCode = GDK_KEY_Help; break;
2186 case KEY_UNDO: nKeyCode = GDK_KEY_Undo; break;
2187 case KEY_REPEAT: nKeyCode = GDK_KEY_Redo; break;
2188 case KEY_DECIMAL: nKeyCode = GDK_KEY_KP_Decimal; break;
2189 case KEY_TILDE: nKeyCode = GDK_KEY_asciitilde; break;
2190 case KEY_QUOTELEFT: nKeyCode = GDK_KEY_quoteleft; break;
2191 case KEY_BRACKETLEFT: nKeyCode = GDK_KEY_bracketleft; break;
2192 case KEY_BRACKETRIGHT: nKeyCode = GDK_KEY_bracketright; break;
2193 case KEY_SEMICOLON: nKeyCode = GDK_KEY_semicolon; break;
2194 case KEY_QUOTERIGHT: nKeyCode = GDK_KEY_quoteright; break;
2196 // Special cases
2197 case KEY_COPY: nKeyCode = GDK_KEY_Copy; break;
2198 case KEY_CUT: nKeyCode = GDK_KEY_Cut; break;
2199 case KEY_PASTE: nKeyCode = GDK_KEY_Paste; break;
2200 case KEY_OPEN: nKeyCode = GDK_KEY_Open; break;
2204 *pGdkKeyCode = nKeyCode;
2207 OUString GtkSalFrame::GetKeyName( sal_uInt16 nKeyCode )
2209 guint nGtkKeyCode;
2210 GdkModifierType nGtkModifiers;
2211 KeyCodeToGdkKey(nKeyCode, &nGtkKeyCode, &nGtkModifiers );
2213 gchar* pName = gtk_accelerator_get_label(nGtkKeyCode, nGtkModifiers);
2214 OUString aRet(pName, rtl_str_getLength(pName), RTL_TEXTENCODING_UTF8);
2215 g_free(pName);
2216 return aRet;
2219 GdkDisplay *GtkSalFrame::getGdkDisplay()
2221 return GetGtkSalData()->GetGdkDisplay();
2224 GtkSalDisplay *GtkSalFrame::getDisplay()
2226 return GetGtkSalData()->GetGtkDisplay();
2229 SalFrame::SalPointerState GtkSalFrame::GetPointerState()
2231 SalPointerState aState;
2232 GdkScreen* pScreen;
2233 gint x, y;
2234 GdkModifierType aMask;
2235 gdk_display_get_pointer( getGdkDisplay(), &pScreen, &x, &y, &aMask );
2236 aState.maPos = Point( x - maGeometry.nX, y - maGeometry.nY );
2237 aState.mnState = GetMouseModCode( aMask );
2238 return aState;
2241 KeyIndicatorState GtkSalFrame::GetIndicatorState()
2243 KeyIndicatorState nState = KeyIndicatorState::NONE;
2245 GdkKeymap *pKeyMap = gdk_keymap_get_for_display(getGdkDisplay());
2247 if (gdk_keymap_get_caps_lock_state(pKeyMap))
2248 nState |= KeyIndicatorState::CAPSLOCK;
2249 if (gdk_keymap_get_num_lock_state(pKeyMap))
2250 nState |= KeyIndicatorState::NUMLOCK;
2251 if (gdk_keymap_get_scroll_lock_state(pKeyMap))
2252 nState |= KeyIndicatorState::SCROLLLOCK;
2254 return nState;
2257 void GtkSalFrame::SimulateKeyPress( sal_uInt16 nKeyCode )
2259 g_warning ("missing simulate keypress %d", nKeyCode);
2262 void GtkSalFrame::SetInputContext( SalInputContext* pContext )
2264 if( ! pContext )
2265 return;
2267 if( ! (pContext->mnOptions & InputContextFlags::Text) )
2268 return;
2270 // create a new im context
2271 if( ! m_pIMHandler )
2272 m_pIMHandler.reset( new IMHandler( this ) );
2275 void GtkSalFrame::EndExtTextInput( EndExtTextInputFlags nFlags )
2277 if( m_pIMHandler )
2278 m_pIMHandler->endExtTextInput( nFlags );
2281 bool GtkSalFrame::MapUnicodeToKeyCode( sal_Unicode , LanguageType , vcl::KeyCode& )
2283 // not supported yet
2284 return false;
2287 LanguageType GtkSalFrame::GetInputLanguage()
2289 return LANGUAGE_DONTKNOW;
2292 void GtkSalFrame::UpdateSettings( AllSettings& rSettings )
2294 if( ! m_pWindow )
2295 return;
2297 GtkSalGraphics* pGraphics = m_pGraphics.get();
2298 bool bFreeGraphics = false;
2299 if( ! pGraphics )
2301 pGraphics = static_cast<GtkSalGraphics*>(AcquireGraphics());
2302 if ( !pGraphics )
2304 SAL_WARN("vcl.gtk3", "Could not get graphics - unable to update settings");
2305 return;
2307 bFreeGraphics = true;
2310 pGraphics->UpdateSettings( rSettings );
2312 if( bFreeGraphics )
2313 ReleaseGraphics( pGraphics );
2316 void GtkSalFrame::Beep()
2318 gdk_display_beep( getGdkDisplay() );
2321 const SystemEnvData* GtkSalFrame::GetSystemData() const
2323 return &m_aSystemData;
2326 void GtkSalFrame::ResolveWindowHandle(SystemEnvData& rData) const
2328 if (!rData.pWidget)
2329 return;
2330 SAL_WARN("vcl.gtk3", "its undesirable to need the NativeWindowHandle, see tdf#139609");
2331 rData.SetWindowHandle(GetNativeWindowHandle(static_cast<GtkWidget*>(rData.pWidget)));
2334 void GtkSalFrame::SetParent( SalFrame* pNewParent )
2336 GtkWindow* pWindow = GTK_IS_WINDOW(m_pWindow) ? GTK_WINDOW(m_pWindow) : nullptr;
2337 if (m_pParent)
2339 if (pWindow && GTK_IS_WINDOW(m_pParent->m_pWindow))
2340 gtk_window_group_remove_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), pWindow);
2341 m_pParent->m_aChildren.remove(this);
2343 m_pParent = static_cast<GtkSalFrame*>(pNewParent);
2344 if (m_pParent)
2346 m_pParent->m_aChildren.push_back(this);
2347 if (pWindow && GTK_IS_WINDOW(m_pParent->m_pWindow))
2348 gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), pWindow);
2350 if (!isChild() && pWindow)
2351 gtk_window_set_transient_for( pWindow,
2352 (m_pParent && ! m_pParent->isChild(true,false)) ? GTK_WINDOW(m_pParent->m_pWindow) : nullptr
2356 bool GtkSalFrame::SetPluginParent( SystemParentData* )
2358 //FIXME: no SetPluginParent impl. for gtk3
2359 return false;
2362 void GtkSalFrame::ResetClipRegion()
2364 if( m_pWindow )
2365 gdk_window_shape_combine_region( gtk_widget_get_window( m_pWindow ), nullptr, 0, 0 );
2368 void GtkSalFrame::BeginSetClipRegion( sal_uInt32 )
2370 if( m_pRegion )
2371 cairo_region_destroy( m_pRegion );
2372 m_pRegion = cairo_region_create();
2375 void GtkSalFrame::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
2377 if( m_pRegion )
2379 GdkRectangle aRect;
2380 aRect.x = nX;
2381 aRect.y = nY;
2382 aRect.width = nWidth;
2383 aRect.height = nHeight;
2384 cairo_region_union_rectangle( m_pRegion, &aRect );
2388 void GtkSalFrame::EndSetClipRegion()
2390 if( m_pWindow && m_pRegion )
2391 gdk_window_shape_combine_region( gtk_widget_get_window(m_pWindow), m_pRegion, 0, 0 );
2394 void GtkSalFrame::PositionByToolkit(const tools::Rectangle& rRect, FloatWinPopupFlags nFlags)
2396 if (ImplGetSVData()->maNWFData.mbCanDetermineWindowPosition)
2397 return;
2399 m_aFloatRect = rRect;
2400 m_nFloatFlags = nFlags;
2401 m_bFloatPositioned = true;
2404 void GtkSalFrame::SetModal(bool bModal)
2406 if (!m_pWindow)
2407 return;
2408 gtk_window_set_modal(GTK_WINDOW(m_pWindow), bModal);
2411 bool GtkSalFrame::GetModal() const
2413 if (!m_pWindow)
2414 return false;
2415 return gtk_window_get_modal(GTK_WINDOW(m_pWindow));
2418 gboolean GtkSalFrame::signalTooltipQuery(GtkWidget*, gint /*x*/, gint /*y*/,
2419 gboolean /*keyboard_mode*/, GtkTooltip *tooltip,
2420 gpointer frame)
2422 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
2423 if (pThis->m_aTooltip.isEmpty() || pThis->m_bTooltipBlocked)
2424 return false;
2425 gtk_tooltip_set_text(tooltip,
2426 OUStringToOString(pThis->m_aTooltip, RTL_TEXTENCODING_UTF8).getStr());
2427 GdkRectangle aHelpArea;
2428 aHelpArea.x = pThis->m_aHelpArea.Left();
2429 aHelpArea.y = pThis->m_aHelpArea.Top();
2430 aHelpArea.width = pThis->m_aHelpArea.GetWidth();
2431 aHelpArea.height = pThis->m_aHelpArea.GetHeight();
2432 if (AllSettings::GetLayoutRTL())
2433 aHelpArea.x = pThis->maGeometry.nWidth-aHelpArea.width-1-aHelpArea.x;
2434 gtk_tooltip_set_tip_area(tooltip, &aHelpArea);
2435 return true;
2438 bool GtkSalFrame::ShowTooltip(const OUString& rHelpText, const tools::Rectangle& rHelpArea)
2440 m_aTooltip = rHelpText;
2441 m_aHelpArea = rHelpArea;
2442 gtk_widget_trigger_tooltip_query(getMouseEventWidget());
2443 return true;
2446 void GtkSalFrame::BlockTooltip()
2448 m_bTooltipBlocked = true;
2451 void GtkSalFrame::UnblockTooltip()
2453 m_bTooltipBlocked = false;
2456 void GtkSalFrame::HideTooltip()
2458 m_aTooltip.clear();
2459 GtkWidget* pEventWidget = getMouseEventWidget();
2460 gtk_widget_trigger_tooltip_query(pEventWidget);
2463 namespace
2465 void set_pointing_to(GtkPopover *pPopOver, vcl::Window* pParent, const tools::Rectangle& rHelpArea, const SalFrameGeometry& rGeometry)
2467 GdkRectangle aRect;
2468 aRect.x = FloatingWindow::ImplConvertToAbsPos(pParent, rHelpArea).Left() - rGeometry.nX;
2469 aRect.y = rHelpArea.Top();
2470 aRect.width = 1;
2471 aRect.height = 1;
2473 GtkPositionType ePos = gtk_popover_get_position(pPopOver);
2474 switch (ePos)
2476 case GTK_POS_BOTTOM:
2477 case GTK_POS_TOP:
2478 aRect.width = rHelpArea.GetWidth();
2479 break;
2480 case GTK_POS_RIGHT:
2481 case GTK_POS_LEFT:
2482 aRect.height = rHelpArea.GetHeight();
2483 break;
2486 gtk_popover_set_pointing_to(pPopOver, &aRect);
2490 void* GtkSalFrame::ShowPopover(const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea, QuickHelpFlags nFlags)
2492 GtkWidget *pWidget = gtk_popover_new(getMouseEventWidget());
2493 OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
2494 GtkWidget *pLabel = gtk_label_new(sUTF.getStr());
2495 gtk_container_add(GTK_CONTAINER(pWidget), pLabel);
2497 if (nFlags & QuickHelpFlags::Top)
2498 gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_BOTTOM);
2499 else if (nFlags & QuickHelpFlags::Bottom)
2500 gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_TOP);
2501 else if (nFlags & QuickHelpFlags::Left)
2502 gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_RIGHT);
2503 else if (nFlags & QuickHelpFlags::Right)
2504 gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_LEFT);
2506 set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry);
2508 gtk_popover_set_modal(GTK_POPOVER(pWidget), false);
2510 gtk_widget_show_all(pWidget);
2512 return pWidget;
2515 bool GtkSalFrame::UpdatePopover(void* nId, const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea)
2517 GtkWidget *pWidget = static_cast<GtkWidget*>(nId);
2519 set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry);
2521 GtkWidget *pLabel = gtk_bin_get_child(GTK_BIN(pWidget));
2522 OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
2523 gtk_label_set_text(GTK_LABEL(pLabel), sUTF.getStr());
2525 return true;
2528 bool GtkSalFrame::HidePopover(void* nId)
2530 GtkWidget *pWidget = static_cast<GtkWidget*>(nId);
2531 gtk_widget_destroy(pWidget);
2532 return true;
2535 void GtkSalFrame::addGrabLevel()
2537 if (m_nGrabLevel == 0)
2538 gtk_grab_add(getMouseEventWidget());
2539 ++m_nGrabLevel;
2542 void GtkSalFrame::removeGrabLevel()
2544 if (m_nGrabLevel > 0)
2546 --m_nGrabLevel;
2547 if (m_nGrabLevel == 0)
2548 gtk_grab_remove(getMouseEventWidget());
2552 void GtkSalFrame::closePopup()
2554 if (!m_nFloats)
2555 return;
2556 ImplSVData* pSVData = ImplGetSVData();
2557 if (!pSVData->mpWinData->mpFirstFloat)
2558 return;
2559 if (pSVData->mpWinData->mpFirstFloat->ImplGetFrame() != this)
2560 return;
2561 pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll);
2564 namespace
2566 //tdf#117981 translate embedded video window mouse events to parent coordinates
2567 void translate_coords(GdkWindow* pSourceWindow, GtkWidget* pTargetWidget, int& rEventX, int& rEventY)
2569 gpointer user_data=nullptr;
2570 gdk_window_get_user_data(pSourceWindow, &user_data);
2571 GtkWidget* pRealEventWidget = static_cast<GtkWidget*>(user_data);
2572 if (pRealEventWidget)
2574 gtk_widget_translate_coordinates(pRealEventWidget, pTargetWidget, rEventX, rEventY, &rEventX, &rEventY);
2579 void GtkSalFrame::GrabFocus()
2581 GtkWidget* pGrabWidget;
2582 if (GTK_IS_EVENT_BOX(m_pWindow))
2583 pGrabWidget = GTK_WIDGET(m_pWindow);
2584 else
2585 pGrabWidget = GTK_WIDGET(m_pFixedContainer);
2586 // m_nSetFocusSignalId is 0 for the DisallowCycleFocusOut case where
2587 // we don't allow focus to enter the toplevel, but expect it to
2588 // stay in some embedded native gtk widget
2589 if (!gtk_widget_get_can_focus(pGrabWidget) && m_nSetFocusSignalId)
2590 gtk_widget_set_can_focus(pGrabWidget, true);
2591 if (!gtk_widget_has_focus(pGrabWidget))
2593 gtk_widget_grab_focus(pGrabWidget);
2594 if (m_pIMHandler)
2595 m_pIMHandler->focusChanged(true);
2599 gboolean GtkSalFrame::signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer frame)
2601 UpdateLastInputEventTime(pEvent->time);
2603 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
2604 GtkWidget* pEventWidget = pThis->getMouseEventWidget();
2605 bool bDifferentEventWindow = pEvent->window != gtk_widget_get_window(pEventWidget);
2607 if (pEvent->type == GDK_BUTTON_PRESS)
2609 // tdf#120764 It isn't allowed under wayland to have two visible popups that share
2610 // the same top level parent. The problem is that since gtk 3.24 tooltips are also
2611 // implemented as popups, which means that we cannot show any popup if there is a
2612 // visible tooltip. In fact, gtk will hide the tooltip by itself after this handler,
2613 // in case of a button press event. But if we intend to show a popup on button press
2614 // it will be too late, so just do it here:
2615 pThis->HideTooltip();
2617 // focus on click
2618 if (!bDifferentEventWindow)
2619 pThis->GrabFocus();
2622 SalMouseEvent aEvent;
2623 SalEvent nEventType = SalEvent::NONE;
2624 switch( pEvent->type )
2626 case GDK_BUTTON_PRESS:
2627 nEventType = SalEvent::MouseButtonDown;
2628 break;
2629 case GDK_BUTTON_RELEASE:
2630 nEventType = SalEvent::MouseButtonUp;
2631 break;
2632 default:
2633 return false;
2635 switch( pEvent->button )
2637 case 1: aEvent.mnButton = MOUSE_LEFT; break;
2638 case 2: aEvent.mnButton = MOUSE_MIDDLE; break;
2639 case 3: aEvent.mnButton = MOUSE_RIGHT; break;
2640 default: return false;
2643 vcl::DeletionListener aDel( pThis );
2645 if (pThis->isFloatGrabWindow())
2647 //rhbz#1505379 if the window that got the event isn't our one, or there's none
2648 //of our windows under the mouse then close this popup window
2649 if (bDifferentEventWindow ||
2650 gdk_device_get_window_at_position(pEvent->device, nullptr, nullptr) == nullptr)
2652 if (pEvent->type == GDK_BUTTON_PRESS)
2653 pThis->closePopup();
2654 else if (pEvent->type == GDK_BUTTON_RELEASE)
2655 return true;
2659 int nEventX = pEvent->x;
2660 int nEventY = pEvent->y;
2662 if (bDifferentEventWindow)
2663 translate_coords(pEvent->window, pEventWidget, nEventX, nEventY);
2665 if (!aDel.isDeleted())
2667 int frame_x = static_cast<int>(pEvent->x_root - nEventX);
2668 int frame_y = static_cast<int>(pEvent->y_root - nEventY);
2669 if (pThis->m_bGeometryIsProvisional || frame_x != pThis->maGeometry.nX || frame_y != pThis->maGeometry.nY)
2671 pThis->m_bGeometryIsProvisional = false;
2672 pThis->maGeometry.nX = frame_x;
2673 pThis->maGeometry.nY = frame_y;
2674 ImplSVData* pSVData = ImplGetSVData();
2675 if (pSVData->maNWFData.mbCanDetermineWindowPosition)
2676 pThis->CallCallbackExc(SalEvent::Move, nullptr);
2680 if (!aDel.isDeleted())
2682 aEvent.mnTime = pEvent->time;
2683 aEvent.mnX = static_cast<tools::Long>(pEvent->x_root) - pThis->maGeometry.nX;
2684 aEvent.mnY = static_cast<tools::Long>(pEvent->y_root) - pThis->maGeometry.nY;
2685 aEvent.mnCode = GetMouseModCode( pEvent->state );
2687 if( AllSettings::GetLayoutRTL() )
2688 aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;
2690 pThis->CallCallbackExc( nEventType, &aEvent );
2693 return true;
2696 void GtkSalFrame::LaunchAsyncScroll(GdkEvent const * pEvent)
2698 //if we don't match previous pending states, flush that queue now
2699 if (!m_aPendingScrollEvents.empty() && pEvent->scroll.state != m_aPendingScrollEvents.back()->scroll.state)
2701 m_aSmoothScrollIdle.Stop();
2702 m_aSmoothScrollIdle.Invoke();
2703 assert(m_aPendingScrollEvents.empty());
2705 //add scroll event to queue
2706 m_aPendingScrollEvents.push_back(gdk_event_copy(pEvent));
2707 if (!m_aSmoothScrollIdle.IsActive())
2708 m_aSmoothScrollIdle.Start();
2711 IMPL_LINK_NOARG(GtkSalFrame, AsyncScroll, Timer *, void)
2713 assert(!m_aPendingScrollEvents.empty());
2715 SalWheelMouseEvent aEvent;
2717 GdkEvent* pEvent = m_aPendingScrollEvents.back();
2719 aEvent.mnTime = pEvent->scroll.time;
2720 aEvent.mnX = static_cast<sal_uLong>(pEvent->scroll.x);
2721 // --- RTL --- (mirror mouse pos)
2722 if (AllSettings::GetLayoutRTL())
2723 aEvent.mnX = maGeometry.nWidth - 1 - aEvent.mnX;
2724 aEvent.mnY = static_cast<sal_uLong>(pEvent->scroll.y);
2725 aEvent.mnCode = GetMouseModCode( pEvent->scroll.state );
2727 double delta_x(0.0), delta_y(0.0);
2728 for (auto pSubEvent : m_aPendingScrollEvents)
2730 delta_x += pSubEvent->scroll.delta_x;
2731 delta_y += pSubEvent->scroll.delta_y;
2732 gdk_event_free(pSubEvent);
2734 m_aPendingScrollEvents.clear();
2736 // rhbz#1344042 "Traditionally" in gtk3 we tool a single up/down event as
2737 // equating to 3 scroll lines and a delta of 120. So scale the delta here
2738 // by 120 where a single mouse wheel click is an incoming delta_x of 1
2739 // and divide that by 40 to get the number of scroll lines
2740 if (delta_x != 0.0)
2742 aEvent.mnDelta = -delta_x * 120;
2743 aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
2744 if (aEvent.mnDelta == 0)
2745 aEvent.mnDelta = aEvent.mnNotchDelta;
2746 aEvent.mbHorz = true;
2747 aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0;
2748 CallCallbackExc(SalEvent::WheelMouse, &aEvent);
2751 if (delta_y != 0.0)
2753 aEvent.mnDelta = -delta_y * 120;
2754 aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
2755 if (aEvent.mnDelta == 0)
2756 aEvent.mnDelta = aEvent.mnNotchDelta;
2757 aEvent.mbHorz = false;
2758 aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0;
2759 CallCallbackExc(SalEvent::WheelMouse, &aEvent);
2763 SalWheelMouseEvent GtkSalFrame::GetWheelEvent(const GdkEventScroll& rEvent)
2765 SalWheelMouseEvent aEvent;
2767 aEvent.mnTime = rEvent.time;
2768 aEvent.mnX = static_cast<sal_uLong>(rEvent.x);
2769 aEvent.mnY = static_cast<sal_uLong>(rEvent.y);
2770 aEvent.mnCode = GetMouseModCode(rEvent.state);
2772 switch (rEvent.direction)
2774 case GDK_SCROLL_UP:
2775 aEvent.mnDelta = 120;
2776 aEvent.mnNotchDelta = 1;
2777 aEvent.mnScrollLines = 3;
2778 aEvent.mbHorz = false;
2779 break;
2781 case GDK_SCROLL_DOWN:
2782 aEvent.mnDelta = -120;
2783 aEvent.mnNotchDelta = -1;
2784 aEvent.mnScrollLines = 3;
2785 aEvent.mbHorz = false;
2786 break;
2788 case GDK_SCROLL_LEFT:
2789 aEvent.mnDelta = 120;
2790 aEvent.mnNotchDelta = 1;
2791 aEvent.mnScrollLines = 3;
2792 aEvent.mbHorz = true;
2793 break;
2795 case GDK_SCROLL_RIGHT:
2796 aEvent.mnDelta = -120;
2797 aEvent.mnNotchDelta = -1;
2798 aEvent.mnScrollLines = 3;
2799 aEvent.mbHorz = true;
2800 break;
2801 default:
2802 break;
2805 return aEvent;
2808 gboolean GtkSalFrame::signalScroll(GtkWidget*, GdkEvent* pInEvent, gpointer frame)
2810 GdkEventScroll& rEvent = pInEvent->scroll;
2812 UpdateLastInputEventTime(rEvent.time);
2814 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
2816 if (rEvent.direction == GDK_SCROLL_SMOOTH)
2818 pThis->LaunchAsyncScroll(pInEvent);
2819 return true;
2822 //if we have smooth scrolling previous pending states, flush that queue now
2823 if (!pThis->m_aPendingScrollEvents.empty())
2825 pThis->m_aSmoothScrollIdle.Stop();
2826 pThis->m_aSmoothScrollIdle.Invoke();
2827 assert(pThis->m_aPendingScrollEvents.empty());
2830 SalWheelMouseEvent aEvent(GetWheelEvent(rEvent));
2832 // --- RTL --- (mirror mouse pos)
2833 if (AllSettings::GetLayoutRTL())
2834 aEvent.mnX = pThis->maGeometry.nWidth - 1 - aEvent.mnX;
2836 pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
2838 return true;
2841 void GtkSalFrame::gestureSwipe(GtkGestureSwipe* gesture, gdouble velocity_x, gdouble velocity_y, gpointer frame)
2843 gdouble x, y;
2844 GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
2845 //I feel I want the first point of the sequence, not the last point which
2846 //the docs say this gives, but for the moment assume we start and end
2847 //within the same vcl window
2848 if (gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y))
2850 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
2852 SalSwipeEvent aEvent;
2853 aEvent.mnVelocityX = velocity_x;
2854 aEvent.mnVelocityY = velocity_y;
2855 aEvent.mnX = x;
2856 aEvent.mnY = y;
2858 pThis->CallCallbackExc(SalEvent::Swipe, &aEvent);
2862 void GtkSalFrame::gestureLongPress(GtkGestureLongPress* gesture, gdouble x, gdouble y, gpointer frame)
2864 GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
2865 if (gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y))
2867 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
2869 SalLongPressEvent aEvent;
2870 aEvent.mnX = x;
2871 aEvent.mnY = y;
2873 pThis->CallCallbackExc(SalEvent::LongPress, &aEvent);
2877 gboolean GtkSalFrame::signalMotion( GtkWidget*, GdkEventMotion* pEvent, gpointer frame )
2879 UpdateLastInputEventTime(pEvent->time);
2881 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
2882 GtkWidget* pEventWidget = pThis->getMouseEventWidget();
2883 bool bDifferentEventWindow = pEvent->window != gtk_widget_get_window(pEventWidget);
2885 //If a menu, e.g. font name dropdown, is open, then under wayland moving the
2886 //mouse in the top left corner of the toplevel window in a
2887 //0,0,float-width,float-height area generates motion events which are
2888 //delivered to the dropdown
2889 if (pThis->isFloatGrabWindow() && bDifferentEventWindow)
2890 return true;
2892 vcl::DeletionListener aDel( pThis );
2894 int nEventX = pEvent->x;
2895 int nEventY = pEvent->y;
2897 if (bDifferentEventWindow)
2898 translate_coords(pEvent->window, pEventWidget, nEventX, nEventY);
2900 int frame_x = static_cast<int>(pEvent->x_root - nEventX);
2901 int frame_y = static_cast<int>(pEvent->y_root - nEventY);
2903 if (pThis->m_bGeometryIsProvisional || frame_x != pThis->maGeometry.nX || frame_y != pThis->maGeometry.nY)
2905 pThis->m_bGeometryIsProvisional = false;
2906 pThis->maGeometry.nX = frame_x;
2907 pThis->maGeometry.nY = frame_y;
2908 ImplSVData* pSVData = ImplGetSVData();
2909 if (pSVData->maNWFData.mbCanDetermineWindowPosition)
2910 pThis->CallCallbackExc(SalEvent::Move, nullptr);
2913 if (!aDel.isDeleted())
2915 SalMouseEvent aEvent;
2916 aEvent.mnTime = pEvent->time;
2917 aEvent.mnX = static_cast<tools::Long>(pEvent->x_root) - pThis->maGeometry.nX;
2918 aEvent.mnY = static_cast<tools::Long>(pEvent->y_root) - pThis->maGeometry.nY;
2919 aEvent.mnCode = GetMouseModCode( pEvent->state );
2920 aEvent.mnButton = 0;
2922 if( AllSettings::GetLayoutRTL() )
2923 aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;
2925 pThis->CallCallbackExc( SalEvent::MouseMove, &aEvent );
2928 if (!aDel.isDeleted())
2930 // ask for the next hint
2931 gint x, y;
2932 GdkModifierType mask;
2933 gdk_window_get_pointer( gtk_widget_get_window(GTK_WIDGET(pThis->m_pWindow)), &x, &y, &mask );
2936 return true;
2939 gboolean GtkSalFrame::signalCrossing( GtkWidget*, GdkEventCrossing* pEvent, gpointer frame )
2941 UpdateLastInputEventTime(pEvent->time);
2943 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
2944 SalMouseEvent aEvent;
2945 aEvent.mnTime = pEvent->time;
2946 aEvent.mnX = static_cast<tools::Long>(pEvent->x_root) - pThis->maGeometry.nX;
2947 aEvent.mnY = static_cast<tools::Long>(pEvent->y_root) - pThis->maGeometry.nY;
2948 aEvent.mnCode = GetMouseModCode( pEvent->state );
2949 aEvent.mnButton = 0;
2951 if (AllSettings::GetLayoutRTL())
2952 aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;
2954 pThis->CallCallbackExc( (pEvent->type == GDK_ENTER_NOTIFY) ? SalEvent::MouseMove : SalEvent::MouseLeave, &aEvent );
2956 return true;
2959 cairo_t* GtkSalFrame::getCairoContext() const
2961 cairo_t* cr = cairo_create(m_pSurface);
2962 assert(cr);
2963 return cr;
2966 void GtkSalFrame::damaged(sal_Int32 nExtentsX, sal_Int32 nExtentsY,
2967 sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight) const
2969 #if OSL_DEBUG_LEVEL > 0
2970 if (dumpframes)
2972 static int frame;
2973 OString tmp("/tmp/frame" + OString::number(frame++) + ".png");
2974 cairo_t* cr = getCairoContext();
2975 cairo_surface_write_to_png(cairo_get_target(cr), tmp.getStr());
2976 cairo_destroy(cr);
2978 #endif
2980 gtk_widget_queue_draw_area(GTK_WIDGET(m_pFixedContainer),
2981 nExtentsX, nExtentsY,
2982 nExtentsWidth, nExtentsHeight);
2985 // blit our backing cairo surface to the target cairo context
2986 gboolean GtkSalFrame::signalDraw(GtkWidget*, cairo_t *cr, gpointer frame)
2988 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
2990 cairo_set_source_surface(cr, pThis->m_pSurface, 0, 0);
2991 cairo_paint(cr);
2993 return false;
2996 void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, GdkRectangle *pAllocation, gpointer frame)
2998 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
2999 // ignore size-allocations that occur during configuring an embedded SalObject
3000 if (pThis->m_bSalObjectSetPosSize)
3001 return;
3002 pThis->maGeometry.nWidth = pAllocation->width;
3003 pThis->maGeometry.nHeight = pAllocation->height;
3004 bool bRealized = gtk_widget_get_realized(pWidget);
3005 if (bRealized)
3006 pThis->AllocateFrame();
3007 pThis->CallCallbackExc( SalEvent::Resize, nullptr );
3008 if (bRealized)
3009 pThis->TriggerPaintEvent();
3012 namespace {
3014 void swapDirection(GdkGravity& gravity)
3016 if (gravity == GDK_GRAVITY_NORTH_WEST)
3017 gravity = GDK_GRAVITY_NORTH_EAST;
3018 else if (gravity == GDK_GRAVITY_NORTH_EAST)
3019 gravity = GDK_GRAVITY_NORTH_WEST;
3020 else if (gravity == GDK_GRAVITY_SOUTH_WEST)
3021 gravity = GDK_GRAVITY_SOUTH_EAST;
3022 else if (gravity == GDK_GRAVITY_SOUTH_EAST)
3023 gravity = GDK_GRAVITY_SOUTH_WEST;
3028 void GtkSalFrame::signalRealize(GtkWidget*, gpointer frame)
3030 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3031 pThis->AllocateFrame();
3032 if (pThis->m_bSalObjectSetPosSize)
3033 return;
3034 pThis->TriggerPaintEvent();
3036 if (!pThis->m_bFloatPositioned)
3037 return;
3039 static auto window_move_to_rect = reinterpret_cast<void (*) (GdkWindow*, const GdkRectangle*, GdkGravity,
3040 GdkGravity, GdkAnchorHints, gint, gint)>(
3041 dlsym(nullptr, "gdk_window_move_to_rect"));
3042 if (!window_move_to_rect)
3043 return;
3045 GdkGravity rect_anchor = GDK_GRAVITY_SOUTH_WEST, menu_anchor = GDK_GRAVITY_NORTH_WEST;
3047 if (pThis->m_nFloatFlags & FloatWinPopupFlags::Left)
3049 rect_anchor = GDK_GRAVITY_NORTH_WEST;
3050 menu_anchor = GDK_GRAVITY_NORTH_EAST;
3052 else if (pThis->m_nFloatFlags & FloatWinPopupFlags::Up)
3054 rect_anchor = GDK_GRAVITY_NORTH_WEST;
3055 menu_anchor = GDK_GRAVITY_SOUTH_WEST;
3057 else if (pThis->m_nFloatFlags & FloatWinPopupFlags::Right)
3059 rect_anchor = GDK_GRAVITY_NORTH_EAST;
3062 VclPtr<vcl::Window> pVclParent = pThis->GetWindow()->GetParent();
3063 if (pVclParent->HasMirroredGraphics() && pVclParent->IsRTLEnabled())
3065 swapDirection(rect_anchor);
3066 swapDirection(menu_anchor);
3069 tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pVclParent, pThis->m_aFloatRect);
3070 if (gdk_window_get_window_type(gtk_widget_get_window(pThis->m_pParent->m_pWindow)) != GDK_WINDOW_TOPLEVEL)
3071 aFloatRect.Move(-pThis->m_pParent->maGeometry.nX, -pThis->m_pParent->maGeometry.nY);
3073 GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
3074 static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
3076 GdkWindow* gdkWindow = gtk_widget_get_window(pThis->m_pWindow);
3077 window_move_to_rect(gdkWindow, &rect, rect_anchor, menu_anchor, static_cast<GdkAnchorHints>(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE), 0, 0);
3080 gboolean GtkSalFrame::signalConfigure(GtkWidget*, GdkEventConfigure* pEvent, gpointer frame)
3082 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3084 bool bMoved = false;
3085 int x = pEvent->x, y = pEvent->y;
3087 /* #i31785# claims we cannot trust the x,y members of the event;
3088 * they are e.g. not set correctly on maximize/demaximize;
3089 * yet the gdkdisplay-x11.c code handling configure_events has
3090 * done this XTranslateCoordinates work since the day ~zero.
3092 if (pThis->m_bGeometryIsProvisional || x != pThis->maGeometry.nX || y != pThis->maGeometry.nY )
3094 bMoved = true;
3095 pThis->m_bGeometryIsProvisional = false;
3096 pThis->maGeometry.nX = x;
3097 pThis->maGeometry.nY = y;
3100 // update decoration hints
3101 GdkRectangle aRect;
3102 gdk_window_get_frame_extents( gtk_widget_get_window(GTK_WIDGET(pThis->m_pWindow)), &aRect );
3103 pThis->maGeometry.nTopDecoration = y - aRect.y;
3104 pThis->maGeometry.nBottomDecoration = aRect.y + aRect.height - y - pEvent->height;
3105 pThis->maGeometry.nLeftDecoration = x - aRect.x;
3106 pThis->maGeometry.nRightDecoration = aRect.x + aRect.width - x - pEvent->width;
3107 pThis->updateScreenNumber();
3109 if (bMoved)
3111 ImplSVData* pSVData = ImplGetSVData();
3112 if (pSVData->maNWFData.mbCanDetermineWindowPosition)
3113 pThis->CallCallbackExc(SalEvent::Move, nullptr);
3116 return false;
3119 void GtkSalFrame::TriggerPaintEvent()
3121 //Under gtk2 we can basically paint directly into the XWindow and on
3122 //additional "expose-event" events we can re-render the missing pieces
3124 //Under gtk3 we have to keep our own buffer up to date and flush it into
3125 //the given cairo context on "draw". So we emit a paint event on
3126 //opportune resize trigger events to initially fill our backbuffer and then
3127 //keep it up to date with our direct paints and tell gtk those regions
3128 //have changed and then blit them into the provided cairo context when
3129 //we get the "draw"
3131 //The other alternative was to always paint everything on "draw", but
3132 //that duplicates the amount of drawing and is hideously slow
3133 SAL_INFO("vcl.gtk3", "force painting" << 0 << "," << 0 << " " << maGeometry.nWidth << "x" << maGeometry.nHeight);
3134 SalPaintEvent aPaintEvt(0, 0, maGeometry.nWidth, maGeometry.nHeight, true);
3135 CallCallbackExc(SalEvent::Paint, &aPaintEvt);
3136 gtk_widget_queue_draw(GTK_WIDGET(m_pFixedContainer));
3139 gboolean GtkSalFrame::signalFocus( GtkWidget*, GdkEventFocus* pEvent, gpointer frame )
3141 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3143 SalGenericInstance *pSalInstance =
3144 static_cast< SalGenericInstance* >(GetSalData()->m_pInstance);
3146 // check if printers have changed (analogous to salframe focus handler)
3147 pSalInstance->updatePrinterUpdate();
3149 if( !pEvent->in )
3150 pThis->m_nKeyModifiers = ModKeyFlags::NONE;
3152 if( pThis->m_pIMHandler )
3154 bool bFocusInAnotherGtkWidget = false;
3155 if (GTK_IS_WINDOW(pThis->m_pWindow))
3157 GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
3158 bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(pThis->m_pFixedContainer);
3160 if (!bFocusInAnotherGtkWidget)
3161 pThis->m_pIMHandler->focusChanged( pEvent->in != 0 );
3164 // ask for changed printers like generic implementation
3165 if( pEvent->in && pSalInstance->isPrinterInit() )
3166 pSalInstance->updatePrinterUpdate();
3168 // FIXME: find out who the hell steals the focus from our frame
3169 // while we have the pointer grabbed, this should not come from
3170 // the window manager. Is this an event that was still queued ?
3171 // The focus does not seem to get set inside our process
3172 // in the meantime do not propagate focus get/lose if floats are open
3173 if( m_nFloats == 0 )
3175 GtkWidget* pGrabWidget;
3176 if (GTK_IS_EVENT_BOX(pThis->m_pWindow))
3177 pGrabWidget = GTK_WIDGET(pThis->m_pWindow);
3178 else
3179 pGrabWidget = GTK_WIDGET(pThis->m_pFixedContainer);
3180 bool bHasFocus = gtk_widget_has_focus(pGrabWidget);
3181 pThis->CallCallbackExc(bHasFocus ? SalEvent::GetFocus : SalEvent::LoseFocus, nullptr);
3184 return false;
3187 // change of focus between native widgets within the toplevel
3188 void GtkSalFrame::signalSetFocus(GtkWindow*, GtkWidget* pWidget, gpointer frame)
3190 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3192 GtkWidget* pGrabWidget;
3193 if (GTK_IS_EVENT_BOX(pThis->m_pWindow))
3194 pGrabWidget = GTK_WIDGET(pThis->m_pWindow);
3195 else
3196 pGrabWidget = GTK_WIDGET(pThis->m_pFixedContainer);
3198 // tdf#129634 interpret losing focus as focus passing explicitly to another widget
3199 bool bLoseFocus = pWidget && pWidget != pGrabWidget;
3201 // do not propagate focus get/lose if floats are open
3202 pThis->CallCallbackExc(bLoseFocus ? SalEvent::LoseFocus : SalEvent::GetFocus, nullptr);
3204 gtk_widget_set_can_focus(GTK_WIDGET(pThis->m_pFixedContainer), !bLoseFocus);
3207 gboolean GtkSalFrame::signalMap(GtkWidget *, GdkEvent*, gpointer frame)
3209 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3211 if (pThis->m_bIconSetWhileUnmapped)
3212 pThis->SetIcon(gtk_window_get_icon_name(GTK_WINDOW(pThis->m_pWindow)));
3214 pThis->CallCallbackExc( SalEvent::Resize, nullptr );
3215 pThis->TriggerPaintEvent();
3217 return false;
3220 gboolean GtkSalFrame::signalUnmap( GtkWidget*, GdkEvent*, gpointer frame )
3222 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3224 pThis->CallCallbackExc( SalEvent::Resize, nullptr );
3226 if (pThis->m_bFloatPositioned)
3228 // Unrealize is needed for cases where we reuse the same popup
3229 // (e.g. the font name control), making the realize signal fire
3230 // again on next show.
3231 gtk_widget_unrealize(pThis->m_pWindow);
3232 pThis->m_bFloatPositioned = false;
3235 return false;
3238 gboolean GtkSalFrame::signalKey(GtkWidget* pWidget, GdkEventKey* pEvent, gpointer frame)
3240 UpdateLastInputEventTime(pEvent->time);
3242 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3244 bool bFocusInAnotherGtkWidget = false;
3246 VclPtr<vcl::Window> xTopLevelInterimWindow;
3248 if (GTK_IS_WINDOW(pThis->m_pWindow))
3250 GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
3251 bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(pThis->m_pFixedContainer);
3252 if (bFocusInAnotherGtkWidget)
3254 if (!gtk_widget_get_realized(pFocusWindow))
3255 return true;
3256 gpointer pClass = g_type_class_ref(GTK_TYPE_WINDOW);
3257 GtkWidgetClass* pWindowClass = GTK_WIDGET_CLASS(pClass);
3258 // if the focus is not in our main widget, see if there is a handler
3259 // for this key stroke in GtkWindow first
3260 bool bHandled = pEvent->type == GDK_KEY_PRESS
3261 ? pWindowClass->key_press_event(pThis->m_pWindow, pEvent)
3262 : pWindowClass->key_release_event(pThis->m_pWindow, pEvent);
3263 g_type_class_unref(pClass);
3264 if (bHandled)
3265 return true;
3267 // Is focus inside an InterimItemWindow? In which case find that
3268 // InterimItemWindow and send unconsumed keystrokes to it to
3269 // support ctrl-q etc shortcuts. Only bother to search for the
3270 // InterimItemWindow if it is a toplevel that fills its frame, or
3271 // the keystroke is sufficiently special its worth passing on,
3272 // e.g. F6 to switch between task-panels or F5 to close a navigator
3273 if (pThis->IsCycleFocusOutDisallowed() || IsFunctionKeyVal(pEvent->keyval))
3275 GtkWidget* pSearch = pFocusWindow;
3276 while (pSearch)
3278 void* pData = g_object_get_data(G_OBJECT(pSearch), "InterimWindowGlue");
3279 if (pData)
3281 xTopLevelInterimWindow = static_cast<vcl::Window*>(pData);
3282 break;
3284 pSearch = gtk_widget_get_parent(pSearch);
3290 if (pThis->isFloatGrabWindow())
3291 return signalKey(pWidget, pEvent, pThis->m_pParent);
3293 vcl::DeletionListener aDel( pThis );
3295 if (!bFocusInAnotherGtkWidget && pThis->m_pIMHandler && pThis->m_pIMHandler->handleKeyEvent(pEvent))
3296 return true;
3298 bool bStopProcessingKey = false;
3300 // handle modifiers
3301 if( pEvent->keyval == GDK_KEY_Shift_L || pEvent->keyval == GDK_KEY_Shift_R ||
3302 pEvent->keyval == GDK_KEY_Control_L || pEvent->keyval == GDK_KEY_Control_R ||
3303 pEvent->keyval == GDK_KEY_Alt_L || pEvent->keyval == GDK_KEY_Alt_R ||
3304 pEvent->keyval == GDK_KEY_Meta_L || pEvent->keyval == GDK_KEY_Meta_R ||
3305 pEvent->keyval == GDK_KEY_Super_L || pEvent->keyval == GDK_KEY_Super_R )
3307 sal_uInt16 nModCode = GetKeyModCode( pEvent->state );
3308 ModKeyFlags nExtModMask = ModKeyFlags::NONE;
3309 sal_uInt16 nModMask = 0;
3310 // pressing just the ctrl key leads to a keysym of XK_Control but
3311 // the event state does not contain ControlMask. In the release
3312 // event it's the other way round: it does contain the Control mask.
3313 // The modifier mode therefore has to be adapted manually.
3314 switch( pEvent->keyval )
3316 case GDK_KEY_Control_L:
3317 nExtModMask = ModKeyFlags::LeftMod1;
3318 nModMask = KEY_MOD1;
3319 break;
3320 case GDK_KEY_Control_R:
3321 nExtModMask = ModKeyFlags::RightMod1;
3322 nModMask = KEY_MOD1;
3323 break;
3324 case GDK_KEY_Alt_L:
3325 nExtModMask = ModKeyFlags::LeftMod2;
3326 nModMask = KEY_MOD2;
3327 break;
3328 case GDK_KEY_Alt_R:
3329 nExtModMask = ModKeyFlags::RightMod2;
3330 nModMask = KEY_MOD2;
3331 break;
3332 case GDK_KEY_Shift_L:
3333 nExtModMask = ModKeyFlags::LeftShift;
3334 nModMask = KEY_SHIFT;
3335 break;
3336 case GDK_KEY_Shift_R:
3337 nExtModMask = ModKeyFlags::RightShift;
3338 nModMask = KEY_SHIFT;
3339 break;
3340 // Map Meta/Super to MOD3 modifier on all Unix systems
3341 // except macOS
3342 case GDK_KEY_Meta_L:
3343 case GDK_KEY_Super_L:
3344 nExtModMask = ModKeyFlags::LeftMod3;
3345 nModMask = KEY_MOD3;
3346 break;
3347 case GDK_KEY_Meta_R:
3348 case GDK_KEY_Super_R:
3349 nExtModMask = ModKeyFlags::RightMod3;
3350 nModMask = KEY_MOD3;
3351 break;
3354 SalKeyModEvent aModEvt;
3355 aModEvt.mbDown = pEvent->type == GDK_KEY_PRESS;
3357 if( pEvent->type == GDK_KEY_RELEASE )
3359 aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
3360 aModEvt.mnCode = nModCode & ~nModMask;
3361 pThis->m_nKeyModifiers &= ~nExtModMask;
3363 else
3365 aModEvt.mnCode = nModCode | nModMask;
3366 pThis->m_nKeyModifiers |= nExtModMask;
3367 aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
3370 pThis->CallCallbackExc( SalEvent::KeyModChange, &aModEvt );
3372 else
3374 VclPtr<vcl::Window> xOrigFocusWin;
3375 if (xTopLevelInterimWindow)
3377 // Focus is inside a full-app InterimItemWindow send unconsumed
3378 // keystrokes to by setting it as the mpFocusWin
3379 VclPtr<vcl::Window> xVclWindow = pThis->GetWindow();
3380 ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
3381 xOrigFocusWin = pFrameData->mpFocusWin;
3382 pFrameData->mpFocusWin = xTopLevelInterimWindow;
3383 if (pEvent->keyval == GDK_KEY_F6)
3385 // For F6, allow the focus to leave the InterimItemWindow
3386 pThis->AllowCycleFocusOut();
3390 bStopProcessingKey = pThis->doKeyCallback(pEvent->state,
3391 pEvent->keyval,
3392 pEvent->hardware_keycode,
3393 pEvent->group,
3394 sal_Unicode(gdk_keyval_to_unicode( pEvent->keyval )),
3395 (pEvent->type == GDK_KEY_PRESS),
3396 false);
3397 if (!aDel.isDeleted())
3399 pThis->m_nKeyModifiers = ModKeyFlags::NONE;
3401 if (xTopLevelInterimWindow)
3403 // Focus was inside a full-app InterimItemWindow, restore the original
3404 // focus win, unless the focus was changed away from the InterimItemWindow
3405 // which should only be possible with F6
3406 VclPtr<vcl::Window> xVclWindow = pThis->GetWindow();
3407 ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
3408 if (pFrameData->mpFocusWin == xTopLevelInterimWindow)
3409 pFrameData->mpFocusWin = xOrigFocusWin;
3410 if (pEvent->keyval == GDK_KEY_F6)
3412 // undo the above AllowCycleFocusOut for F6
3413 pThis->DisallowCycleFocusOut();
3420 if (!bFocusInAnotherGtkWidget && !aDel.isDeleted() && pThis->m_pIMHandler)
3421 pThis->m_pIMHandler->updateIMSpotLocation();
3423 return bStopProcessingKey;
3426 gboolean GtkSalFrame::signalDelete( GtkWidget*, GdkEvent*, gpointer frame )
3428 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3429 pThis->CallCallbackExc( SalEvent::Close, nullptr );
3430 return true;
3433 void GtkSalFrame::signalStyleUpdated(GtkWidget*, gpointer frame)
3435 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3437 // note: settings changed for multiple frames is avoided in winproc.cxx ImplHandleSettings
3438 GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::SettingsChanged );
3440 // fire off font-changed when the system cairo font hints change
3441 GtkInstance *pInstance = static_cast<GtkInstance*>(GetSalData()->m_pInstance);
3442 const cairo_font_options_t* pLastCairoFontOptions = pInstance->GetLastSeenCairoFontOptions();
3443 const cairo_font_options_t* pCurrentCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
3444 bool bFontSettingsChanged = true;
3445 if (pLastCairoFontOptions && pCurrentCairoFontOptions)
3446 bFontSettingsChanged = !cairo_font_options_equal(pLastCairoFontOptions, pCurrentCairoFontOptions);
3447 else if (!pLastCairoFontOptions && !pCurrentCairoFontOptions)
3448 bFontSettingsChanged = false;
3449 if (bFontSettingsChanged)
3451 pInstance->ResetLastSeenCairoFontOptions(pCurrentCairoFontOptions);
3452 GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::FontChanged );
3456 gboolean GtkSalFrame::signalWindowState( GtkWidget*, GdkEvent* pEvent, gpointer frame )
3458 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3459 if( (pThis->m_nState & GDK_WINDOW_STATE_ICONIFIED) != (pEvent->window_state.new_window_state & GDK_WINDOW_STATE_ICONIFIED ) )
3461 GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize );
3462 pThis->TriggerPaintEvent();
3465 if ((pEvent->window_state.new_window_state & GDK_WINDOW_STATE_MAXIMIZED) &&
3466 !(pThis->m_nState & GDK_WINDOW_STATE_MAXIMIZED))
3468 pThis->m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(pThis->m_pWindow));
3471 if ((pEvent->window_state.new_window_state & GDK_WINDOW_STATE_WITHDRAWN) &&
3472 !(pThis->m_nState & GDK_WINDOW_STATE_WITHDRAWN))
3474 if (pThis->isFloatGrabWindow())
3475 pThis->closePopup();
3478 pThis->m_nState = pEvent->window_state.new_window_state;
3480 #if OSL_DEBUG_LEVEL > 1
3481 SAL_INFO_IF((pEvent->window_state.changed_mask &
3482 GDK_WINDOW_STATE_FULLSCREEN),
3483 "vcl.gtk3", "window "
3484 << pThis
3485 << " "
3486 << ((pEvent->window_state.new_window_state &
3487 GDK_WINDOW_STATE_FULLSCREEN) ?
3488 "enters" :
3489 "leaves")
3490 << " full screen state.");
3491 #endif
3493 return false;
3496 gboolean GtkSalFrame::signalVisibility( GtkWidget*, GdkEventVisibility* /*pEvent*/, gpointer /*frame*/ )
3498 return true;
3501 namespace
3503 GdkDragAction VclToGdk(sal_Int8 dragOperation)
3505 GdkDragAction eRet(static_cast<GdkDragAction>(0));
3506 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
3507 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_COPY);
3508 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
3509 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_MOVE);
3510 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
3511 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_LINK);
3512 return eRet;
3515 sal_Int8 GdkToVcl(GdkDragAction dragOperation)
3517 sal_Int8 nRet(0);
3518 if (dragOperation & GDK_ACTION_COPY)
3519 nRet |= css::datatransfer::dnd::DNDConstants::ACTION_COPY;
3520 if (dragOperation & GDK_ACTION_MOVE)
3521 nRet |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
3522 if (dragOperation & GDK_ACTION_LINK)
3523 nRet |= css::datatransfer::dnd::DNDConstants::ACTION_LINK;
3524 return nRet;
3528 namespace
3530 GdkDragAction getPreferredDragAction(sal_Int8 dragOperation)
3532 GdkDragAction eAct(static_cast<GdkDragAction>(0));
3534 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
3535 eAct = GDK_ACTION_MOVE;
3536 else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
3537 eAct = GDK_ACTION_COPY;
3538 else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
3539 eAct = GDK_ACTION_LINK;
3541 return eAct;
3545 static bool g_DropSuccessSet = false;
3546 static bool g_DropSuccess = false;
3548 namespace {
3550 class GtkDropTargetDropContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDropContext>
3552 GdkDragContext *m_pContext;
3553 guint m_nTime;
3554 public:
3555 GtkDropTargetDropContext(GdkDragContext *pContext, guint nTime)
3556 : m_pContext(pContext)
3557 , m_nTime(nTime)
3561 // XDropTargetDropContext
3562 virtual void SAL_CALL acceptDrop(sal_Int8 dragOperation) override
3564 gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
3567 virtual void SAL_CALL rejectDrop() override
3569 gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
3572 virtual void SAL_CALL dropComplete(sal_Bool bSuccess) override
3574 gtk_drag_finish(m_pContext, bSuccess, false, m_nTime);
3575 if (GtkDragSource::g_ActiveDragSource)
3577 g_DropSuccessSet = true;
3578 g_DropSuccess = bSuccess;
3585 class GtkDnDTransferable : public GtkTransferable
3587 GdkDragContext *m_pContext;
3588 guint m_nTime;
3589 GtkWidget *m_pWidget;
3590 GtkDropTarget* m_pDropTarget;
3591 GMainLoop *m_pLoop;
3592 GtkSelectionData *m_pData;
3593 public:
3594 GtkDnDTransferable(GdkDragContext *pContext, guint nTime, GtkWidget *pWidget, GtkDropTarget *pDropTarget)
3595 : m_pContext(pContext)
3596 , m_nTime(nTime)
3597 , m_pWidget(pWidget)
3598 , m_pDropTarget(pDropTarget)
3599 , m_pLoop(nullptr)
3600 , m_pData(nullptr)
3604 virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override
3606 css::datatransfer::DataFlavor aFlavor(rFlavor);
3607 if (aFlavor.MimeType == "text/plain;charset=utf-16")
3608 aFlavor.MimeType = "text/plain;charset=utf-8";
3610 auto it = m_aMimeTypeToAtom.find(aFlavor.MimeType);
3611 if (it == m_aMimeTypeToAtom.end())
3612 return css::uno::Any();
3614 /* like gtk_clipboard_wait_for_contents run a sub loop
3615 * waiting for drag-data-received triggered from
3616 * gtk_drag_get_data
3619 m_pLoop = g_main_loop_new(nullptr, true);
3620 m_pDropTarget->SetFormatConversionRequest(this);
3622 gtk_drag_get_data(m_pWidget, m_pContext, it->second, m_nTime);
3624 if (g_main_loop_is_running(m_pLoop))
3626 gdk_threads_leave();
3627 g_main_loop_run(m_pLoop);
3628 gdk_threads_enter();
3631 g_main_loop_unref(m_pLoop);
3632 m_pLoop = nullptr;
3633 m_pDropTarget->SetFormatConversionRequest(nullptr);
3636 css::uno::Any aRet;
3638 if (aFlavor.MimeType == "text/plain;charset=utf-8")
3640 OUString aStr;
3641 gchar *pText = reinterpret_cast<gchar*>(gtk_selection_data_get_text(m_pData));
3642 if (pText)
3643 aStr = OUString(pText, rtl_str_getLength(pText), RTL_TEXTENCODING_UTF8);
3644 g_free(pText);
3645 aRet <<= aStr.replaceAll("\r\n", "\n");
3647 else
3649 gint length(0);
3650 const guchar *rawdata = gtk_selection_data_get_data_with_length(m_pData,
3651 &length);
3652 // seen here was rawhide == nullptr and length set to -1
3653 if (rawdata)
3655 css::uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
3656 aRet <<= aSeq;
3660 gtk_selection_data_free(m_pData);
3662 return aRet;
3665 virtual std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector() override
3667 std::vector<GdkAtom> targets;
3668 for (GList* l = gdk_drag_context_list_targets(m_pContext); l; l = l->next)
3669 targets.push_back(static_cast<GdkAtom>(l->data));
3670 return GtkTransferable::getTransferDataFlavorsAsVector(targets.data(), targets.size());
3673 void LoopEnd(GtkSelectionData *pData)
3675 m_pData = pData;
3676 g_main_loop_quit(m_pLoop);
3680 // For LibreOffice internal D&D we provide the Transferable without Gtk
3681 // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
3682 GtkDragSource* GtkDragSource::g_ActiveDragSource;
3684 gboolean GtkSalFrame::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time, gpointer frame)
3686 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3687 if (!pThis->m_pDropTarget)
3688 return false;
3689 return pThis->m_pDropTarget->signalDragDrop(pWidget, context, x, y, time);
3692 gboolean GtkDropTarget::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time)
3694 // remove the deferred dragExit, as we'll do a drop
3695 #ifndef NDEBUG
3696 bool res =
3697 #endif
3698 g_idle_remove_by_data(this);
3699 assert(res);
3701 css::datatransfer::dnd::DropTargetDropEvent aEvent;
3702 aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(this);
3703 aEvent.Context = new GtkDropTargetDropContext(context, time);
3704 aEvent.LocationX = x;
3705 aEvent.LocationY = y;
3706 aEvent.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
3707 // ACTION_DEFAULT is documented as...
3708 // 'This means the user did not press any key during the Drag and Drop operation
3709 // and the action that was combined with ACTION_DEFAULT is the system default action'
3710 // in tdf#107031 writer won't insert a link when a heading is dragged from the
3711 // navigator unless this is set. Its unclear really what ACTION_DEFAULT means,
3712 // there is a deprecated 'GDK_ACTION_DEFAULT Means nothing, and should not be used'
3713 // possible equivalent in gtk.
3714 // So (tdf#109227) set ACTION_DEFAULT if no modifier key is held down
3715 GdkModifierType mask;
3716 gdk_window_get_pointer(gtk_widget_get_window(pWidget), nullptr, nullptr, &mask);
3717 if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
3718 aEvent.DropAction |= css::datatransfer::dnd::DNDConstants::ACTION_DEFAULT;
3719 aEvent.SourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
3720 css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
3721 // For LibreOffice internal D&D we provide the Transferable without Gtk
3722 // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
3723 if (GtkDragSource::g_ActiveDragSource)
3724 xTransferable = GtkDragSource::g_ActiveDragSource->GetTransferrable();
3725 else
3726 xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
3727 aEvent.Transferable = xTransferable;
3729 fire_drop(aEvent);
3731 return true;
3734 namespace {
3736 class GtkDropTargetDragContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDragContext>
3738 GdkDragContext *m_pContext;
3739 guint m_nTime;
3740 public:
3741 GtkDropTargetDragContext(GdkDragContext *pContext, guint nTime)
3742 : m_pContext(pContext)
3743 , m_nTime(nTime)
3747 virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) override
3749 gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
3752 virtual void SAL_CALL rejectDrag() override
3754 gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
3760 void GtkSalFrame::signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time, gpointer frame)
3762 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3763 if (!pThis->m_pDropTarget)
3764 return;
3765 pThis->m_pDropTarget->signalDragDropReceived(pWidget, context, x, y, data, ttype, time);
3768 void GtkDropTarget::signalDragDropReceived(GtkWidget* /*pWidget*/, GdkDragContext * /*context*/, gint /*x*/, gint /*y*/, GtkSelectionData* data, guint /*ttype*/, guint /*time*/)
3771 * If we get a drop, then we will call like gtk_clipboard_wait_for_contents
3772 * with a loop inside a loop to get the right format, so if this is the
3773 * case return to the outer loop here with a copy of the desired data
3775 * don't look at me like that.
3777 if (!m_pFormatConversionRequest)
3778 return;
3780 m_pFormatConversionRequest->LoopEnd(gtk_selection_data_copy(data));
3783 gboolean GtkSalFrame::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time, gpointer frame)
3785 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3786 if (!pThis->m_pDropTarget)
3787 return false;
3788 return pThis->m_pDropTarget->signalDragMotion(pWidget, context, x, y, time);
3792 gboolean GtkDropTarget::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time)
3794 if (!m_bInDrag)
3795 gtk_drag_highlight(pWidget);
3797 css::datatransfer::dnd::DropTargetDragEnterEvent aEvent;
3798 aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(this);
3799 GtkDropTargetDragContext* pContext = new GtkDropTargetDragContext(context, time);
3800 //preliminary accept the Drag and select the preferred action, the fire_* will
3801 //inform the original caller of our choice and the callsite can decide
3802 //to overrule this choice. i.e. typically here we default to ACTION_MOVE
3803 sal_Int8 nSourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
3804 GdkModifierType mask;
3805 gdk_window_get_pointer(gtk_widget_get_window(pWidget), nullptr, nullptr, &mask);
3807 // tdf#124411 default to move if drag originates within LO itself, default
3808 // to copy if it comes from outside, this is similar to srcAndDestEqual
3809 // in macosx DropTarget::determineDropAction equivalent
3810 sal_Int8 nNewDropAction = GtkDragSource::g_ActiveDragSource ?
3811 css::datatransfer::dnd::DNDConstants::ACTION_MOVE :
3812 css::datatransfer::dnd::DNDConstants::ACTION_COPY;
3814 // tdf#109227 if a modifier is held down, default to the matching
3815 // action for that modifier combo, otherwise pick the preferred
3816 // default from the possible source actions
3817 if ((mask & GDK_SHIFT_MASK) && !(mask & GDK_CONTROL_MASK))
3818 nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
3819 else if ((mask & GDK_CONTROL_MASK) && !(mask & GDK_SHIFT_MASK))
3820 nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
3821 else if ((mask & GDK_SHIFT_MASK) && (mask & GDK_CONTROL_MASK) )
3822 nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_LINK;
3823 nNewDropAction &= nSourceActions;
3825 GdkDragAction eAction;
3826 if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) && !nNewDropAction)
3827 eAction = getPreferredDragAction(nSourceActions);
3828 else
3829 eAction = getPreferredDragAction(nNewDropAction);
3831 gdk_drag_status(context, eAction, time);
3832 aEvent.Context = pContext;
3833 aEvent.LocationX = x;
3834 aEvent.LocationY = y;
3835 //under wayland at least, the action selected by gdk_drag_status on the
3836 //context is not immediately available via gdk_drag_context_get_selected_action
3837 //so here we set the DropAction from what we selected on the context, not
3838 //what the context says is selected
3839 aEvent.DropAction = GdkToVcl(eAction);
3840 aEvent.SourceActions = nSourceActions;
3842 if (!m_bInDrag)
3844 css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
3845 // For LibreOffice internal D&D we provide the Transferable without Gtk
3846 // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
3847 if (GtkDragSource::g_ActiveDragSource)
3848 xTransferable = GtkDragSource::g_ActiveDragSource->GetTransferrable();
3849 else
3850 xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
3851 css::uno::Sequence<css::datatransfer::DataFlavor> aFormats = xTransferable->getTransferDataFlavors();
3852 aEvent.SupportedDataFlavors = aFormats;
3853 fire_dragEnter(aEvent);
3854 m_bInDrag = true;
3856 else
3858 fire_dragOver(aEvent);
3861 return true;
3864 void GtkSalFrame::signalDragLeave(GtkWidget *pWidget, GdkDragContext *context, guint time, gpointer frame)
3866 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3867 if (!pThis->m_pDropTarget)
3868 return;
3869 pThis->m_pDropTarget->signalDragLeave(pWidget, context, time);
3872 static gboolean lcl_deferred_dragExit(gpointer user_data)
3874 GtkDropTarget* pThis = static_cast<GtkDropTarget*>(user_data);
3875 css::datatransfer::dnd::DropTargetEvent aEvent;
3876 aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(pThis);
3877 pThis->fire_dragExit(aEvent);
3878 return FALSE;
3881 void GtkDropTarget::signalDragLeave(GtkWidget* pWidget, GdkDragContext* /*context*/, guint /*time*/)
3883 m_bInDrag = false;
3884 gtk_drag_unhighlight(pWidget);
3885 // defer fire_dragExit, since gtk also sends a drag-leave before the drop, while
3886 // LO expect to either handle the drop or the exit... at least in Writer.
3887 // but since we don't know there will be a drop following the leave, defer the
3888 // exit handling to an idle.
3889 g_idle_add(lcl_deferred_dragExit, this);
3892 void GtkSalFrame::signalDestroy( GtkWidget* pObj, gpointer frame )
3894 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3895 if( pObj != pThis->m_pWindow )
3896 return;
3898 pThis->m_aDamageHandler.damaged = nullptr;
3899 pThis->m_aDamageHandler.handle = nullptr;
3900 if (pThis->m_pSurface)
3901 cairo_surface_set_user_data(pThis->m_pSurface, SvpSalGraphics::getDamageKey(), nullptr, nullptr);
3902 pThis->m_pFixedContainer = nullptr;
3903 pThis->m_pEventBox = nullptr;
3904 pThis->m_pTopLevelGrid = nullptr;
3905 pThis->m_pWindow = nullptr;
3906 pThis->m_xFrameWeld.reset();
3907 pThis->InvalidateGraphics();
3910 // GtkSalFrame::IMHandler
3912 GtkSalFrame::IMHandler::IMHandler( GtkSalFrame* pFrame )
3913 : m_pFrame(pFrame),
3914 m_nPrevKeyPresses( 0 ),
3915 m_pIMContext( nullptr ),
3916 m_bFocused( true ),
3917 m_bPreeditJustChanged( false )
3919 m_aInputEvent.mpTextAttr = nullptr;
3920 createIMContext();
3923 GtkSalFrame::IMHandler::~IMHandler()
3925 // cancel an eventual event posted to begin preedit again
3926 GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
3927 deleteIMContext();
3930 void GtkSalFrame::IMHandler::createIMContext()
3932 if( m_pIMContext )
3933 return;
3935 m_pIMContext = gtk_im_multicontext_new ();
3936 g_signal_connect( m_pIMContext, "commit",
3937 G_CALLBACK (signalIMCommit), this );
3938 g_signal_connect( m_pIMContext, "preedit_changed",
3939 G_CALLBACK (signalIMPreeditChanged), this );
3940 g_signal_connect( m_pIMContext, "retrieve_surrounding",
3941 G_CALLBACK (signalIMRetrieveSurrounding), this );
3942 g_signal_connect( m_pIMContext, "delete_surrounding",
3943 G_CALLBACK (signalIMDeleteSurrounding), this );
3944 g_signal_connect( m_pIMContext, "preedit_start",
3945 G_CALLBACK (signalIMPreeditStart), this );
3946 g_signal_connect( m_pIMContext, "preedit_end",
3947 G_CALLBACK (signalIMPreeditEnd), this );
3949 GetGenericUnixSalData()->ErrorTrapPush();
3950 gtk_im_context_set_client_window(m_pIMContext, gtk_widget_get_window(m_pFrame->getMouseEventWidget()));
3951 gtk_im_context_focus_in( m_pIMContext );
3952 GetGenericUnixSalData()->ErrorTrapPop();
3953 m_bFocused = true;
3957 void GtkSalFrame::IMHandler::deleteIMContext()
3959 if( m_pIMContext )
3961 // first give IC a chance to deinitialize
3962 GetGenericUnixSalData()->ErrorTrapPush();
3963 gtk_im_context_set_client_window( m_pIMContext, nullptr );
3964 GetGenericUnixSalData()->ErrorTrapPop();
3965 // destroy old IC
3966 g_object_unref( m_pIMContext );
3967 m_pIMContext = nullptr;
3971 void GtkSalFrame::IMHandler::doCallEndExtTextInput()
3973 m_aInputEvent.mpTextAttr = nullptr;
3974 m_pFrame->CallCallbackExc( SalEvent::EndExtTextInput, nullptr );
3977 void GtkSalFrame::IMHandler::updateIMSpotLocation()
3979 SalExtTextInputPosEvent aPosEvent;
3980 m_pFrame->CallCallbackExc( SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent) );
3981 GdkRectangle aArea;
3982 aArea.x = aPosEvent.mnX;
3983 aArea.y = aPosEvent.mnY;
3984 aArea.width = aPosEvent.mnWidth;
3985 aArea.height = aPosEvent.mnHeight;
3986 GetGenericUnixSalData()->ErrorTrapPush();
3987 gtk_im_context_set_cursor_location( m_pIMContext, &aArea );
3988 GetGenericUnixSalData()->ErrorTrapPop();
3991 void GtkSalFrame::IMHandler::sendEmptyCommit()
3993 vcl::DeletionListener aDel( m_pFrame );
3995 SalExtTextInputEvent aEmptyEv;
3996 aEmptyEv.mpTextAttr = nullptr;
3997 aEmptyEv.maText.clear();
3998 aEmptyEv.mnCursorPos = 0;
3999 aEmptyEv.mnCursorFlags = 0;
4000 m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&aEmptyEv) );
4001 if( ! aDel.isDeleted() )
4002 m_pFrame->CallCallbackExc( SalEvent::EndExtTextInput, nullptr );
4005 void GtkSalFrame::IMHandler::endExtTextInput( EndExtTextInputFlags /*nFlags*/ )
4007 gtk_im_context_reset ( m_pIMContext );
4009 if( !m_aInputEvent.mpTextAttr )
4010 return;
4012 vcl::DeletionListener aDel( m_pFrame );
4013 // delete preedit in sal (commit an empty string)
4014 sendEmptyCommit();
4015 if( ! aDel.isDeleted() )
4017 // mark previous preedit state again (will e.g. be sent at focus gain)
4018 m_aInputEvent.mpTextAttr = m_aInputFlags.data();
4019 if( m_bFocused )
4021 // begin preedit again
4022 GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
4027 void GtkSalFrame::IMHandler::focusChanged( bool bFocusIn )
4029 m_bFocused = bFocusIn;
4030 if( bFocusIn )
4032 GetGenericUnixSalData()->ErrorTrapPush();
4033 gtk_im_context_focus_in( m_pIMContext );
4034 GetGenericUnixSalData()->ErrorTrapPop();
4035 if( m_aInputEvent.mpTextAttr )
4037 sendEmptyCommit();
4038 // begin preedit again
4039 GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
4042 else
4044 GetGenericUnixSalData()->ErrorTrapPush();
4045 gtk_im_context_focus_out( m_pIMContext );
4046 GetGenericUnixSalData()->ErrorTrapPop();
4047 // cancel an eventual event posted to begin preedit again
4048 GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
4052 bool GtkSalFrame::IMHandler::handleKeyEvent( GdkEventKey* pEvent )
4054 vcl::DeletionListener aDel( m_pFrame );
4056 if( pEvent->type == GDK_KEY_PRESS )
4058 // Add this key press event to the list of previous key presses
4059 // to which we compare key release events. If a later key release
4060 // event has a matching key press event in this list, we swallow
4061 // the key release because some GTK Input Methods don't swallow it
4062 // for us.
4063 m_aPrevKeyPresses.emplace_back(pEvent );
4064 m_nPrevKeyPresses++;
4066 // Also pop off the earliest key press event if there are more than 10
4067 // already.
4068 while (m_nPrevKeyPresses > 10)
4070 m_aPrevKeyPresses.pop_front();
4071 m_nPrevKeyPresses--;
4074 GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) );
4076 // #i51353# update spot location on every key input since we cannot
4077 // know which key may activate a preedit choice window
4078 updateIMSpotLocation();
4079 if( aDel.isDeleted() )
4080 return true;
4082 bool bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
4083 g_object_unref( pRef );
4085 if( aDel.isDeleted() )
4086 return true;
4088 m_bPreeditJustChanged = false;
4090 if( bResult )
4091 return true;
4092 else
4094 SAL_WARN_IF( m_nPrevKeyPresses <= 0, "vcl.gtk3", "key press has vanished !" );
4095 if( ! m_aPrevKeyPresses.empty() ) // sanity check
4097 // event was not swallowed, do not filter a following
4098 // key release event
4099 // note: this relies on gtk_im_context_filter_keypress
4100 // returning without calling a handler (in the "not swallowed"
4101 // case ) which might change the previous key press list so
4102 // we would pop the wrong event here
4103 m_aPrevKeyPresses.pop_back();
4104 m_nPrevKeyPresses--;
4109 // Determine if we got an earlier key press event corresponding to this key release
4110 if (pEvent->type == GDK_KEY_RELEASE)
4112 GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) );
4113 bool bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
4114 g_object_unref( pRef );
4116 if( aDel.isDeleted() )
4117 return true;
4119 m_bPreeditJustChanged = false;
4121 auto iter = std::find(m_aPrevKeyPresses.begin(), m_aPrevKeyPresses.end(), pEvent);
4122 // If we found a corresponding previous key press event, swallow the release
4123 // and remove the earlier key press from our list
4124 if (iter != m_aPrevKeyPresses.end())
4126 m_aPrevKeyPresses.erase(iter);
4127 m_nPrevKeyPresses--;
4128 return true;
4131 if( bResult )
4132 return true;
4135 return false;
4138 /* FIXME:
4139 * #122282# still more hacking: some IMEs never start a preedit but simply commit
4140 * in this case we cannot commit a single character. Workaround: do not do the
4141 * single key hack for enter or space if the unicode committed does not match
4144 static bool checkSingleKeyCommitHack( guint keyval, sal_Unicode cCode )
4146 bool bRet = true;
4147 switch( keyval )
4149 case GDK_KEY_KP_Enter:
4150 case GDK_KEY_Return:
4151 if( cCode != '\n' && cCode != '\r' )
4152 bRet = false;
4153 break;
4154 case GDK_KEY_space:
4155 case GDK_KEY_KP_Space:
4156 if( cCode != ' ' )
4157 bRet = false;
4158 break;
4159 default:
4160 break;
4162 return bRet;
4165 void GtkSalFrame::IMHandler::signalIMCommit( GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler )
4167 GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
4169 SolarMutexGuard aGuard;
4170 vcl::DeletionListener aDel( pThis->m_pFrame );
4172 const bool bWasPreedit =
4173 (pThis->m_aInputEvent.mpTextAttr != nullptr) ||
4174 pThis->m_bPreeditJustChanged;
4176 pThis->m_aInputEvent.mpTextAttr = nullptr;
4177 pThis->m_aInputEvent.maText = OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 );
4178 pThis->m_aInputEvent.mnCursorPos = pThis->m_aInputEvent.maText.getLength();
4179 pThis->m_aInputEvent.mnCursorFlags = 0;
4181 pThis->m_aInputFlags.clear();
4183 /* necessary HACK: all keyboard input comes in here as soon as an IMContext is set
4184 * which is logical and consequent. But since even simple input like
4185 * <space> comes through the commit signal instead of signalKey
4186 * and all kinds of windows only implement KeyInput (e.g. PushButtons,
4187 * RadioButtons and a lot of other Controls), will send a single
4188 * KeyInput/KeyUp sequence instead of an ExtText event if there
4189 * never was a preedit and the text is only one character.
4191 * In this case there the last ExtText event must have been
4192 * SalEvent::EndExtTextInput, either because of a regular commit
4193 * or because there never was a preedit.
4195 bool bSingleCommit = false;
4196 if( ! bWasPreedit
4197 && pThis->m_aInputEvent.maText.getLength() == 1
4198 && ! pThis->m_aPrevKeyPresses.empty()
4201 const PreviousKeyPress& rKP = pThis->m_aPrevKeyPresses.back();
4202 sal_Unicode aOrigCode = pThis->m_aInputEvent.maText[0];
4204 if( checkSingleKeyCommitHack( rKP.keyval, aOrigCode ) )
4206 pThis->m_pFrame->doKeyCallback( rKP.state, rKP.keyval, rKP.hardware_keycode, rKP.group, aOrigCode, true, true );
4207 bSingleCommit = true;
4210 if( ! bSingleCommit )
4212 pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
4213 if( ! aDel.isDeleted() )
4214 pThis->doCallEndExtTextInput();
4216 if( ! aDel.isDeleted() )
4218 // reset input event
4219 pThis->m_aInputEvent.maText.clear();
4220 pThis->m_aInputEvent.mnCursorPos = 0;
4221 pThis->updateIMSpotLocation();
4226 OUString GtkSalFrame::GetPreeditDetails(GtkIMContext* pIMContext, std::vector<ExtTextInputAttr>& rInputFlags, sal_Int32& rCursorPos, sal_uInt8& rCursorFlags)
4228 char* pText = nullptr;
4229 PangoAttrList* pAttrs = nullptr;
4230 gint nCursorPos = 0;
4232 gtk_im_context_get_preedit_string( pIMContext,
4233 &pText,
4234 &pAttrs,
4235 &nCursorPos );
4237 gint nUtf8Len = pText ? strlen(pText) : 0;
4238 OUString sText = pText ? OUString(pText, nUtf8Len, RTL_TEXTENCODING_UTF8) : OUString();
4240 std::vector<sal_Int32> aUtf16Offsets;
4241 for (sal_Int32 nUtf16Offset = 0; nUtf16Offset < sText.getLength(); sText.iterateCodePoints(&nUtf16Offset))
4242 aUtf16Offsets.push_back(nUtf16Offset);
4244 sal_Int32 nUtf32Len = aUtf16Offsets.size();
4245 // from the above loop filling aUtf16Offsets, we know that its size() fits into sal_Int32
4246 aUtf16Offsets.push_back(sText.getLength());
4248 // sanitize the CurPos which is in utf-32
4249 if (nCursorPos < 0)
4250 nCursorPos = 0;
4251 else if (nCursorPos > nUtf32Len)
4252 nCursorPos = nUtf32Len;
4254 rCursorPos = aUtf16Offsets[nCursorPos];
4255 rCursorFlags = 0;
4257 rInputFlags.resize(std::max(1, static_cast<int>(sText.getLength())), ExtTextInputAttr::NONE);
4259 PangoAttrIterator *iter = pango_attr_list_get_iterator(pAttrs);
4262 GSList *attr_list = nullptr;
4263 GSList *tmp_list = nullptr;
4264 gint nUtf8Start, nUtf8End;
4265 ExtTextInputAttr sal_attr = ExtTextInputAttr::NONE;
4267 // docs say... "Get the range of the current segment ... the stored
4268 // return values are signed, not unsigned like the values in
4269 // PangoAttribute", which implies that the units are otherwise the same
4270 // as that of PangoAttribute whose docs state these units are "in
4271 // bytes"
4272 // so this is the utf8 range
4273 pango_attr_iterator_range(iter, &nUtf8Start, &nUtf8End);
4275 // sanitize the utf8 range
4276 nUtf8Start = std::min(nUtf8Start, nUtf8Len);
4277 nUtf8End = std::min(nUtf8End, nUtf8Len);
4278 if (nUtf8Start >= nUtf8End)
4279 continue;
4281 // get the utf32 range
4282 sal_Int32 nUtf32Start = g_utf8_pointer_to_offset(pText, pText + nUtf8Start);
4283 sal_Int32 nUtf32End = g_utf8_pointer_to_offset(pText, pText + nUtf8End);
4285 // sanitize the utf32 range
4286 nUtf32Start = std::min(nUtf32Start, nUtf32Len);
4287 nUtf32End = std::min(nUtf32End, nUtf32Len);
4288 if (nUtf32Start >= nUtf32End)
4289 continue;
4291 tmp_list = attr_list = pango_attr_iterator_get_attrs (iter);
4292 while (tmp_list)
4294 PangoAttribute *pango_attr = static_cast<PangoAttribute *>(tmp_list->data);
4296 switch (pango_attr->klass->type)
4298 case PANGO_ATTR_BACKGROUND:
4299 sal_attr |= ExtTextInputAttr::Highlight;
4300 rCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
4301 break;
4302 case PANGO_ATTR_UNDERLINE:
4303 sal_attr |= ExtTextInputAttr::Underline;
4304 break;
4305 case PANGO_ATTR_STRIKETHROUGH:
4306 sal_attr |= ExtTextInputAttr::RedText;
4307 break;
4308 default:
4309 break;
4311 pango_attribute_destroy (pango_attr);
4312 tmp_list = tmp_list->next;
4314 if (sal_attr == ExtTextInputAttr::NONE)
4315 sal_attr |= ExtTextInputAttr::Underline;
4316 g_slist_free (attr_list);
4318 // Set the sal attributes on our text
4319 // rhbz#1648281 apply over our utf-16 range derived from the input utf-32 range
4320 for (sal_Int32 i = aUtf16Offsets[nUtf32Start]; i < aUtf16Offsets[nUtf32End]; ++i)
4322 SAL_WARN_IF(i >= static_cast<int>(rInputFlags.size()),
4323 "vcl.gtk3", "pango attrib out of range. Broken range: "
4324 << aUtf16Offsets[nUtf32Start] << "," << aUtf16Offsets[nUtf32End] << " Legal range: 0,"
4325 << rInputFlags.size());
4326 if (i >= static_cast<int>(rInputFlags.size()))
4327 continue;
4328 rInputFlags[i] |= sal_attr;
4330 } while (pango_attr_iterator_next (iter));
4331 pango_attr_iterator_destroy(iter);
4333 g_free( pText );
4334 pango_attr_list_unref( pAttrs );
4336 return sText;
4339 void GtkSalFrame::IMHandler::signalIMPreeditChanged( GtkIMContext* pIMContext, gpointer im_handler )
4341 GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
4343 sal_Int32 nCursorPos(0);
4344 sal_uInt8 nCursorFlags(0);
4345 std::vector<ExtTextInputAttr> aInputFlags;
4346 OUString sText = GtkSalFrame::GetPreeditDetails(pIMContext, aInputFlags, nCursorPos, nCursorFlags);
4347 if (sText.isEmpty() && pThis->m_aInputEvent.maText.isEmpty())
4349 // change from nothing to nothing -> do not start preedit
4350 // e.g. this will activate input into a calc cell without
4351 // user input
4352 return;
4355 pThis->m_bPreeditJustChanged = true;
4357 bool bEndPreedit = sText.isEmpty() && pThis->m_aInputEvent.mpTextAttr != nullptr;
4358 pThis->m_aInputEvent.maText = sText;
4359 pThis->m_aInputEvent.mnCursorPos = nCursorPos;
4360 pThis->m_aInputEvent.mnCursorFlags = nCursorFlags;
4361 pThis->m_aInputFlags = aInputFlags;
4362 pThis->m_aInputEvent.mpTextAttr = pThis->m_aInputFlags.data();
4364 SolarMutexGuard aGuard;
4365 vcl::DeletionListener aDel( pThis->m_pFrame );
4367 pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
4368 if( bEndPreedit && ! aDel.isDeleted() )
4369 pThis->doCallEndExtTextInput();
4370 if( ! aDel.isDeleted() )
4371 pThis->updateIMSpotLocation();
4374 void GtkSalFrame::IMHandler::signalIMPreeditStart( GtkIMContext*, gpointer /*im_handler*/ )
4378 void GtkSalFrame::IMHandler::signalIMPreeditEnd( GtkIMContext*, gpointer im_handler )
4380 GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
4382 pThis->m_bPreeditJustChanged = true;
4384 SolarMutexGuard aGuard;
4385 vcl::DeletionListener aDel( pThis->m_pFrame );
4386 pThis->doCallEndExtTextInput();
4387 if( ! aDel.isDeleted() )
4388 pThis->updateIMSpotLocation();
4391 gboolean GtkSalFrame::IMHandler::signalIMRetrieveSurrounding( GtkIMContext* pContext, gpointer im_handler )
4393 GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
4395 SalSurroundingTextRequestEvent aEvt;
4396 aEvt.maText.clear();
4397 aEvt.mnStart = aEvt.mnEnd = 0;
4399 SolarMutexGuard aGuard;
4400 pThis->m_pFrame->CallCallback(SalEvent::SurroundingTextRequest, &aEvt);
4402 OString sUTF = OUStringToOString(aEvt.maText, RTL_TEXTENCODING_UTF8);
4403 OUString sCursorText(aEvt.maText.copy(0, aEvt.mnStart));
4404 gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
4405 OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
4406 return true;
4409 Selection GtkSalFrame::CalcDeleteSurroundingSelection(const OUString& rSurroundingText, sal_Int32 nCursorIndex, int nOffset, int nChars)
4411 Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32);
4413 if (nCursorIndex == -1)
4414 return aInvalid;
4416 // Note that offset and n_chars are in characters not in bytes
4417 // which differs from the usage other places in GtkIMContext
4419 if (nOffset > 0)
4421 while (nOffset && nCursorIndex < rSurroundingText.getLength())
4423 rSurroundingText.iterateCodePoints(&nCursorIndex, 1);
4424 --nOffset;
4427 else if (nOffset < 0)
4429 while (nOffset && nCursorIndex > 0)
4431 rSurroundingText.iterateCodePoints(&nCursorIndex, -1);
4432 ++nOffset;
4436 if (nOffset)
4438 SAL_WARN("vcl.gtk", "IM delete-surrounding, unable to move to offset: " << nOffset);
4439 return aInvalid;
4442 sal_Int32 nCursorEndIndex(nCursorIndex);
4443 sal_Int32 nCount(0);
4444 while (nCount < nChars && nCursorEndIndex < rSurroundingText.getLength())
4446 rSurroundingText.iterateCodePoints(&nCursorEndIndex, 1);
4447 ++nCount;
4450 if (nCount != nChars)
4452 SAL_WARN("vcl.gtk", "IM delete-surrounding, unable to select: " << nChars << " characters");
4453 return aInvalid;
4456 return Selection(nCursorIndex, nCursorEndIndex);
4459 gboolean GtkSalFrame::IMHandler::signalIMDeleteSurrounding( GtkIMContext*, gint offset, gint nchars,
4460 gpointer im_handler )
4462 GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
4464 // First get the surrounding text
4465 SalSurroundingTextRequestEvent aSurroundingTextEvt;
4466 aSurroundingTextEvt.maText.clear();
4467 aSurroundingTextEvt.mnStart = aSurroundingTextEvt.mnEnd = 0;
4469 SolarMutexGuard aGuard;
4470 pThis->m_pFrame->CallCallback(SalEvent::SurroundingTextRequest, &aSurroundingTextEvt);
4472 // Turn offset, nchars into a utf-16 selection
4473 Selection aSelection = GtkSalFrame::CalcDeleteSurroundingSelection(aSurroundingTextEvt.maText,
4474 aSurroundingTextEvt.mnStart,
4475 offset, nchars);
4476 Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32);
4477 if (aSelection == aInvalid)
4478 return false;
4480 SalSurroundingTextSelectionChangeEvent aEvt;
4481 aEvt.mnStart = aSelection.Min();
4482 aEvt.mnEnd = aSelection.Max();
4484 pThis->m_pFrame->CallCallback(SalEvent::DeleteSurroundingTextRequest, &aEvt);
4486 aSelection = Selection(aEvt.mnStart, aEvt.mnEnd);
4487 if (aSelection == aInvalid)
4488 return false;
4490 return true;
4493 Size GtkSalDisplay::GetScreenSize( int nDisplayScreen )
4495 tools::Rectangle aRect = m_pSys->GetDisplayScreenPosSizePixel( nDisplayScreen );
4496 return Size( aRect.GetWidth(), aRect.GetHeight() );
4499 sal_uIntPtr GtkSalFrame::GetNativeWindowHandle(GtkWidget *pWidget) const
4501 (void) this; // Silence loplugin:staticmethods
4502 GdkDisplay *pDisplay = getGdkDisplay();
4503 GdkWindow *pWindow = gtk_widget_get_window(pWidget);
4505 #if defined(GDK_WINDOWING_X11)
4506 if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
4508 return GDK_WINDOW_XID(pWindow);
4510 #endif
4511 #if defined(GDK_WINDOWING_WAYLAND)
4512 if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
4514 return reinterpret_cast<sal_uIntPtr>(gdk_wayland_window_get_wl_surface(pWindow));
4516 #endif
4517 return 0;
4520 sal_uIntPtr GtkSalFrame::GetNativeWindowHandle()
4522 return GetNativeWindowHandle(m_pWindow);
4525 void GtkDragSource::set_datatransfer(const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
4526 const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener)
4528 m_xListener = rListener;
4529 m_xTrans = rTrans;
4532 void GtkDragSource::setActiveDragSource()
4534 // For LibreOffice internal D&D we provide the Transferable without Gtk
4535 // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
4536 g_ActiveDragSource = this;
4537 g_DropSuccessSet = false;
4538 g_DropSuccess = false;
4541 std::vector<GtkTargetEntry> GtkDragSource::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
4543 return m_aConversionHelper.FormatsToGtk(rFormats);
4546 void GtkDragSource::startDrag(const datatransfer::dnd::DragGestureEvent& rEvent,
4547 sal_Int8 sourceActions, sal_Int32 /*cursor*/, sal_Int32 /*image*/,
4548 const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
4549 const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener)
4551 set_datatransfer(rTrans, rListener);
4553 if (m_pFrame)
4555 auto aFormats = m_xTrans->getTransferDataFlavors();
4556 std::vector<GtkTargetEntry> aGtkTargets(FormatsToGtk(aFormats));
4557 GtkTargetList *pTargetList = gtk_target_list_new(aGtkTargets.data(), aGtkTargets.size());
4559 gint nDragButton = 1; // default to left button
4560 css::awt::MouseEvent aEvent;
4561 if (rEvent.Event >>= aEvent)
4563 if (aEvent.Buttons & css::awt::MouseButton::LEFT )
4564 nDragButton = 1;
4565 else if (aEvent.Buttons & css::awt::MouseButton::RIGHT)
4566 nDragButton = 3;
4567 else if (aEvent.Buttons & css::awt::MouseButton::MIDDLE)
4568 nDragButton = 2;
4571 setActiveDragSource();
4573 m_pFrame->startDrag(nDragButton, rEvent.DragOriginX, rEvent.DragOriginY,
4574 VclToGdk(sourceActions), pTargetList);
4576 gtk_target_list_unref(pTargetList);
4577 for (auto &a : aGtkTargets)
4578 g_free(a.target);
4580 else
4581 dragFailed();
4584 void GtkSalFrame::startDrag(gint nButton, gint nDragOriginX, gint nDragOriginY,
4585 GdkDragAction sourceActions, GtkTargetList* pTargetList)
4587 SolarMutexGuard aGuard;
4589 assert(m_pDragSource);
4591 GdkEvent aFakeEvent;
4592 memset(&aFakeEvent, 0, sizeof(GdkEvent));
4593 aFakeEvent.type = GDK_BUTTON_PRESS;
4594 aFakeEvent.button.window = gtk_widget_get_window(getMouseEventWidget());
4595 aFakeEvent.button.time = GDK_CURRENT_TIME;
4596 GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(getGdkDisplay());
4597 aFakeEvent.button.device = gdk_device_manager_get_client_pointer(pDeviceManager);
4599 GdkDragContext *pContext = gtk_drag_begin_with_coordinates(getMouseEventWidget(),
4600 pTargetList,
4601 sourceActions,
4602 nButton,
4603 &aFakeEvent,
4604 nDragOriginX,
4605 nDragOriginY);
4607 if (!pContext)
4608 m_pDragSource->dragFailed();
4611 void GtkDragSource::dragFailed()
4613 if (m_xListener.is())
4615 datatransfer::dnd::DragSourceDropEvent aEv;
4616 aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_NONE;
4617 aEv.DropSuccess = false;
4618 auto xListener = m_xListener;
4619 m_xListener.clear();
4620 xListener->dragDropEnd(aEv);
4624 gboolean GtkSalFrame::signalDragFailed(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkDragResult /*result*/, gpointer frame)
4626 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4627 if (!pThis->m_pDragSource)
4628 return false;
4629 pThis->m_pDragSource->dragFailed();
4630 return false;
4633 void GtkDragSource::dragDelete()
4635 if (m_xListener.is())
4637 datatransfer::dnd::DragSourceDropEvent aEv;
4638 aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_MOVE;
4639 aEv.DropSuccess = true;
4640 auto xListener = m_xListener;
4641 m_xListener.clear();
4642 xListener->dragDropEnd(aEv);
4646 void GtkSalFrame::signalDragDelete(GtkWidget* /*widget*/, GdkDragContext* /*context*/, gpointer frame)
4648 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4649 if (!pThis->m_pDragSource)
4650 return;
4651 pThis->m_pDragSource->dragDelete();
4654 void GtkDragSource::dragEnd(GdkDragContext* context)
4656 if (m_xListener.is())
4658 datatransfer::dnd::DragSourceDropEvent aEv;
4659 aEv.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
4660 // an internal drop can accept the drop but fail with dropComplete( false )
4661 // this is different than the GTK API
4662 if (g_DropSuccessSet)
4663 aEv.DropSuccess = g_DropSuccess;
4664 else
4665 aEv.DropSuccess = true;
4666 auto xListener = m_xListener;
4667 m_xListener.clear();
4668 xListener->dragDropEnd(aEv);
4670 g_ActiveDragSource = nullptr;
4673 void GtkSalFrame::signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer frame)
4675 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4676 if (!pThis->m_pDragSource)
4677 return;
4678 pThis->m_pDragSource->dragEnd(context);
4681 void GtkDragSource::dragDataGet(GtkSelectionData *data, guint info)
4683 m_aConversionHelper.setSelectionData(m_xTrans, data, info);
4686 void GtkSalFrame::signalDragDataGet(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkSelectionData *data, guint info,
4687 guint /*time*/, gpointer frame)
4689 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4690 if (!pThis->m_pDragSource)
4691 return;
4692 pThis->m_pDragSource->dragDataGet(data, info);
4695 bool GtkSalFrame::CallCallbackExc(SalEvent nEvent, const void* pEvent) const
4697 bool nRet = false;
4700 nRet = CallCallback(nEvent, pEvent);
4702 catch (...)
4704 GtkSalData *pSalData = static_cast<GtkSalData*>(GetSalData());
4705 pSalData->setException(std::current_exception());
4707 return nRet;
4710 void GtkSalFrame::nopaint_container_resize_children(GtkContainer *pContainer)
4712 bool bOrigSalObjectSetPosSize = m_bSalObjectSetPosSize;
4713 m_bSalObjectSetPosSize = true;
4714 gtk_container_resize_children(pContainer);
4715 m_bSalObjectSetPosSize = bOrigSalObjectSetPosSize;
4718 GdkEvent* GtkSalFrame::makeFakeKeyPress(GtkWidget* pWidget)
4720 GdkEvent *event = gdk_event_new(GDK_KEY_PRESS);
4721 event->key.window = GDK_WINDOW(g_object_ref(gtk_widget_get_window(pWidget)));
4723 GdkSeat *seat = gdk_display_get_default_seat(gtk_widget_get_display(pWidget));
4724 gdk_event_set_device(event, gdk_seat_get_keyboard(seat));
4726 event->key.send_event = 1 /* TRUE */;
4727 event->key.time = gtk_get_current_event_time();
4728 event->key.state = 0;
4729 event->key.keyval = 0;
4730 event->key.length = 0;
4731 event->key.string = nullptr;
4732 event->key.hardware_keycode = 0;
4733 event->key.group = 0;
4734 event->key.is_modifier = false;
4735 return event;
4738 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */