1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
41 #include <X11/Xutil.h>
42 #include <unx/gtk/gtkbackend.hxx>
46 #include <basegfx/vector/b2ivector.hxx>
52 #if OSL_DEBUG_LEVEL > 1
56 #include <i18nlangtag/mslangid.hxx>
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
)
73 if( state
& GDK_SHIFT_MASK
)
75 if( state
& GDK_CONTROL_MASK
)
77 if( state
& GDK_MOD1_MASK
)
79 if( state
& GDK_SUPER_MASK
)
84 sal_uInt16
GtkSalFrame::GetMouseModCode( guint state
)
86 sal_uInt16 nCode
= GetKeyModCode( state
);
87 if( state
& GDK_BUTTON1_MASK
)
89 if( state
& GDK_BUTTON2_MASK
)
90 nCode
|= MOUSE_MIDDLE
;
91 if( state
& GDK_BUTTON3_MASK
)
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
))
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
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
132 nCode
= KEY_F1
+ (keyval
-GDK_KEY_F1
); break;
139 case GDK_KEY_KP_Down
:
140 case GDK_KEY_Down
: nCode
= KEY_DOWN
; break;
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
:
150 case GDK_KEY_Home
: nCode
= KEY_HOME
; break;
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
:
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;
171 case GDK_KEY_KP_Add
: nCode
= KEY_ADD
; break;
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;
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
208 case 0x1000FF03: // apXK_Cut
211 case 0x1000FF04: // apXK_Paste
214 case 0x1000FF14: // apXK_Repeat
218 // - - - - - - - - - - - - - - D E C - - - - - - - - - - - - - 0x1000
219 // These can be found in DECkeysym.h
223 // - - - - - - - - - - - - - - H P - - - - - - - - - - - - - 0x1000
224 // These can be found in HPkeysym.h
225 case 0x1000FF73: // hpXK_DeleteChar
228 case 0x1000FF74: // hpXK_BackTab
229 case 0x1000FF75: // hpXK_KP_BackTab
232 // - - - - - - - - - - - - - - I B M - - - - - - - - - - - - -
233 // - - - - - - - - - - - - - - O S F - - - - - - - - - - - - - 0x1004
234 // These also can be found in HPkeysym.h
235 case 0x1004FF02: // osfXK_Copy
238 case 0x1004FF03: // osfXK_Cut
241 case 0x1004FF04: // osfXK_Paste
244 case 0x1004FF07: // osfXK_BackTab
247 case 0x1004FF08: // osfXK_BackSpace
248 nCode
= KEY_BACKSPACE
;
250 case 0x1004FF1B: // osfXK_Escape
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
262 case 0x1005FF11: // SunXK_F37
265 case 0x1005FF70: // SunXK_Props
266 nCode
= KEY_PROPERTIES
;
268 case 0x1005FF71: // SunXK_Front
271 case 0x1005FF72: // SunXK_Copy
274 case 0x1005FF73: // SunXK_Open
277 case 0x1005FF74: // SunXK_Paste
280 case 0x1005FF75: // SunXK_Cut
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
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
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
;
308 // F10 means either KEY_F10 or KEY_MENU, which has to be decided
309 // in the independent part.
313 sal_Unicode nCharCode
;
314 KeyAlternate() : nKeyCode( 0 ), nCharCode( 0 ) {}
315 KeyAlternate( sal_uInt16 nKey
, sal_Unicode nChar
= 0 ) : nKeyCode( nKey
), nCharCode( nChar
) {}
321 GetAlternateKeyCode( const sal_uInt16 nKeyCode
)
323 KeyAlternate aAlternate
;
327 case KEY_F10
: aAlternate
= KeyAlternate( KEY_MENU
);break;
328 case KEY_F24
: aAlternate
= KeyAlternate( KEY_SUBTRACT
, '-' );break;
334 #if OSL_DEBUG_LEVEL > 0
335 static bool dumpframes
= false;
338 bool GtkSalFrame::doKeyCallback( guint state
,
340 guint16 hardware_keycode
,
342 sal_Unicode aOrigCode
,
349 aEvent
.mnCharCode
= aOrigCode
;
352 vcl::DeletionListener
aDel( this );
354 #if OSL_DEBUG_LEVEL > 0
355 const char* pKeyDebug
= getenv("VCL_GTK3_PAINTDEBUG");
357 if (pKeyDebug
&& *pKeyDebug
== '1')
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
));
368 else if (keyval
== GDK_KEY_1
)
370 SAL_INFO("vcl.gtk3", "force repaint all.");
374 else if (keyval
== GDK_KEY_2
)
376 dumpframes
= !dumpframes
;
377 SAL_INFO("vcl.gtk3", "toggle dump frames to " << dumpframes
);
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
389 * #i52338# do this for all keys that the independent part has no key code
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();
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)
411 best_group
= std::min(best_group
, keys
[i
].group
);
418 //Unavailable, go with original group then I suppose
419 if (best_group
== SAL_MAX_INT32
)
422 guint updated_keyval
= GetKeyValFor(keymap
, hardware_keycode
, best_group
);
423 aEvent
.mnCode
= GetKeyCode(updated_keyval
);
426 aEvent
.mnCode
|= GetKeyModCode( state
);
428 bool bStopProcessingKey
;
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
);
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)
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)
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;
480 // AppMenu watch functions.
482 static void ObjectDestroyedNotify( gpointer data
)
485 g_object_unref( data
);
489 static void hud_activated( gboolean hud_active
, gpointer user_data
)
493 SolarMutexGuard aGuard
;
494 GtkSalFrame
* pSalFrame
= static_cast< GtkSalFrame
* >( user_data
);
495 GtkSalMenu
* pSalMenu
= reinterpret_cast< GtkSalMenu
* >( pSalFrame
->GetMenu() );
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.
511 pSessionBus
= g_bus_get_sync (G_BUS_TYPE_SESSION
, nullptr, nullptr);
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
) );
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",
549 g_dbus_connection_get_unique_name( pSessionBus
));
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
);
566 void on_registrar_available( GDBusConnection
* /*connection*/,
567 const gchar
* /*name*/,
568 const gchar
* /*name_owner*/,
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*/,
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();
599 GtkSalMenu
* pGtkSalMenu
= static_cast< GtkSalMenu
* >( pSalMenu
);
600 pGtkSalMenu
->EnableUnity(false);
604 void GtkSalFrame::EnsureAppMenuWatch()
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 )
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
,
628 void GtkSalFrame::InvalidateGraphics()
636 GtkSalFrame::~GtkSalFrame()
638 m_aSmoothScrollIdle
.Stop();
639 m_aSmoothScrollIdle
.ClearInvokeHandler();
643 m_pDropTarget
->deinitialize();
644 m_pDropTarget
= nullptr;
649 m_pDragSource
->deinitialize();
650 m_pDragSource
= nullptr;
653 InvalidateGraphics();
657 m_pParent
->m_aChildren
.remove( this );
660 getDisplay()->deregisterFrame( this );
664 cairo_region_destroy( m_pRegion
);
667 m_pIMHandler
.reset();
669 //tdf#108705 remove grabs on event widget before
670 //destroying event widget
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
) );
680 gtk_widget_destroy( GTK_WIDGET(m_pEventBox
) );
681 if( m_pTopLevelGrid
)
682 gtk_widget_destroy( GTK_WIDGET(m_pTopLevelGrid
) );
684 SolarMutexGuard aGuard
;
687 g_bus_unwatch_name(m_nWatcherId
);
691 g_object_set_data( G_OBJECT( m_pWindow
), "SalFrame", nullptr );
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
) );
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
),
727 nX
- m_pParent
->maGeometry
.nX
, nY
- m_pParent
->maGeometry
.nY
);
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.
762 ooo_fixed_get_preferred_height(GtkWidget
*, gint
*minimum
, gint
*natural
)
769 ooo_fixed_get_preferred_width(GtkWidget
*, gint
*minimum
, gint
*natural
)
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 ..
793 static GType type
= 0;
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));
817 void GtkSalFrame::updateScreenNumber()
820 GdkScreen
*pScreen
= gtk_widget_get_screen( m_pWindow
);
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;
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);
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 ));
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 ));
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 );
928 m_nKeyModifiers
= ModKeyFlags::NONE
;
929 m_bFullscreen
= false;
930 m_bSpanMonitorsWhenFullscreen
= false;
931 m_nState
= GDK_WINDOW_STATE_WITHDRAWN
;
932 m_pIMHandler
= 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;
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
953 gtk_widget_show_all(GTK_WIDGET(m_pTopLevelGrid
));
955 // realize the window, we need an XWindow id
956 gtk_widget_realize( m_pWindow
);
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
);
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
;
986 m_pGraphics
= nullptr;
988 m_nFloatFlags
= FloatWinPopupFlags::NONE
;
989 m_bFloatPositioned
= false;
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();
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
)
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
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
)
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
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
;
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();
1077 // insert into container
1078 gtk_fixed_put( m_pParent
->getFixedContainer(),
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
1100 m_sWMClass
= m_pParent
->m_sWMClass
;
1104 if (GTK_IS_WINDOW(m_pWindow
))
1108 GtkWidget
* pTopLevel
= gtk_widget_get_toplevel(m_pParent
->m_pWindow
);
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
));
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
)));
1125 bool bDecoHandling
=
1127 ( ! (nStyle
& SalFrameStyleFlags::FLOAT
) ||
1128 (nStyle
& SalFrameStyleFlags::OWNERDRAWDECORATION
) );
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
));
1176 else if( nStyle
& SalFrameStyleFlags::FLOAT
)
1177 gtk_window_set_type_hint( GTK_WINDOW(m_pWindow
), GDK_WINDOW_TYPE_HINT_POPUP_MENU
);
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
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);
1213 m_pWindow
= gtk_window_new( GTK_WINDOW_POPUP
);
1215 m_nStyle
= SalFrameStyleFlags::PLUG
;
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()
1235 m_pGraphics
.reset( new GtkSalGraphics( this, m_pWindow
) );
1239 TriggerPaintEvent();
1241 m_pGraphics
->setSurface(m_pSurface
, m_aFrameSize
);
1244 return m_pGraphics
.get();
1247 void GtkSalFrame::ReleaseGraphics( SalGraphics
* 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() );
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());
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
);
1293 void GtkSalFrame::SetIcon( sal_uInt16 nIcon
)
1295 if( (m_nStyle
& (SalFrameStyleFlags::PLUG
|SalFrameStyleFlags::SYSTEMCHILD
|SalFrameStyleFlags::FLOAT
|SalFrameStyleFlags::INTRO
|SalFrameStyleFlags::OWNERDRAWDECORATION
))
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");
1314 appicon
= g_strdup ("libreoffice-startcenter");
1321 void GtkSalFrame::SetMenu( SalMenu
* pSalMenu
)
1323 m_pSalMenu
= static_cast<GtkSalMenu
*>(pSalMenu
);
1326 SalMenu
* GtkSalFrame::GetMenu()
1331 void GtkSalFrame::DrawMenuBar()
1335 void GtkSalFrame::Center()
1337 if (!GTK_IS_WINDOW(m_pWindow
))
1340 gtk_window_set_position(GTK_WINDOW(m_pWindow
), GTK_WIN_POS_CENTER_ON_PARENT
);
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*/ )
1372 getDisplay()->startupNotificationCompleted();
1376 if( m_bDefaultSize
)
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());
1407 gtk_widget_show(m_pWindow
);
1410 gtk_widget_show(m_pWindow
);
1413 if( isFloatGrabWindow() )
1416 if (!getDisplay()->GetCaptureFrame())
1418 grabPointer(true, true, true);
1421 // #i44068# reset parent's IM context
1423 m_pParent
->EndExtTextInput(EndExtTextInputFlags::NONE
);
1428 if( isFloatGrabWindow() )
1431 if (!getDisplay()->GetCaptureFrame())
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
);
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() )
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
;
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
;
1495 gtk_window_set_geometry_hints( GTK_WINDOW(m_pWindow
),
1498 GdkWindowHints( aHints
) );
1502 void GtkSalFrame::SetMaxClientSize( tools::Long nWidth
, tools::Long nHeight
)
1506 m_aMaxSize
= Size( nWidth
, nHeight
);
1510 void GtkSalFrame::SetMinClientSize( tools::Long nWidth
, tools::Long nHeight
)
1514 m_aMinSize
= Size( nWidth
, nHeight
);
1517 widget_set_size_request(nWidth
, nHeight
);
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() )
1530 if( aFrameSize
.getX() == 0 )
1531 aFrameSize
.setX( 1 );
1532 if( aFrameSize
.getY() == 0 )
1533 aFrameSize
.setY( 1 );
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
,
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
);
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 ) )
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
);
1577 else if( m_bDefaultSize
)
1580 m_bDefaultSize
= false;
1582 if( nFlags
& ( SAL_FRAME_POSSIZE_X
| SAL_FRAME_POSSIZE_Y
) )
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
)
1594 if (nFlags
& SAL_FRAME_POSSIZE_Y
)
1596 m_bGeometryIsProvisional
= true;
1598 m_bDefaultPos
= false;
1600 moveWindow(maGeometry
.nX
, maGeometry
.nY
);
1602 updateScreenNumber();
1604 else if( m_bDefaultPos
)
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
;
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
)
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
);
1636 SalFrame
* GtkSalFrame::GetParent() const
1641 void GtkSalFrame::SetWindowState( const SalFrameState
* pState
)
1643 if( ! m_pWindow
|| ! pState
|| isChild( true, false ) )
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
;
1676 nX
= maGeometry
.nX
- (m_pParent
? m_pParent
->maGeometry
.nX
: 0);
1677 if( pState
->mnMask
& WindowStateMask::Y
)
1678 nPosSizeFlags
|= SAL_FRAME_POSSIZE_Y
;
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
) );
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
)
1703 gtk_window_iconify( GTK_WINDOW(m_pWindow
) );
1705 gtk_window_deiconify( GTK_WINDOW(m_pWindow
) );
1707 TriggerPaintEvent();
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
);
1719 gtk_window_get_size(GTK_WINDOW(pWindow
), &width
, &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
;
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
;
1766 void GtkSalFrame::SetScreen( unsigned int nNewScreen
, SetType eType
, tools::Rectangle
const *pSize
)
1771 if (maGeometry
.nDisplayScreenNumber
== nNewScreen
&& eType
== SetType::RetainSize
)
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;
1782 if (m_bSpanMonitorsWhenFullscreen
) //span all screens
1784 pScreen
= gtk_widget_get_screen( m_pWindow
);
1787 aNewMonitor
.width
= gdk_screen_get_width(pScreen
);
1788 aNewMonitor
.height
= gdk_screen_get_height(pScreen
);
1792 bool bSameMonitor
= false;
1794 if (!bSpanAllScreens
)
1796 pScreen
= getDisplay()->getSystem()->getScreenMonitorFromIdx( nNewScreen
, nMonitor
);
1799 g_warning ("Attempt to move GtkSalFrame to invalid screen %d => "
1800 "fallback to current\n", nNewScreen
);
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
) );
1817 nMonitor
= nOldMonitor
;
1819 #if OSL_DEBUG_LEVEL > 1
1820 if( nMonitor
== nOldMonitor
)
1821 g_warning( "An apparently pointless SetScreen - should we elide it ?" );
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
);
1837 if( eType
== SetType::Fullscreen
)
1841 nWidth
= aNewMonitor
.width
;
1842 nHeight
= aNewMonitor
.height
;
1843 m_nStyle
|= SalFrameStyleFlags::PARTIAL_FULLSCREEN
;
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
)
1856 nWidth
= pSize
->GetWidth();
1857 nHeight
= pSize
->GetHeight();
1858 m_nStyle
&= ~SalFrameStyleFlags::PARTIAL_FULLSCREEN
;
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
));
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();
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();
1925 if (!getDisplay()->IsX11Display())
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
),
1943 void GtkSalFrame::SetApplicationID( const OUString
&rWMClass
)
1945 if( rWMClass
!= m_sWMClass
&& ! isChild() )
1947 m_sWMClass
= rWMClass
;
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() )
1964 m_aRestorePosSize
= GetPosAndSize(GTK_WINDOW(m_pWindow
));
1965 SetScreen( nScreen
, SetType::Fullscreen
);
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
,
1987 getDisplay()->IsX11Display(),
1992 void GtkSalFrame::SetAlwaysOnTop( bool bOnTop
)
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
)
2012 nLastUserInputTime
= nUserInputTime
;
2015 void GtkSalFrame::ToTop( SalFrameToTop nFlags
)
2020 if( isChild( false ) )
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
);
2030 if (!(nFlags
& SalFrameToTop::GrabFocusOnly
))
2031 gtk_window_present_with_time(GTK_WINDOW(m_pWindow
), nTimestamp
);
2033 gdk_window_focus(gtk_widget_get_window(m_pWindow
), nTimestamp
);
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
)
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
)
2063 // tdf#135779 move focus back inside usual input window out of any
2064 // other gtk widgets before grabbing the pointer
2068 static const char* pEnv
= getenv( "SAL_NO_MOUSEGRABS" );
2075 GdkSeat
* pSeat
= gdk_display_get_default_seat(getGdkDisplay());
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);
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
;
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
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 )
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
;
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
;
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;
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
)
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
);
2219 GdkDisplay
*GtkSalFrame::getGdkDisplay()
2221 return GetGtkSalData()->GetGdkDisplay();
2224 GtkSalDisplay
*GtkSalFrame::getDisplay()
2226 return GetGtkSalData()->GetGtkDisplay();
2229 SalFrame::SalPointerState
GtkSalFrame::GetPointerState()
2231 SalPointerState aState
;
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
);
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
;
2257 void GtkSalFrame::SimulateKeyPress( sal_uInt16 nKeyCode
)
2259 g_warning ("missing simulate keypress %d", nKeyCode
);
2262 void GtkSalFrame::SetInputContext( SalInputContext
* pContext
)
2267 if( ! (pContext
->mnOptions
& InputContextFlags::Text
) )
2270 // create a new im context
2271 if( ! m_pIMHandler
)
2272 m_pIMHandler
.reset( new IMHandler( this ) );
2275 void GtkSalFrame::EndExtTextInput( EndExtTextInputFlags nFlags
)
2278 m_pIMHandler
->endExtTextInput( nFlags
);
2281 bool GtkSalFrame::MapUnicodeToKeyCode( sal_Unicode
, LanguageType
, vcl::KeyCode
& )
2283 // not supported yet
2287 LanguageType
GtkSalFrame::GetInputLanguage()
2289 return LANGUAGE_DONTKNOW
;
2292 void GtkSalFrame::UpdateSettings( AllSettings
& rSettings
)
2297 GtkSalGraphics
* pGraphics
= m_pGraphics
.get();
2298 bool bFreeGraphics
= false;
2301 pGraphics
= static_cast<GtkSalGraphics
*>(AcquireGraphics());
2304 SAL_WARN("vcl.gtk3", "Could not get graphics - unable to update settings");
2307 bFreeGraphics
= true;
2310 pGraphics
->UpdateSettings( rSettings
);
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
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;
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
);
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
2362 void GtkSalFrame::ResetClipRegion()
2365 gdk_window_shape_combine_region( gtk_widget_get_window( m_pWindow
), nullptr, 0, 0 );
2368 void GtkSalFrame::BeginSetClipRegion( sal_uInt32
)
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
)
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
)
2399 m_aFloatRect
= rRect
;
2400 m_nFloatFlags
= nFlags
;
2401 m_bFloatPositioned
= true;
2404 void GtkSalFrame::SetModal(bool bModal
)
2408 gtk_window_set_modal(GTK_WINDOW(m_pWindow
), bModal
);
2411 bool GtkSalFrame::GetModal() const
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
,
2422 GtkSalFrame
* pThis
= static_cast<GtkSalFrame
*>(frame
);
2423 if (pThis
->m_aTooltip
.isEmpty() || pThis
->m_bTooltipBlocked
)
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
);
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());
2446 void GtkSalFrame::BlockTooltip()
2448 m_bTooltipBlocked
= true;
2451 void GtkSalFrame::UnblockTooltip()
2453 m_bTooltipBlocked
= false;
2456 void GtkSalFrame::HideTooltip()
2459 GtkWidget
* pEventWidget
= getMouseEventWidget();
2460 gtk_widget_trigger_tooltip_query(pEventWidget
);
2465 void set_pointing_to(GtkPopover
*pPopOver
, vcl::Window
* pParent
, const tools::Rectangle
& rHelpArea
, const SalFrameGeometry
& rGeometry
)
2468 aRect
.x
= FloatingWindow::ImplConvertToAbsPos(pParent
, rHelpArea
).Left() - rGeometry
.nX
;
2469 aRect
.y
= rHelpArea
.Top();
2473 GtkPositionType ePos
= gtk_popover_get_position(pPopOver
);
2476 case GTK_POS_BOTTOM
:
2478 aRect
.width
= rHelpArea
.GetWidth();
2482 aRect
.height
= rHelpArea
.GetHeight();
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
);
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());
2528 bool GtkSalFrame::HidePopover(void* nId
)
2530 GtkWidget
*pWidget
= static_cast<GtkWidget
*>(nId
);
2531 gtk_widget_destroy(pWidget
);
2535 void GtkSalFrame::addGrabLevel()
2537 if (m_nGrabLevel
== 0)
2538 gtk_grab_add(getMouseEventWidget());
2542 void GtkSalFrame::removeGrabLevel()
2544 if (m_nGrabLevel
> 0)
2547 if (m_nGrabLevel
== 0)
2548 gtk_grab_remove(getMouseEventWidget());
2552 void GtkSalFrame::closePopup()
2556 ImplSVData
* pSVData
= ImplGetSVData();
2557 if (!pSVData
->mpWinData
->mpFirstFloat
)
2559 if (pSVData
->mpWinData
->mpFirstFloat
->ImplGetFrame() != this)
2561 pSVData
->mpWinData
->mpFirstFloat
->EndPopupMode(FloatWinPopupEndFlags::Cancel
| FloatWinPopupEndFlags::CloseAll
);
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
);
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
);
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();
2618 if (!bDifferentEventWindow
)
2622 SalMouseEvent aEvent
;
2623 SalEvent nEventType
= SalEvent::NONE
;
2624 switch( pEvent
->type
)
2626 case GDK_BUTTON_PRESS
:
2627 nEventType
= SalEvent::MouseButtonDown
;
2629 case GDK_BUTTON_RELEASE
:
2630 nEventType
= SalEvent::MouseButtonUp
;
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
)
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
);
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
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
);
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
)
2775 aEvent
.mnDelta
= 120;
2776 aEvent
.mnNotchDelta
= 1;
2777 aEvent
.mnScrollLines
= 3;
2778 aEvent
.mbHorz
= false;
2781 case GDK_SCROLL_DOWN
:
2782 aEvent
.mnDelta
= -120;
2783 aEvent
.mnNotchDelta
= -1;
2784 aEvent
.mnScrollLines
= 3;
2785 aEvent
.mbHorz
= false;
2788 case GDK_SCROLL_LEFT
:
2789 aEvent
.mnDelta
= 120;
2790 aEvent
.mnNotchDelta
= 1;
2791 aEvent
.mnScrollLines
= 3;
2792 aEvent
.mbHorz
= true;
2795 case GDK_SCROLL_RIGHT
:
2796 aEvent
.mnDelta
= -120;
2797 aEvent
.mnNotchDelta
= -1;
2798 aEvent
.mnScrollLines
= 3;
2799 aEvent
.mbHorz
= true;
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
);
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
);
2841 void GtkSalFrame::gestureSwipe(GtkGestureSwipe
* gesture
, gdouble velocity_x
, gdouble velocity_y
, gpointer frame
)
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
;
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
;
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
)
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
2932 GdkModifierType mask
;
2933 gdk_window_get_pointer( gtk_widget_get_window(GTK_WIDGET(pThis
->m_pWindow
)), &x
, &y
, &mask
);
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
);
2959 cairo_t
* GtkSalFrame::getCairoContext() const
2961 cairo_t
* cr
= cairo_create(m_pSurface
);
2966 void GtkSalFrame::damaged(sal_Int32 nExtentsX
, sal_Int32 nExtentsY
,
2967 sal_Int32 nExtentsWidth
, sal_Int32 nExtentsHeight
) const
2969 #if OSL_DEBUG_LEVEL > 0
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());
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);
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
)
3002 pThis
->maGeometry
.nWidth
= pAllocation
->width
;
3003 pThis
->maGeometry
.nHeight
= pAllocation
->height
;
3004 bool bRealized
= gtk_widget_get_realized(pWidget
);
3006 pThis
->AllocateFrame();
3007 pThis
->CallCallbackExc( SalEvent::Resize
, nullptr );
3009 pThis
->TriggerPaintEvent();
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
)
3034 pThis
->TriggerPaintEvent();
3036 if (!pThis
->m_bFloatPositioned
)
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
)
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
)
3095 pThis
->m_bGeometryIsProvisional
= false;
3096 pThis
->maGeometry
.nX
= x
;
3097 pThis
->maGeometry
.nY
= y
;
3100 // update decoration hints
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();
3111 ImplSVData
* pSVData
= ImplGetSVData();
3112 if (pSVData
->maNWFData
.mbCanDetermineWindowPosition
)
3113 pThis
->CallCallbackExc(SalEvent::Move
, nullptr);
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
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();
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
);
3179 pGrabWidget
= GTK_WIDGET(pThis
->m_pFixedContainer
);
3180 bool bHasFocus
= gtk_widget_has_focus(pGrabWidget
);
3181 pThis
->CallCallbackExc(bHasFocus
? SalEvent::GetFocus
: SalEvent::LoseFocus
, nullptr);
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
);
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();
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;
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
))
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
);
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
;
3278 void* pData
= g_object_get_data(G_OBJECT(pSearch
), "InterimWindowGlue");
3281 xTopLevelInterimWindow
= static_cast<vcl::Window
*>(pData
);
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
))
3298 bool bStopProcessingKey
= false;
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
;
3320 case GDK_KEY_Control_R
:
3321 nExtModMask
= ModKeyFlags::RightMod1
;
3322 nModMask
= KEY_MOD1
;
3325 nExtModMask
= ModKeyFlags::LeftMod2
;
3326 nModMask
= KEY_MOD2
;
3329 nExtModMask
= ModKeyFlags::RightMod2
;
3330 nModMask
= KEY_MOD2
;
3332 case GDK_KEY_Shift_L
:
3333 nExtModMask
= ModKeyFlags::LeftShift
;
3334 nModMask
= KEY_SHIFT
;
3336 case GDK_KEY_Shift_R
:
3337 nExtModMask
= ModKeyFlags::RightShift
;
3338 nModMask
= KEY_SHIFT
;
3340 // Map Meta/Super to MOD3 modifier on all Unix systems
3342 case GDK_KEY_Meta_L
:
3343 case GDK_KEY_Super_L
:
3344 nExtModMask
= ModKeyFlags::LeftMod3
;
3345 nModMask
= KEY_MOD3
;
3347 case GDK_KEY_Meta_R
:
3348 case GDK_KEY_Super_R
:
3349 nExtModMask
= ModKeyFlags::RightMod3
;
3350 nModMask
= KEY_MOD3
;
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
;
3365 aModEvt
.mnCode
= nModCode
| nModMask
;
3366 pThis
->m_nKeyModifiers
|= nExtModMask
;
3367 aModEvt
.mnModKeyCode
= pThis
->m_nKeyModifiers
;
3370 pThis
->CallCallbackExc( SalEvent::KeyModChange
, &aModEvt
);
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
,
3392 pEvent
->hardware_keycode
,
3394 sal_Unicode(gdk_keyval_to_unicode( pEvent
->keyval
)),
3395 (pEvent
->type
== GDK_KEY_PRESS
),
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 );
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 "
3486 << ((pEvent
->window_state
.new_window_state
&
3487 GDK_WINDOW_STATE_FULLSCREEN
) ?
3490 << " full screen state.");
3496 gboolean
GtkSalFrame::signalVisibility( GtkWidget
*, GdkEventVisibility
* /*pEvent*/, gpointer
/*frame*/ )
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
);
3515 sal_Int8
GdkToVcl(GdkDragAction dragOperation
)
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
;
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
;
3545 static bool g_DropSuccessSet
= false;
3546 static bool g_DropSuccess
= false;
3550 class GtkDropTargetDropContext
: public cppu::WeakImplHelper
<css::datatransfer::dnd::XDropTargetDropContext
>
3552 GdkDragContext
*m_pContext
;
3555 GtkDropTargetDropContext(GdkDragContext
*pContext
, guint nTime
)
3556 : m_pContext(pContext
)
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
;
3589 GtkWidget
*m_pWidget
;
3590 GtkDropTarget
* m_pDropTarget
;
3592 GtkSelectionData
*m_pData
;
3594 GtkDnDTransferable(GdkDragContext
*pContext
, guint nTime
, GtkWidget
*pWidget
, GtkDropTarget
*pDropTarget
)
3595 : m_pContext(pContext
)
3597 , m_pWidget(pWidget
)
3598 , m_pDropTarget(pDropTarget
)
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
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
);
3633 m_pDropTarget
->SetFormatConversionRequest(nullptr);
3638 if (aFlavor
.MimeType
== "text/plain;charset=utf-8")
3641 gchar
*pText
= reinterpret_cast<gchar
*>(gtk_selection_data_get_text(m_pData
));
3643 aStr
= OUString(pText
, rtl_str_getLength(pText
), RTL_TEXTENCODING_UTF8
);
3645 aRet
<<= aStr
.replaceAll("\r\n", "\n");
3650 const guchar
*rawdata
= gtk_selection_data_get_data_with_length(m_pData
,
3652 // seen here was rawhide == nullptr and length set to -1
3655 css::uno::Sequence
<sal_Int8
> aSeq(reinterpret_cast<const sal_Int8
*>(rawdata
), length
);
3660 gtk_selection_data_free(m_pData
);
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
)
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
)
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
3698 g_idle_remove_by_data(this);
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();
3726 xTransferable
= new GtkDnDTransferable(context
, time
, pWidget
, this);
3727 aEvent
.Transferable
= xTransferable
;
3736 class GtkDropTargetDragContext
: public cppu::WeakImplHelper
<css::datatransfer::dnd::XDropTargetDragContext
>
3738 GdkDragContext
*m_pContext
;
3741 GtkDropTargetDragContext(GdkDragContext
*pContext
, guint nTime
)
3742 : m_pContext(pContext
)
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
)
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
)
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
)
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
)
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
);
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
;
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();
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
);
3858 fire_dragOver(aEvent
);
3864 void GtkSalFrame::signalDragLeave(GtkWidget
*pWidget
, GdkDragContext
*context
, guint time
, gpointer frame
)
3866 GtkSalFrame
* pThis
= static_cast<GtkSalFrame
*>(frame
);
3867 if (!pThis
->m_pDropTarget
)
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
);
3881 void GtkDropTarget::signalDragLeave(GtkWidget
* pWidget
, GdkDragContext
* /*context*/, guint
/*time*/)
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
)
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
)
3914 m_nPrevKeyPresses( 0 ),
3915 m_pIMContext( nullptr ),
3917 m_bPreeditJustChanged( false )
3919 m_aInputEvent
.mpTextAttr
= nullptr;
3923 GtkSalFrame::IMHandler::~IMHandler()
3925 // cancel an eventual event posted to begin preedit again
3926 GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame
, &m_aInputEvent
, SalEvent::ExtTextInput
);
3930 void GtkSalFrame::IMHandler::createIMContext()
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();
3957 void GtkSalFrame::IMHandler::deleteIMContext()
3961 // first give IC a chance to deinitialize
3962 GetGenericUnixSalData()->ErrorTrapPush();
3963 gtk_im_context_set_client_window( m_pIMContext
, nullptr );
3964 GetGenericUnixSalData()->ErrorTrapPop();
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
) );
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
)
4012 vcl::DeletionListener
aDel( m_pFrame
);
4013 // delete preedit in sal (commit an empty string)
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();
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
;
4032 GetGenericUnixSalData()->ErrorTrapPush();
4033 gtk_im_context_focus_in( m_pIMContext
);
4034 GetGenericUnixSalData()->ErrorTrapPop();
4035 if( m_aInputEvent
.mpTextAttr
)
4038 // begin preedit again
4039 GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame
, &m_aInputEvent
, SalEvent::ExtTextInput
);
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
4063 m_aPrevKeyPresses
.emplace_back(pEvent
);
4064 m_nPrevKeyPresses
++;
4066 // Also pop off the earliest key press event if there are more than 10
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() )
4082 bool bResult
= gtk_im_context_filter_keypress( m_pIMContext
, pEvent
);
4083 g_object_unref( pRef
);
4085 if( aDel
.isDeleted() )
4088 m_bPreeditJustChanged
= false;
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() )
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
--;
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
)
4149 case GDK_KEY_KP_Enter
:
4150 case GDK_KEY_Return
:
4151 if( cCode
!= '\n' && cCode
!= '\r' )
4155 case GDK_KEY_KP_Space
:
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;
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
,
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
4251 else if (nCursorPos
> nUtf32Len
)
4252 nCursorPos
= nUtf32Len
;
4254 rCursorPos
= aUtf16Offsets
[nCursorPos
];
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
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
)
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
)
4291 tmp_list
= attr_list
= pango_attr_iterator_get_attrs (iter
);
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
;
4302 case PANGO_ATTR_UNDERLINE
:
4303 sal_attr
|= ExtTextInputAttr::Underline
;
4305 case PANGO_ATTR_STRIKETHROUGH
:
4306 sal_attr
|= ExtTextInputAttr::RedText
;
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()))
4328 rInputFlags
[i
] |= sal_attr
;
4330 } while (pango_attr_iterator_next (iter
));
4331 pango_attr_iterator_destroy(iter
);
4334 pango_attr_list_unref( pAttrs
);
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
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());
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)
4416 // Note that offset and n_chars are in characters not in bytes
4417 // which differs from the usage other places in GtkIMContext
4421 while (nOffset
&& nCursorIndex
< rSurroundingText
.getLength())
4423 rSurroundingText
.iterateCodePoints(&nCursorIndex
, 1);
4427 else if (nOffset
< 0)
4429 while (nOffset
&& nCursorIndex
> 0)
4431 rSurroundingText
.iterateCodePoints(&nCursorIndex
, -1);
4438 SAL_WARN("vcl.gtk", "IM delete-surrounding, unable to move to offset: " << nOffset
);
4442 sal_Int32
nCursorEndIndex(nCursorIndex
);
4443 sal_Int32
nCount(0);
4444 while (nCount
< nChars
&& nCursorEndIndex
< rSurroundingText
.getLength())
4446 rSurroundingText
.iterateCodePoints(&nCursorEndIndex
, 1);
4450 if (nCount
!= nChars
)
4452 SAL_WARN("vcl.gtk", "IM delete-surrounding, unable to select: " << nChars
<< " characters");
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
,
4476 Selection
aInvalid(SAL_MAX_UINT32
, SAL_MAX_UINT32
);
4477 if (aSelection
== aInvalid
)
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
)
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
);
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
));
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
;
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
);
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
)
4565 else if (aEvent
.Buttons
& css::awt::MouseButton::RIGHT
)
4567 else if (aEvent
.Buttons
& css::awt::MouseButton::MIDDLE
)
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
)
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(),
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
)
4629 pThis
->m_pDragSource
->dragFailed();
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
)
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
;
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
)
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
)
4692 pThis
->m_pDragSource
->dragDataGet(data
, info
);
4695 bool GtkSalFrame::CallCallbackExc(SalEvent nEvent
, const void* pEvent
) const
4700 nRet
= CallCallback(nEvent
, pEvent
);
4704 GtkSalData
*pSalData
= static_cast<GtkSalData
*>(GetSalData());
4705 pSalData
->setException(std::current_exception());
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;
4738 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */