Version 6.1.4.1, tag libreoffice-6.1.4.1
[LibreOffice.git] / vcl / unx / gtk3 / gtk3gtkframe.cxx
blob71ab50efc81351535747f6175b310a3deadf3fe4
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <unx/gtk/gtkframe.hxx>
21 #include <unx/gtk/gtkdata.hxx>
22 #include <unx/gtk/gtkinst.hxx>
23 #include <unx/gtk/gtkgdi.hxx>
24 #include <unx/gtk/gtksalmenu.hxx>
25 #include <unx/gtk/hudawareness.h>
26 #include <vcl/help.hxx>
27 #include <vcl/keycodes.hxx>
28 #include <vcl/layout.hxx>
29 #include <unx/wmadaptor.hxx>
30 #include <unx/sm.hxx>
31 #include <unx/salbmp.h>
32 #include <unx/genprn.h>
33 #include <unx/geninst.h>
34 #include <headless/svpgdi.hxx>
35 #include <o3tl/runtimetooustring.hxx>
36 #include <osl/file.hxx>
37 #include <rtl/bootstrap.hxx>
38 #include <rtl/process.h>
39 #include <vcl/floatwin.hxx>
40 #include <vcl/svapp.hxx>
41 #include <vcl/weld.hxx>
42 #include <vcl/window.hxx>
43 #include <vcl/settings.hxx>
44 #include <cppuhelper/exc_hlp.hxx>
46 #include <config_gio.h>
48 #include <gtk/gtk.h>
50 #include <X11/Xlib.h>
51 #include <X11/Xutil.h>
52 #include <X11/Xatom.h>
53 #if defined(GDK_WINDOWING_X11)
54 # include <gdk/gdkx.h>
55 #endif
56 #if defined(GDK_WINDOWING_WAYLAND)
57 # include <gdk/gdkwayland.h>
58 #endif
60 #include <dlfcn.h>
61 #include <vcl/salbtype.hxx>
62 #include <window.h>
63 #include <strings.hrc>
64 #include <bitmaps.hlst>
65 #include <sal/macros.h>
67 #include <basegfx/range/b2ibox.hxx>
68 #include <basegfx/vector/b2ivector.hxx>
70 #include <algorithm>
71 #include <glib/gprintf.h>
73 #if OSL_DEBUG_LEVEL > 1
74 # include <cstdio>
75 #endif
77 #include <i18nlangtag/mslangid.hxx>
79 #include <cstdlib>
80 #include <cmath>
82 #include <comphelper/processfactory.hxx>
83 #include <comphelper/sequenceashashmap.hxx>
84 #include <com/sun/star/accessibility/XAccessibleContext.hpp>
85 #include <com/sun/star/accessibility/AccessibleRole.hpp>
86 #include <com/sun/star/accessibility/XAccessibleStateSet.hpp>
87 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
88 #include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
89 #include <com/sun/star/awt/MouseButton.hpp>
90 #include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
91 #include <com/sun/star/frame/Desktop.hpp>
92 #include <com/sun/star/frame/ModuleManager.hpp>
93 #include <com/sun/star/frame/XFrame.hpp>
94 #include <com/sun/star/util/URLTransformer.hpp>
96 #include <config_folders.h>
98 #define IS_WIDGET_REALIZED gtk_widget_get_realized
99 #define IS_WIDGET_MAPPED gtk_widget_get_mapped
101 #ifndef GDK_IS_X11_DISPLAY
102 #define GDK_IS_X11_DISPLAY(foo) (true)
103 #endif
106 using namespace com::sun::star;
108 int GtkSalFrame::m_nFloats = 0;
110 static GDBusConnection* pSessionBus = nullptr;
112 sal_uInt16 GtkSalFrame::GetKeyModCode( guint state )
114 sal_uInt16 nCode = 0;
115 if( state & GDK_SHIFT_MASK )
116 nCode |= KEY_SHIFT;
117 if( state & GDK_CONTROL_MASK )
118 nCode |= KEY_MOD1;
119 if( state & GDK_MOD1_MASK )
120 nCode |= KEY_MOD2;
121 if( state & GDK_SUPER_MASK )
122 nCode |= KEY_MOD3;
123 return nCode;
126 sal_uInt16 GtkSalFrame::GetMouseModCode( guint state )
128 sal_uInt16 nCode = GetKeyModCode( state );
129 if( state & GDK_BUTTON1_MASK )
130 nCode |= MOUSE_LEFT;
131 if( state & GDK_BUTTON2_MASK )
132 nCode |= MOUSE_MIDDLE;
133 if( state & GDK_BUTTON3_MASK )
134 nCode |= MOUSE_RIGHT;
136 return nCode;
139 sal_uInt16 GtkSalFrame::GetKeyCode(guint keyval)
141 sal_uInt16 nCode = 0;
142 if( keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9 )
143 nCode = KEY_0 + (keyval-GDK_KEY_0);
144 else if( keyval >= GDK_KEY_KP_0 && keyval <= GDK_KEY_KP_9 )
145 nCode = KEY_0 + (keyval-GDK_KEY_KP_0);
146 else if( keyval >= GDK_KEY_A && keyval <= GDK_KEY_Z )
147 nCode = KEY_A + (keyval-GDK_KEY_A );
148 else if( keyval >= GDK_KEY_a && keyval <= GDK_KEY_z )
149 nCode = KEY_A + (keyval-GDK_KEY_a );
150 else if( keyval >= GDK_KEY_F1 && keyval <= GDK_KEY_F26 )
151 // KEY_F26 is the last function key known to keycodes.hxx
153 switch( keyval )
155 // - - - - - Sun keyboard, see vcl/unx/source/app/saldisp.cxx
156 // althopugh GDK_KEY_F1 ... GDK_KEY_L10 are known to
157 // gdk/gdkkeysyms.h, they are unlikely to be generated
158 // except possibly by Solaris systems
159 // this whole section needs review
160 case GDK_KEY_L2:
161 nCode = KEY_F12;
162 break;
163 case GDK_KEY_L3: nCode = KEY_PROPERTIES; break;
164 case GDK_KEY_L4: nCode = KEY_UNDO; break;
165 case GDK_KEY_L6: nCode = KEY_COPY; break; // KEY_F16
166 case GDK_KEY_L8: nCode = KEY_PASTE; break; // KEY_F18
167 case GDK_KEY_L10: nCode = KEY_CUT; break; // KEY_F20
168 default:
169 nCode = KEY_F1 + (keyval-GDK_KEY_F1); break;
172 else
174 switch( keyval )
176 case GDK_KEY_KP_Down:
177 case GDK_KEY_Down: nCode = KEY_DOWN; break;
178 case GDK_KEY_KP_Up:
179 case GDK_KEY_Up: nCode = KEY_UP; break;
180 case GDK_KEY_KP_Left:
181 case GDK_KEY_Left: nCode = KEY_LEFT; break;
182 case GDK_KEY_KP_Right:
183 case GDK_KEY_Right: nCode = KEY_RIGHT; break;
184 case GDK_KEY_KP_Begin:
185 case GDK_KEY_KP_Home:
186 case GDK_KEY_Begin:
187 case GDK_KEY_Home: nCode = KEY_HOME; break;
188 case GDK_KEY_KP_End:
189 case GDK_KEY_End: nCode = KEY_END; break;
190 case GDK_KEY_KP_Page_Up:
191 case GDK_KEY_Page_Up: nCode = KEY_PAGEUP; break;
192 case GDK_KEY_KP_Page_Down:
193 case GDK_KEY_Page_Down: nCode = KEY_PAGEDOWN; break;
194 case GDK_KEY_KP_Enter:
195 case GDK_KEY_Return: nCode = KEY_RETURN; break;
196 case GDK_KEY_Escape: nCode = KEY_ESCAPE; break;
197 case GDK_KEY_ISO_Left_Tab:
198 case GDK_KEY_KP_Tab:
199 case GDK_KEY_Tab: nCode = KEY_TAB; break;
200 case GDK_KEY_BackSpace: nCode = KEY_BACKSPACE; break;
201 case GDK_KEY_KP_Space:
202 case GDK_KEY_space: nCode = KEY_SPACE; break;
203 case GDK_KEY_KP_Insert:
204 case GDK_KEY_Insert: nCode = KEY_INSERT; break;
205 case GDK_KEY_KP_Delete:
206 case GDK_KEY_Delete: nCode = KEY_DELETE; break;
207 case GDK_KEY_plus:
208 case GDK_KEY_KP_Add: nCode = KEY_ADD; break;
209 case GDK_KEY_minus:
210 case GDK_KEY_KP_Subtract: nCode = KEY_SUBTRACT; break;
211 case GDK_KEY_asterisk:
212 case GDK_KEY_KP_Multiply: nCode = KEY_MULTIPLY; break;
213 case GDK_KEY_slash:
214 case GDK_KEY_KP_Divide: nCode = KEY_DIVIDE; break;
215 case GDK_KEY_period: nCode = KEY_POINT; break;
216 case GDK_KEY_decimalpoint: nCode = KEY_POINT; break;
217 case GDK_KEY_comma: nCode = KEY_COMMA; break;
218 case GDK_KEY_less: nCode = KEY_LESS; break;
219 case GDK_KEY_greater: nCode = KEY_GREATER; break;
220 case GDK_KEY_KP_Equal:
221 case GDK_KEY_equal: nCode = KEY_EQUAL; break;
222 case GDK_KEY_Find: nCode = KEY_FIND; break;
223 case GDK_KEY_Menu: nCode = KEY_CONTEXTMENU;break;
224 case GDK_KEY_Help: nCode = KEY_HELP; break;
225 case GDK_KEY_Undo: nCode = KEY_UNDO; break;
226 case GDK_KEY_Redo: nCode = KEY_REPEAT; break;
227 // on a sun keyboard this actually is usually SunXK_Stop = 0x0000FF69 (XK_Cancel),
228 // but VCL doesn't have a key definition for that
229 case GDK_KEY_Cancel: nCode = KEY_F11; break;
230 case GDK_KEY_KP_Decimal:
231 case GDK_KEY_KP_Separator: nCode = KEY_DECIMAL; break;
232 case GDK_KEY_asciitilde: nCode = KEY_TILDE; break;
233 case GDK_KEY_leftsinglequotemark:
234 case GDK_KEY_quoteleft: nCode = KEY_QUOTELEFT; break;
235 case GDK_KEY_bracketleft: nCode = KEY_BRACKETLEFT; break;
236 case GDK_KEY_bracketright: nCode = KEY_BRACKETRIGHT; break;
237 case GDK_KEY_semicolon: nCode = KEY_SEMICOLON; break;
238 case GDK_KEY_quoteright: nCode = KEY_QUOTERIGHT; break;
239 // some special cases, also see saldisp.cxx
240 // - - - - - - - - - - - - - Apollo - - - - - - - - - - - - - 0x1000
241 // These can be found in ap_keysym.h
242 case 0x1000FF02: // apXK_Copy
243 nCode = KEY_COPY;
244 break;
245 case 0x1000FF03: // apXK_Cut
246 nCode = KEY_CUT;
247 break;
248 case 0x1000FF04: // apXK_Paste
249 nCode = KEY_PASTE;
250 break;
251 case 0x1000FF14: // apXK_Repeat
252 nCode = KEY_REPEAT;
253 break;
254 // Exit, Save
255 // - - - - - - - - - - - - - - D E C - - - - - - - - - - - - - 0x1000
256 // These can be found in DECkeysym.h
257 case 0x1000FF00:
258 nCode = KEY_DELETE;
259 break;
260 // - - - - - - - - - - - - - - H P - - - - - - - - - - - - - 0x1000
261 // These can be found in HPkeysym.h
262 case 0x1000FF73: // hpXK_DeleteChar
263 nCode = KEY_DELETE;
264 break;
265 case 0x1000FF74: // hpXK_BackTab
266 case 0x1000FF75: // hpXK_KP_BackTab
267 nCode = KEY_TAB;
268 break;
269 // - - - - - - - - - - - - - - I B M - - - - - - - - - - - - -
270 // - - - - - - - - - - - - - - O S F - - - - - - - - - - - - - 0x1004
271 // These also can be found in HPkeysym.h
272 case 0x1004FF02: // osfXK_Copy
273 nCode = KEY_COPY;
274 break;
275 case 0x1004FF03: // osfXK_Cut
276 nCode = KEY_CUT;
277 break;
278 case 0x1004FF04: // osfXK_Paste
279 nCode = KEY_PASTE;
280 break;
281 case 0x1004FF07: // osfXK_BackTab
282 nCode = KEY_TAB;
283 break;
284 case 0x1004FF08: // osfXK_BackSpace
285 nCode = KEY_BACKSPACE;
286 break;
287 case 0x1004FF1B: // osfXK_Escape
288 nCode = KEY_ESCAPE;
289 break;
290 // Up, Down, Left, Right, PageUp, PageDown
291 // - - - - - - - - - - - - - - S C O - - - - - - - - - - - - -
292 // - - - - - - - - - - - - - - S G I - - - - - - - - - - - - - 0x1007
293 // - - - - - - - - - - - - - - S N I - - - - - - - - - - - - -
294 // - - - - - - - - - - - - - - S U N - - - - - - - - - - - - - 0x1005
295 // These can be found in Sunkeysym.h
296 case 0x1005FF10: // SunXK_F36
297 nCode = KEY_F11;
298 break;
299 case 0x1005FF11: // SunXK_F37
300 nCode = KEY_F12;
301 break;
302 case 0x1005FF70: // SunXK_Props
303 nCode = KEY_PROPERTIES;
304 break;
305 case 0x1005FF71: // SunXK_Front
306 nCode = KEY_FRONT;
307 break;
308 case 0x1005FF72: // SunXK_Copy
309 nCode = KEY_COPY;
310 break;
311 case 0x1005FF73: // SunXK_Open
312 nCode = KEY_OPEN;
313 break;
314 case 0x1005FF74: // SunXK_Paste
315 nCode = KEY_PASTE;
316 break;
317 case 0x1005FF75: // SunXK_Cut
318 nCode = KEY_CUT;
319 break;
320 // - - - - - - - - - - - - - X F 8 6 - - - - - - - - - - - - - 0x1008
321 // These can be found in XF86keysym.h
322 // but more importantly they are also available GTK/Gdk version 3
323 // and hence are already provided in gdk/gdkkeysyms.h, and hence
324 // in gdk/gdk.h
325 case GDK_KEY_Copy: nCode = KEY_COPY; break; // 0x1008ff57
326 case GDK_KEY_Cut: nCode = KEY_CUT; break; // 0x1008ff58
327 case GDK_KEY_Open: nCode = KEY_OPEN; break; // 0x1008ff6b
328 case GDK_KEY_Paste: nCode = KEY_PASTE; break; // 0x1008ff6d
332 return nCode;
335 guint GtkSalFrame::GetKeyValFor(GdkKeymap* pKeyMap, guint16 hardware_keycode, guint8 group)
337 guint updated_keyval = 0;
338 gdk_keymap_translate_keyboard_state(pKeyMap, hardware_keycode,
339 GdkModifierType(0), group, &updated_keyval, nullptr, nullptr, nullptr);
340 return updated_keyval;
343 // F10 means either KEY_F10 or KEY_MENU, which has to be decided
344 // in the independent part.
345 struct KeyAlternate
347 sal_uInt16 nKeyCode;
348 sal_Unicode nCharCode;
349 KeyAlternate() : nKeyCode( 0 ), nCharCode( 0 ) {}
350 KeyAlternate( sal_uInt16 nKey, sal_Unicode nChar = 0 ) : nKeyCode( nKey ), nCharCode( nChar ) {}
353 inline KeyAlternate
354 GetAlternateKeyCode( const sal_uInt16 nKeyCode )
356 KeyAlternate aAlternate;
358 switch( nKeyCode )
360 case KEY_F10: aAlternate = KeyAlternate( KEY_MENU );break;
361 case KEY_F24: aAlternate = KeyAlternate( KEY_SUBTRACT, '-' );break;
364 return aAlternate;
367 #if OSL_DEBUG_LEVEL > 0
368 static bool dumpframes = false;
369 #endif
371 bool GtkSalFrame::doKeyCallback( guint state,
372 guint keyval,
373 guint16 hardware_keycode,
374 guint8 group,
375 sal_Unicode aOrigCode,
376 bool bDown,
377 bool bSendRelease
380 SalKeyEvent aEvent;
382 aEvent.mnCharCode = aOrigCode;
383 aEvent.mnRepeat = 0;
385 vcl::DeletionListener aDel( this );
387 #if OSL_DEBUG_LEVEL > 0
388 const char* pKeyDebug = getenv("VCL_GTK3_PAINTDEBUG");
390 if (pKeyDebug && *pKeyDebug == '1')
392 if (bDown)
394 // shift-zero forces a re-draw and event is swallowed
395 if (keyval == GDK_KEY_0)
397 fprintf( stderr, "force widget_queue_draw\n");
398 gtk_widget_queue_draw(GTK_WIDGET(m_pFixedContainer));
399 return false;
401 else if (keyval == GDK_KEY_1)
403 fprintf( stderr, "force repaint all\n");
404 TriggerPaintEvent();
405 return false;
407 else if (keyval == GDK_KEY_2)
409 dumpframes = !dumpframes;
410 fprintf(stderr, "toggle dump frames to %d\n", dumpframes);
411 return false;
415 #endif
418 * #i42122# translate all keys with Ctrl and/or Alt to group 0 else
419 * shortcuts (e.g. Ctrl-o) will not work but be inserted by the
420 * application
422 * #i52338# do this for all keys that the independent part has no key code
423 * for
425 * fdo#41169 rather than use group 0, detect if there is a group which can
426 * be used to input Latin text and use that if possible
428 aEvent.mnCode = GetKeyCode( keyval );
429 if( aEvent.mnCode == 0 )
431 gint best_group = SAL_MAX_INT32;
433 // Try and find Latin layout
434 GdkKeymap* keymap = gdk_keymap_get_default();
435 GdkKeymapKey *keys;
436 gint n_keys;
437 if (gdk_keymap_get_entries_for_keyval(keymap, GDK_KEY_A, &keys, &n_keys))
439 // Find the lowest group that supports Latin layout
440 for (gint i = 0; i < n_keys; ++i)
442 if (keys[i].level != 0 && keys[i].level != 1)
443 continue;
444 best_group = std::min(best_group, keys[i].group);
445 if (best_group == 0)
446 break;
448 g_free(keys);
451 //Unavailable, go with original group then I suppose
452 if (best_group == SAL_MAX_INT32)
453 best_group = group;
455 guint updated_keyval = GetKeyValFor(keymap, hardware_keycode, best_group);
456 aEvent.mnCode = GetKeyCode(updated_keyval);
459 aEvent.mnCode |= GetKeyModCode( state );
461 bool bStopProcessingKey;
462 if (bDown)
464 bStopProcessingKey = CallCallbackExc(SalEvent::KeyInput, &aEvent);
465 // #i46889# copy AlternateKeyCode handling from generic plugin
466 if (!bStopProcessingKey)
468 KeyAlternate aAlternate = GetAlternateKeyCode( aEvent.mnCode );
469 if( aAlternate.nKeyCode )
471 aEvent.mnCode = aAlternate.nKeyCode;
472 if( aAlternate.nCharCode )
473 aEvent.mnCharCode = aAlternate.nCharCode;
474 bStopProcessingKey = CallCallbackExc(SalEvent::KeyInput, &aEvent);
477 if( bSendRelease && ! aDel.isDeleted() )
479 CallCallbackExc(SalEvent::KeyUp, &aEvent);
482 else
483 bStopProcessingKey = CallCallbackExc(SalEvent::KeyUp, &aEvent);
484 return bStopProcessingKey;
487 GtkSalFrame::GtkSalFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
488 : m_nXScreen( getDisplay()->GetDefaultXScreen() )
489 , m_pHeaderBar(nullptr)
490 , m_pGraphics(nullptr)
491 , m_bGraphics(false)
493 getDisplay()->registerFrame( this );
494 m_bDefaultPos = true;
495 m_bDefaultSize = ( (nStyle & SalFrameStyleFlags::SIZEABLE) && ! pParent );
496 m_bWindowIsGtkPlug = false;
497 Init( pParent, nStyle );
500 GtkSalFrame::GtkSalFrame( SystemParentData* pSysData )
501 : m_nXScreen( getDisplay()->GetDefaultXScreen() )
502 , m_pHeaderBar(nullptr)
503 , m_pGraphics(nullptr)
504 , m_bGraphics(false)
506 getDisplay()->registerFrame( this );
507 // permanently ignore errors from our unruly children ...
508 GetGenericUnixSalData()->ErrorTrapPush();
509 m_bDefaultPos = true;
510 m_bDefaultSize = true;
511 Init( pSysData );
514 // AppMenu watch functions.
516 static void ObjectDestroyedNotify( gpointer data )
518 if ( data ) {
519 g_object_unref( data );
523 static void hud_activated( gboolean hud_active, gpointer user_data )
525 if ( hud_active )
527 SolarMutexGuard aGuard;
528 GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
529 GtkSalMenu* pSalMenu = reinterpret_cast< GtkSalMenu* >( pSalFrame->GetMenu() );
531 if ( pSalMenu )
532 pSalMenu->UpdateFull();
536 static void activate_uno(GSimpleAction *action, GVariant*, gpointer)
538 uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
540 uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create( xContext );
542 uno::Reference < css::frame::XFrame > xFrame(xDesktop->getActiveFrame());
543 if (!xFrame.is())
544 xFrame.set(xDesktop, uno::UNO_QUERY);
546 if (!xFrame.is())
547 return;
549 uno::Reference< css::frame::XDispatchProvider > xDispatchProvider(xFrame, uno::UNO_QUERY);
550 if (!xDispatchProvider.is())
551 return;
553 gchar *strval = nullptr;
554 g_object_get(action, "name", &strval, nullptr);
555 if (!strval)
556 return;
558 if (strcmp(strval, "New") == 0)
560 g_free(strval);
562 uno::Reference<frame::XModuleManager2> xModuleManager(frame::ModuleManager::create(xContext));
563 OUString aModuleId(xModuleManager->identify(xFrame));
564 if (aModuleId.isEmpty())
565 return;
567 comphelper::SequenceAsHashMap lModuleDescription(xModuleManager->getByName(aModuleId));
568 OUString sFactoryService;
569 lModuleDescription[OUString("ooSetupFactoryEmptyDocumentURL")] >>= sFactoryService;
570 if (sFactoryService.isEmpty())
571 return;
573 uno::Sequence < css::beans::PropertyValue > args(0);
574 xDesktop->loadComponentFromURL(sFactoryService, "_blank", 0, args);
575 return;
578 OUString sCommand(".uno:");
579 sCommand += OUString(strval, strlen(strval), RTL_TEXTENCODING_UTF8);
580 g_free(strval);
582 css::util::URL aCommand;
583 aCommand.Complete = sCommand;
584 uno::Reference< css::util::XURLTransformer > xParser = css::util::URLTransformer::create(xContext);
585 xParser->parseStrict(aCommand);
587 uno::Reference< css::frame::XDispatch > xDisp = xDispatchProvider->queryDispatch(aCommand, OUString(), 0);
589 if (!xDisp.is())
590 return;
592 xDisp->dispatch(aCommand, css::uno::Sequence< css::beans::PropertyValue >());
595 static const GActionEntry app_entries[] = {
596 { "OptionsTreeDialog", activate_uno, nullptr, nullptr, nullptr, {0} },
597 { "About", activate_uno, nullptr, nullptr, nullptr, {0} },
598 { "HelpIndex", activate_uno, nullptr, nullptr, nullptr, {0} },
599 { "Quit", activate_uno, nullptr, nullptr, nullptr, {0} },
600 { "New", activate_uno, nullptr, nullptr, nullptr, {0} }
603 gboolean ensure_dbus_setup( gpointer data )
605 GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( data );
606 GdkWindow* gdkWindow = widget_get_window( pSalFrame->getWindow() );
608 if ( gdkWindow != nullptr && g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) == nullptr )
610 // Get a DBus session connection.
611 if(!pSessionBus)
612 pSessionBus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, nullptr);
613 if( !pSessionBus )
615 return FALSE;
618 // Create menu model and action group attached to this frame.
619 GMenuModel* pMenuModel = G_MENU_MODEL( g_lo_menu_new() );
620 GActionGroup* pActionGroup = reinterpret_cast<GActionGroup*>(g_lo_action_group_new());
622 // Generate menu paths.
623 sal_uIntPtr windowId = pSalFrame->GetNativeWindowHandle(pSalFrame->getWindow());
624 gchar* aDBusWindowPath = g_strdup_printf( "/org/libreoffice/window/%lu", windowId );
625 gchar* aDBusMenubarPath = g_strdup_printf( "/org/libreoffice/window/%lu/menus/menubar", windowId );
627 // Set window properties.
628 g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-menubar", pMenuModel, ObjectDestroyedNotify );
629 g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-action-group", pActionGroup, ObjectDestroyedNotify );
631 GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
632 // fdo#70885 we don't want app menu under Unity
633 const bool bDesktopIsUnity = (SalGetDesktopEnvironment() == "UNITY");
634 #if defined(GDK_WINDOWING_X11)
635 if (GDK_IS_X11_DISPLAY(pDisplay))
637 gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_ID", "org.libreoffice" );
638 if (!bDesktopIsUnity)
639 gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APP_MENU_OBJECT_PATH", "/org/libreoffice/menus/appmenu" );
640 gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_MENUBAR_OBJECT_PATH", aDBusMenubarPath );
641 gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_WINDOW_OBJECT_PATH", aDBusWindowPath );
642 gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_OBJECT_PATH", "/org/libreoffice" );
643 gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_UNIQUE_BUS_NAME", g_dbus_connection_get_unique_name( pSessionBus ) );
645 #endif
646 #if defined(GDK_WINDOWING_WAYLAND)
647 if (GDK_IS_WAYLAND_DISPLAY(pDisplay))
649 gdk_wayland_window_set_dbus_properties_libgtk_only(gdkWindow, "org.libreoffice",
650 "/org/libreoffice/menus/appmenu",
651 !bDesktopIsUnity ? aDBusMenubarPath : nullptr,
652 aDBusWindowPath,
653 "/org/libreoffice",
654 g_dbus_connection_get_unique_name( pSessionBus ));
656 #endif
657 // Publish the menu model and the action group.
658 SAL_INFO("vcl.unity", "exporting menu model at " << pMenuModel << " for window " << windowId);
659 pSalFrame->m_nMenuExportId = g_dbus_connection_export_menu_model (pSessionBus, aDBusMenubarPath, pMenuModel, nullptr);
660 SAL_INFO("vcl.unity", "exporting action group at " << pActionGroup << " for window " << windowId);
661 pSalFrame->m_nActionGroupExportId = g_dbus_connection_export_action_group( pSessionBus, aDBusWindowPath, pActionGroup, nullptr);
662 pSalFrame->m_nHudAwarenessId = hud_awareness_register( pSessionBus, aDBusMenubarPath, hud_activated, pSalFrame, nullptr, nullptr );
664 //app menu, to-do translations, block normal menus when active, honor use appmenu settings
665 if (!bDesktopIsUnity)
667 GMenu *menu = g_menu_new ();
668 GMenuItem* item;
670 GMenu *firstsubmenu = g_menu_new ();
672 OString sNew(OUStringToOString(VclResId(SV_BUTTONTEXT_NEW),
673 RTL_TEXTENCODING_UTF8).replaceFirst("~", "_"));
675 item = g_menu_item_new(sNew.getStr(), "app.New");
676 g_menu_append_item( firstsubmenu, item );
677 g_object_unref(item);
679 g_menu_append_section( menu, nullptr, G_MENU_MODEL(firstsubmenu));
680 g_object_unref(firstsubmenu);
682 GMenu *secondsubmenu = g_menu_new ();
684 OString sPreferences(OUStringToOString(VclResId(SV_STDTEXT_PREFERENCES),
685 RTL_TEXTENCODING_UTF8).replaceFirst("~", "_"));
687 item = g_menu_item_new(sPreferences.getStr(), "app.OptionsTreeDialog");
688 g_menu_append_item( secondsubmenu, item );
689 g_object_unref(item);
691 g_menu_append_section( menu, nullptr, G_MENU_MODEL(secondsubmenu));
692 g_object_unref(secondsubmenu);
694 GMenu *thirdsubmenu = g_menu_new ();
696 OString sHelp(OUStringToOString(VclResId(SV_BUTTONTEXT_HELP),
697 RTL_TEXTENCODING_UTF8).replaceFirst("~", "_"));
699 item = g_menu_item_new(sHelp.getStr(), "app.HelpIndex");
700 g_menu_append_item( thirdsubmenu, item );
701 g_object_unref(item);
703 OString sAbout(OUStringToOString(VclResId(SV_STDTEXT_ABOUT),
704 RTL_TEXTENCODING_UTF8).replaceFirst("~", "_"));
706 item = g_menu_item_new(sAbout.getStr(), "app.About");
707 g_menu_append_item( thirdsubmenu, item );
708 g_object_unref(item);
710 OString sQuit(OUStringToOString(VclResId(SV_MENU_MAC_QUITAPP),
711 RTL_TEXTENCODING_UTF8).replaceFirst("~", "_"));
713 item = g_menu_item_new(sQuit.getStr(), "app.Quit");
714 g_menu_append_item( thirdsubmenu, item );
715 g_object_unref(item);
716 g_menu_append_section( menu, nullptr, G_MENU_MODEL(thirdsubmenu));
717 g_object_unref(thirdsubmenu);
719 GSimpleActionGroup *group = g_simple_action_group_new ();
720 g_action_map_add_action_entries (G_ACTION_MAP (group), app_entries, G_N_ELEMENTS (app_entries), nullptr);
721 GActionGroup* pAppActionGroup = G_ACTION_GROUP(group);
723 pSalFrame->m_nAppActionGroupExportId = g_dbus_connection_export_action_group( pSessionBus, "/org/libreoffice", pAppActionGroup, nullptr);
724 g_object_unref(pAppActionGroup);
725 pSalFrame->m_nAppMenuExportId = g_dbus_connection_export_menu_model (pSessionBus, "/org/libreoffice/menus/appmenu", G_MENU_MODEL (menu), nullptr);
726 g_object_unref(menu);
729 g_free( aDBusMenubarPath );
730 g_free( aDBusWindowPath );
733 return FALSE;
736 void on_registrar_available( GDBusConnection * /*connection*/,
737 const gchar * /*name*/,
738 const gchar * /*name_owner*/,
739 gpointer user_data )
741 SolarMutexGuard aGuard;
743 GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
745 SalMenu* pSalMenu = pSalFrame->GetMenu();
747 if ( pSalMenu != nullptr )
749 GtkSalMenu* pGtkSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
750 pGtkSalMenu->EnableUnity(true);
754 // This is called when the registrar becomes unavailable. It shows the menubar.
755 void on_registrar_unavailable( GDBusConnection * /*connection*/,
756 const gchar * /*name*/,
757 gpointer user_data )
759 SolarMutexGuard aGuard;
761 SAL_INFO("vcl.unity", "on_registrar_unavailable");
763 //pSessionBus = NULL;
764 GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
766 SalMenu* pSalMenu = pSalFrame->GetMenu();
768 if ( pSalMenu ) {
769 GtkSalMenu* pGtkSalMenu = static_cast< GtkSalMenu* >( pSalMenu );
770 pGtkSalMenu->EnableUnity(false);
774 void GtkSalFrame::EnsureAppMenuWatch()
776 if ( !m_nWatcherId )
778 // Get a DBus session connection.
779 if ( pSessionBus == nullptr )
781 pSessionBus = g_bus_get_sync( G_BUS_TYPE_SESSION, nullptr, nullptr );
783 if ( pSessionBus == nullptr )
784 return;
787 // Publish the menu only if AppMenu registrar is available.
788 m_nWatcherId = g_bus_watch_name_on_connection( pSessionBus,
789 "com.canonical.AppMenu.Registrar",
790 G_BUS_NAME_WATCHER_FLAGS_NONE,
791 on_registrar_available,
792 on_registrar_unavailable,
793 this,
794 nullptr );
798 void GtkSalFrame::InvalidateGraphics()
800 if( m_pGraphics )
802 m_bGraphics = false;
806 GtkSalFrame::~GtkSalFrame()
808 m_aSmoothScrollIdle.Stop();
809 m_aSmoothScrollIdle.ClearInvokeHandler();
811 if (m_pDropTarget)
813 m_pDropTarget->deinitialize();
814 m_pDropTarget = nullptr;
817 if (m_pDragSource)
819 m_pDragSource->deinitialize();
820 m_pDragSource= nullptr;
823 InvalidateGraphics();
825 if (m_pParent)
827 m_pParent->m_aChildren.remove( this );
830 getDisplay()->deregisterFrame( this );
832 if( m_pRegion )
834 cairo_region_destroy( m_pRegion );
837 m_pIMHandler.reset();
839 //tdf#108705 remove grabs on event widget before
840 //destroying event widget
841 while (m_nGrabLevel)
842 removeGrabLevel();
844 GtkWidget *pEventWidget = getMouseEventWidget();
845 for (auto handler_id : m_aMouseSignalIds)
846 g_signal_handler_disconnect(G_OBJECT(pEventWidget), handler_id);
847 if( m_pFixedContainer )
848 gtk_widget_destroy( GTK_WIDGET( m_pFixedContainer ) );
849 if( m_pEventBox )
850 gtk_widget_destroy( GTK_WIDGET(m_pEventBox) );
851 if( m_pTopLevelGrid )
852 gtk_widget_destroy( GTK_WIDGET(m_pTopLevelGrid) );
854 SolarMutexGuard aGuard;
856 if(m_nWatcherId)
857 g_bus_unwatch_name(m_nWatcherId);
859 if( m_pWindow )
861 g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", nullptr );
863 if ( pSessionBus )
865 if ( m_nHudAwarenessId )
866 hud_awareness_unregister( pSessionBus, m_nHudAwarenessId );
867 if ( m_nMenuExportId )
868 g_dbus_connection_unexport_menu_model( pSessionBus, m_nMenuExportId );
869 if ( m_nAppMenuExportId )
870 g_dbus_connection_unexport_menu_model( pSessionBus, m_nAppMenuExportId );
871 if ( m_nActionGroupExportId )
872 g_dbus_connection_unexport_action_group( pSessionBus, m_nActionGroupExportId );
873 if ( m_nAppActionGroupExportId )
874 g_dbus_connection_unexport_action_group( pSessionBus, m_nAppActionGroupExportId );
876 gtk_widget_destroy( m_pWindow );
879 if( m_pForeignParent )
880 g_object_unref( G_OBJECT( m_pForeignParent ) );
881 if( m_pForeignTopLevel )
882 g_object_unref( G_OBJECT( m_pForeignTopLevel) );
884 m_pGraphics.reset();
886 if (m_pSurface)
887 cairo_surface_destroy(m_pSurface);
890 void GtkSalFrame::moveWindow( long nX, long nY )
892 if( isChild( false ) )
894 if( m_pParent )
895 gtk_fixed_move( m_pParent->getFixedContainer(),
896 m_pWindow,
897 nX - m_pParent->maGeometry.nX, nY - m_pParent->maGeometry.nY );
899 else
900 gtk_window_move( GTK_WINDOW(m_pWindow), nX, nY );
903 void GtkSalFrame::widget_set_size_request(long nWidth, long nHeight)
905 gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), nWidth, nHeight );
908 void GtkSalFrame::window_resize(long nWidth, long nHeight)
910 m_nWidthRequest = nWidth;
911 m_nHeightRequest = nHeight;
912 gtk_window_set_default_size(GTK_WINDOW(m_pWindow), nWidth, nHeight);
913 if (gtk_widget_get_visible(m_pWindow))
914 gtk_window_resize(GTK_WINDOW(m_pWindow), nWidth, nHeight);
917 void GtkSalFrame::resizeWindow( long nWidth, long nHeight )
919 if( isChild( false ) )
921 widget_set_size_request(nWidth, nHeight);
923 else if( ! isChild( true, false ) )
924 window_resize(nWidth, nHeight);
927 static void
928 ooo_fixed_class_init(GtkFixedClass *klass)
930 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
931 widget_class->get_accessible = ooo_fixed_get_accessible;
935 * Always use a sub-class of GtkFixed we can tag for a11y. This allows us to
936 * utilize GAIL for the toplevel window and toolkit implementation incl.
937 * key event listener support ..
940 GType
941 ooo_fixed_get_type()
943 static GType type = 0;
945 if (!type) {
946 static const GTypeInfo tinfo =
948 sizeof (GtkFixedClass),
949 nullptr, /* base init */
950 nullptr, /* base finalize */
951 reinterpret_cast<GClassInitFunc>(ooo_fixed_class_init), /* class init */
952 nullptr, /* class finalize */
953 nullptr, /* class data */
954 sizeof (GtkFixed), /* instance size */
955 0, /* nb preallocs */
956 nullptr, /* instance init */
957 nullptr /* value table */
960 type = g_type_register_static( GTK_TYPE_FIXED, "OOoFixed",
961 &tinfo, GTypeFlags(0));
964 return type;
967 void GtkSalFrame::updateScreenNumber()
969 int nScreen = 0;
970 GdkScreen *pScreen = gtk_widget_get_screen( m_pWindow );
971 if( pScreen )
972 nScreen = getDisplay()->getSystem()->getScreenMonitorIdx( pScreen, maGeometry.nX, maGeometry.nY );
973 maGeometry.nDisplayScreenNumber = nScreen;
976 GtkWidget *GtkSalFrame::getMouseEventWidget() const
978 return GTK_WIDGET(m_pEventBox);
981 static void damaged(void *handle,
982 sal_Int32 nExtentsX, sal_Int32 nExtentsY,
983 sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight)
985 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(handle);
986 pThis->damaged(nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight);
989 void GtkSalFrame::InitCommon()
991 m_pSurface = nullptr;
992 m_nGrabLevel = 0;
993 m_bSalObjectSetPosSize = false;
995 m_aDamageHandler.handle = this;
996 m_aDamageHandler.damaged = ::damaged;
998 m_aSmoothScrollIdle.SetInvokeHandler(LINK(this, GtkSalFrame, AsyncScroll));
1000 m_pTopLevelGrid = GTK_GRID(gtk_grid_new());
1001 gtk_container_add(GTK_CONTAINER(m_pWindow), GTK_WIDGET(m_pTopLevelGrid));
1003 m_pEventBox = GTK_EVENT_BOX(gtk_event_box_new());
1004 gtk_widget_add_events( GTK_WIDGET(m_pEventBox),
1005 GDK_ALL_EVENTS_MASK );
1006 gtk_widget_set_vexpand(GTK_WIDGET(m_pEventBox), true);
1007 gtk_widget_set_hexpand(GTK_WIDGET(m_pEventBox), true);
1008 gtk_grid_attach(m_pTopLevelGrid, GTK_WIDGET(m_pEventBox), 0, 0, 1, 1);
1010 // add the fixed container child,
1011 // fixed is needed since we have to position plugin windows
1012 m_pFixedContainer = GTK_FIXED(g_object_new( ooo_fixed_get_type(), nullptr ));
1013 gtk_container_add( GTK_CONTAINER(m_pEventBox), GTK_WIDGET(m_pFixedContainer) );
1015 GtkWidget *pEventWidget = getMouseEventWidget();
1017 gtk_widget_set_app_paintable(GTK_WIDGET(m_pFixedContainer), true);
1018 /*non-X11 displays won't show anything at all without double-buffering
1019 enabled*/
1020 if (GDK_IS_X11_DISPLAY(getGdkDisplay()))
1021 gtk_widget_set_double_buffered(GTK_WIDGET(m_pFixedContainer), false);
1022 gtk_widget_set_redraw_on_allocate(GTK_WIDGET(m_pFixedContainer), false);
1025 // connect signals
1026 g_signal_connect( G_OBJECT(m_pWindow), "style-updated", G_CALLBACK(signalStyleUpdated), this );
1027 gtk_widget_set_has_tooltip(pEventWidget, true);
1028 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "query-tooltip", G_CALLBACK(signalTooltipQuery), this ));
1029 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-press-event", G_CALLBACK(signalButton), this ));
1030 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "motion-notify-event", G_CALLBACK(signalMotion), this ));
1031 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-release-event", G_CALLBACK(signalButton), this ));
1032 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "leave-notify-event", G_CALLBACK(signalCrossing), this ));
1033 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "enter-notify-event", G_CALLBACK(signalCrossing), this ));
1036 //Drop Target Stuff
1037 gtk_drag_dest_set(GTK_WIDGET(pEventWidget), GtkDestDefaults(0), nullptr, 0, GdkDragAction(0));
1038 gtk_drag_dest_set_track_motion(GTK_WIDGET(pEventWidget), true);
1039 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-motion", G_CALLBACK(signalDragMotion), this ));
1040 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-drop", G_CALLBACK(signalDragDrop), this ));
1041 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-received", G_CALLBACK(signalDragDropReceived), this ));
1042 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-leave", G_CALLBACK(signalDragLeave), this ));
1044 //Drag Source Stuff
1045 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-end", G_CALLBACK(signalDragEnd), this ));
1046 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-failed", G_CALLBACK(signalDragFailed), this ));
1047 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-delete", G_CALLBACK(signalDragDelete), this ));
1048 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-get", G_CALLBACK(signalDragDataGet), this ));
1049 m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "scroll-event", G_CALLBACK(signalScroll), this ));
1051 g_signal_connect( G_OBJECT(m_pFixedContainer), "draw", G_CALLBACK(signalDraw), this );
1052 g_signal_connect( G_OBJECT(m_pFixedContainer), "realize", G_CALLBACK(signalRealize), this );
1053 g_signal_connect( G_OBJECT(m_pFixedContainer), "size-allocate", G_CALLBACK(sizeAllocated), this );
1055 GtkGesture *pSwipe = gtk_gesture_swipe_new(pEventWidget);
1056 g_signal_connect(pSwipe, "swipe", G_CALLBACK(gestureSwipe), this);
1057 gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pSwipe), GTK_PHASE_TARGET);
1058 g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pSwipe);
1060 GtkGesture *pLongPress = gtk_gesture_long_press_new(pEventWidget);
1061 g_signal_connect(pLongPress, "pressed", G_CALLBACK(gestureLongPress), this);
1062 gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pLongPress), GTK_PHASE_TARGET);
1063 g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pLongPress);
1065 g_signal_connect( G_OBJECT(m_pWindow), "focus-in-event", G_CALLBACK(signalFocus), this );
1066 g_signal_connect( G_OBJECT(m_pWindow), "focus-out-event", G_CALLBACK(signalFocus), this );
1067 g_signal_connect( G_OBJECT(m_pWindow), "map-event", G_CALLBACK(signalMap), this );
1068 g_signal_connect( G_OBJECT(m_pWindow), "unmap-event", G_CALLBACK(signalUnmap), this );
1069 g_signal_connect( G_OBJECT(m_pWindow), "configure-event", G_CALLBACK(signalConfigure), this );
1070 g_signal_connect( G_OBJECT(m_pWindow), "key-press-event", G_CALLBACK(signalKey), this );
1071 g_signal_connect( G_OBJECT(m_pWindow), "key-release-event", G_CALLBACK(signalKey), this );
1072 g_signal_connect( G_OBJECT(m_pWindow), "delete-event", G_CALLBACK(signalDelete), this );
1073 g_signal_connect( G_OBJECT(m_pWindow), "window-state-event", G_CALLBACK(signalWindowState), this );
1074 g_signal_connect( G_OBJECT(m_pWindow), "visibility-notify-event", G_CALLBACK(signalVisibility), this );
1075 g_signal_connect( G_OBJECT(m_pWindow), "destroy", G_CALLBACK(signalDestroy), this );
1077 // init members
1078 m_pCurrentCursor = nullptr;
1079 m_nKeyModifiers = ModKeyFlags::NONE;
1080 m_bFullscreen = false;
1081 m_bSpanMonitorsWhenFullscreen = false;
1082 m_nState = GDK_WINDOW_STATE_WITHDRAWN;
1083 m_pIMHandler = nullptr;
1084 m_pRegion = nullptr;
1085 m_pDropTarget = nullptr;
1086 m_pDragSource = nullptr;
1087 m_bInDrag = false;
1088 m_pFormatConversionRequest = nullptr;
1089 m_bGeometryIsProvisional = false;
1090 m_ePointerStyle = static_cast<PointerStyle>(0xffff);
1091 m_pSalMenu = nullptr;
1092 m_nWatcherId = 0;
1093 m_nMenuExportId = 0;
1094 m_nAppMenuExportId = 0;
1095 m_nActionGroupExportId = 0;
1096 m_nAppActionGroupExportId = 0;
1097 m_nHudAwarenessId = 0;
1099 gtk_widget_add_events( m_pWindow,
1100 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1101 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
1102 GDK_VISIBILITY_NOTIFY_MASK | GDK_SCROLL_MASK
1105 // show the widgets
1106 gtk_widget_show_all(GTK_WIDGET(m_pTopLevelGrid));
1108 // realize the window, we need an XWindow id
1109 gtk_widget_realize( m_pWindow );
1111 //system data
1112 m_aSystemData.nSize = sizeof( SystemEnvData );
1113 m_aSystemData.aWindow = GetNativeWindowHandle(m_pWindow);
1114 m_aSystemData.aShellWindow = reinterpret_cast<sal_IntPtr>(this);
1115 m_aSystemData.pSalFrame = this;
1116 m_aSystemData.pWidget = m_pWindow;
1117 m_aSystemData.nScreen = m_nXScreen.getXScreen();
1118 m_aSystemData.pToolkit = "gtk3";
1119 GdkScreen* pScreen = gtk_window_get_screen(GTK_WINDOW(m_pWindow));
1120 GdkVisual* pVisual = gdk_screen_get_system_visual(pScreen);
1122 #if defined(GDK_WINDOWING_X11)
1123 GdkDisplay *pDisplay = getGdkDisplay();
1124 if (GDK_IS_X11_DISPLAY(pDisplay))
1126 m_aSystemData.pDisplay = gdk_x11_display_get_xdisplay(pDisplay);
1127 m_aSystemData.pVisual = gdk_x11_visual_get_xvisual(pVisual);
1129 #endif
1131 m_bGraphics = false;
1132 m_pGraphics = nullptr;
1134 m_nFloatFlags = FloatWinPopupFlags::NONE;
1135 m_bFloatPositioned = false;
1137 m_nWidthRequest = 0;
1138 m_nHeightRequest = 0;
1140 // fake an initial geometry, gets updated via configure event or SetPosSize
1141 if (m_bDefaultPos || m_bDefaultSize)
1143 Size aDefSize = calcDefaultSize();
1144 maGeometry.nX = -1;
1145 maGeometry.nY = -1;
1146 maGeometry.nWidth = aDefSize.Width();
1147 maGeometry.nHeight = aDefSize.Height();
1148 maGeometry.nTopDecoration = 0;
1149 maGeometry.nBottomDecoration = 0;
1150 maGeometry.nLeftDecoration = 0;
1151 maGeometry.nRightDecoration = 0;
1153 updateScreenNumber();
1155 SetIcon(SV_ICON_ID_OFFICE);
1158 GtkSalFrame *GtkSalFrame::getFromWindow( GtkWindow *pWindow )
1160 return static_cast<GtkSalFrame *>(g_object_get_data( G_OBJECT( pWindow ), "SalFrame" ));
1163 void GtkSalFrame::Init( SalFrame* pParent, SalFrameStyleFlags nStyle )
1165 if( nStyle & SalFrameStyleFlags::DEFAULT ) // ensure default style
1167 nStyle |= SalFrameStyleFlags::MOVEABLE | SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::CLOSEABLE;
1168 nStyle &= ~SalFrameStyleFlags::FLOAT;
1171 m_pParent = static_cast<GtkSalFrame*>(pParent);
1172 m_pForeignParent = nullptr;
1173 m_aForeignParentWindow = None;
1174 m_pForeignTopLevel = nullptr;
1175 m_aForeignTopLevelWindow = None;
1176 m_nStyle = nStyle;
1178 GtkWindowType eWinType = ( (nStyle & SalFrameStyleFlags::FLOAT) &&
1179 ! (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION)
1181 ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL;
1183 if( nStyle & SalFrameStyleFlags::SYSTEMCHILD )
1185 m_pWindow = gtk_event_box_new();
1186 if( m_pParent )
1188 // insert into container
1189 gtk_fixed_put( m_pParent->getFixedContainer(),
1190 m_pWindow, 0, 0 );
1194 else
1196 m_pWindow = gtk_window_new(eWinType);
1199 g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", this );
1200 g_object_set_data( G_OBJECT( m_pWindow ), "libo-version", const_cast<char *>(LIBO_VERSION_DOTTED));
1202 // force wm class hint
1203 if (!isChild())
1205 if (m_pParent)
1206 m_sWMClass = m_pParent->m_sWMClass;
1207 updateWMClass();
1210 if( m_pParent && m_pParent->m_pWindow && ! isChild() )
1211 gtk_window_set_screen( GTK_WINDOW(m_pWindow), gtk_window_get_screen( GTK_WINDOW(m_pParent->m_pWindow) ) );
1213 if (m_pParent)
1215 if (!(m_pParent->m_nStyle & SalFrameStyleFlags::PLUG))
1216 gtk_window_set_transient_for( GTK_WINDOW(m_pWindow), GTK_WINDOW(m_pParent->m_pWindow) );
1217 m_pParent->m_aChildren.push_back( this );
1218 gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), GTK_WINDOW(m_pWindow));
1220 else
1222 gtk_window_group_add_window(gtk_window_group_new(), GTK_WINDOW(m_pWindow));
1223 g_object_unref(gtk_window_get_group(GTK_WINDOW(m_pWindow)));
1226 // set window type
1227 bool bDecoHandling =
1228 ! isChild() &&
1229 ( ! (nStyle & SalFrameStyleFlags::FLOAT) ||
1230 (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) );
1232 if( bDecoHandling )
1234 GdkWindowTypeHint eType = GDK_WINDOW_TYPE_HINT_NORMAL;
1235 if( (nStyle & SalFrameStyleFlags::DIALOG) && m_pParent != nullptr )
1236 eType = GDK_WINDOW_TYPE_HINT_DIALOG;
1237 if( nStyle & SalFrameStyleFlags::INTRO )
1239 gtk_window_set_role( GTK_WINDOW(m_pWindow), "splashscreen" );
1240 eType = GDK_WINDOW_TYPE_HINT_SPLASHSCREEN;
1242 else if( nStyle & SalFrameStyleFlags::TOOLWINDOW )
1244 eType = GDK_WINDOW_TYPE_HINT_DIALOG;
1245 gtk_window_set_skip_taskbar_hint( GTK_WINDOW(m_pWindow), true );
1247 else if( nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
1249 eType = GDK_WINDOW_TYPE_HINT_TOOLBAR;
1250 gtk_window_set_focus_on_map(GTK_WINDOW(m_pWindow), false);
1251 gtk_window_set_decorated(GTK_WINDOW(m_pWindow), false);
1253 gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), eType );
1254 gtk_window_set_gravity( GTK_WINDOW(m_pWindow), GDK_GRAVITY_STATIC );
1255 gtk_window_set_resizable( GTK_WINDOW(m_pWindow), bool(nStyle & SalFrameStyleFlags::SIZEABLE) );
1257 #if defined(GDK_WINDOWING_WAYLAND)
1258 //rhbz#1392145 under wayland/csd if we've overridden the default widget direction in order to set LibreOffice's
1259 //UI to the configured ui language but the system ui locale is a different text direction, then the toplevel
1260 //built-in close button of the titlebar follows the overridden direction rather than continue in the same
1261 //direction as every other titlebar on the user's desktop. So if they don't match set an explicit
1262 //header bar with the desired 'outside' direction
1263 if ((eType == GDK_WINDOW_TYPE_HINT_NORMAL || eType == GDK_WINDOW_TYPE_HINT_DIALOG) && GDK_IS_WAYLAND_DISPLAY(GtkSalFrame::getGdkDisplay()))
1265 const bool bDesktopIsRTL = MsLangId::isRightToLeft(MsLangId::getSystemUILanguage());
1266 const bool bAppIsRTL = gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL;
1267 if (bDesktopIsRTL != bAppIsRTL)
1269 m_pHeaderBar = GTK_HEADER_BAR(gtk_header_bar_new());
1270 gtk_widget_set_direction(GTK_WIDGET(m_pHeaderBar), bDesktopIsRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
1271 gtk_header_bar_set_show_close_button(m_pHeaderBar, true);
1272 gtk_window_set_titlebar(GTK_WINDOW(m_pWindow), GTK_WIDGET(m_pHeaderBar));
1273 gtk_widget_show(GTK_WIDGET(m_pHeaderBar));
1276 #endif
1278 else if( nStyle & SalFrameStyleFlags::FLOAT )
1279 gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), GDK_WINDOW_TYPE_HINT_POPUP_MENU );
1281 InitCommon();
1283 if( eWinType == GTK_WINDOW_TOPLEVEL )
1285 // Enable DBus native menu if available.
1286 ensure_dbus_setup( this );
1291 GdkNativeWindow GtkSalFrame::findTopLevelSystemWindow( GdkNativeWindow )
1293 //FIXME: no findToplevelSystemWindow
1294 return 0;
1297 void GtkSalFrame::Init( SystemParentData* pSysData )
1299 m_pParent = nullptr;
1300 m_aForeignParentWindow = pSysData->aWindow;
1301 m_pForeignParent = nullptr;
1302 m_aForeignTopLevelWindow = findTopLevelSystemWindow(pSysData->aWindow);
1303 m_pForeignTopLevel = gdk_window_foreign_new_for_display( getGdkDisplay(), m_aForeignTopLevelWindow );
1304 gdk_window_set_events( m_pForeignTopLevel, GDK_STRUCTURE_MASK );
1306 if( pSysData->nSize > sizeof(pSysData->nSize)+sizeof(pSysData->aWindow) && pSysData->bXEmbedSupport )
1308 m_pWindow = gtk_plug_new_for_display( getGdkDisplay(), pSysData->aWindow );
1309 m_bWindowIsGtkPlug = true;
1310 widget_set_can_default( m_pWindow, true );
1311 widget_set_can_focus( m_pWindow, true );
1312 gtk_widget_set_sensitive( m_pWindow, true );
1314 else
1316 m_pWindow = gtk_window_new( GTK_WINDOW_POPUP );
1317 m_bWindowIsGtkPlug = false;
1319 m_nStyle = SalFrameStyleFlags::PLUG;
1320 InitCommon();
1322 m_pForeignParent = gdk_window_foreign_new_for_display( getGdkDisplay(), m_aForeignParentWindow );
1323 gdk_window_set_events( m_pForeignParent, GDK_STRUCTURE_MASK );
1325 //FIXME: Handling embedded windows, is going to be fun ...
1328 void GtkSalFrame::SetExtendedFrameStyle(SalExtStyle)
1332 SalGraphics* GtkSalFrame::AcquireGraphics()
1334 if( m_bGraphics )
1335 return nullptr;
1337 if( !m_pGraphics )
1339 m_pGraphics.reset( new GtkSalGraphics( this, m_pWindow ) );
1340 if (!m_pSurface)
1342 AllocateFrame();
1343 TriggerPaintEvent();
1345 m_pGraphics->setSurface(m_pSurface, m_aFrameSize);
1347 m_bGraphics = true;
1348 return m_pGraphics.get();
1351 void GtkSalFrame::ReleaseGraphics( SalGraphics* pGraphics )
1353 (void) pGraphics;
1354 assert( pGraphics == m_pGraphics.get() );
1355 m_bGraphics = false;
1358 bool GtkSalFrame::PostEvent(ImplSVEvent* pData)
1360 getDisplay()->SendInternalEvent( this, pData );
1361 return true;
1364 void GtkSalFrame::SetTitle( const OUString& rTitle )
1366 m_aTitle = rTitle;
1367 if( m_pWindow && ! isChild() )
1369 OString sTitle(OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8));
1370 gtk_window_set_title(GTK_WINDOW(m_pWindow), sTitle.getStr());
1371 if (m_pHeaderBar)
1372 gtk_header_bar_set_title(m_pHeaderBar, sTitle.getStr());
1376 void GtkSalFrame::SetIcon( sal_uInt16 nIcon )
1378 if( (m_nStyle & (SalFrameStyleFlags::PLUG|SalFrameStyleFlags::SYSTEMCHILD|SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::INTRO|SalFrameStyleFlags::OWNERDRAWDECORATION))
1379 || ! m_pWindow )
1380 return;
1382 gchar* appicon;
1384 if (nIcon == SV_ICON_ID_TEXT)
1385 appicon = g_strdup ("libreoffice-writer");
1386 else if (nIcon == SV_ICON_ID_SPREADSHEET)
1387 appicon = g_strdup ("libreoffice-calc");
1388 else if (nIcon == SV_ICON_ID_DRAWING)
1389 appicon = g_strdup ("libreoffice-draw");
1390 else if (nIcon == SV_ICON_ID_PRESENTATION)
1391 appicon = g_strdup ("libreoffice-impress");
1392 else if (nIcon == SV_ICON_ID_DATABASE)
1393 appicon = g_strdup ("libreoffice-base");
1394 else if (nIcon == SV_ICON_ID_FORMULA)
1395 appicon = g_strdup ("libreoffice-math");
1396 else
1397 appicon = g_strdup ("libreoffice-startcenter");
1399 gtk_window_set_icon_name (GTK_WINDOW (m_pWindow), appicon);
1400 g_free (appicon);
1403 void GtkSalFrame::SetMenu( SalMenu* pSalMenu )
1405 m_pSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
1408 SalMenu* GtkSalFrame::GetMenu()
1410 return m_pSalMenu;
1413 void GtkSalFrame::DrawMenuBar()
1417 void GtkSalFrame::Center()
1419 if (m_pParent)
1420 gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER_ON_PARENT);
1421 else
1422 gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER);
1425 Size GtkSalFrame::calcDefaultSize()
1427 Size aScreenSize(getDisplay()->GetScreenSize(GetDisplayScreen()));
1428 int scale = gtk_widget_get_scale_factor(m_pWindow);
1429 aScreenSize.setWidth( aScreenSize.Width() / scale );
1430 aScreenSize.setHeight( aScreenSize.Height() / scale );
1431 return bestmaxFrameSizeForScreenSize(aScreenSize);
1434 void GtkSalFrame::SetDefaultSize()
1436 Size aDefSize = calcDefaultSize();
1438 SetPosSize( 0, 0, aDefSize.Width(), aDefSize.Height(),
1439 SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
1441 if( (m_nStyle & SalFrameStyleFlags::DEFAULT) && m_pWindow )
1442 gtk_window_maximize( GTK_WINDOW(m_pWindow) );
1445 void GtkSalFrame::Show( bool bVisible, bool /*bNoActivate*/ )
1447 if( m_pWindow )
1449 if( bVisible )
1451 getDisplay()->startupNotificationCompleted();
1453 if( m_bDefaultPos )
1454 Center();
1455 if( m_bDefaultSize )
1456 SetDefaultSize();
1457 setMinMaxSize();
1459 if (isFloatGrabWindow() && !getDisplay()->GetCaptureFrame())
1461 m_pParent->grabPointer(true, true);
1462 m_pParent->addGrabLevel();
1465 #if defined(GDK_WINDOWING_WAYLAND)
1466 //rhbz#1334915, gnome#779143, tdf#100158
1467 //gtk under wayland lacks a way to change the app_id
1468 //of a window, so brute force everything as a
1469 //startcenter when initially shown to at least get
1470 //the default LibreOffice icon and not the broken
1471 //app icon
1472 if (GDK_IS_WAYLAND_DISPLAY(getGdkDisplay()))
1474 OString sOrigName(g_get_prgname());
1475 g_set_prgname("libreoffice-startcenter");
1476 gtk_widget_show(m_pWindow);
1477 g_set_prgname(sOrigName.getStr());
1479 else
1481 gtk_widget_show(m_pWindow);
1483 #else
1484 gtk_widget_show(m_pWindow);
1485 #endif
1487 if( isFloatGrabWindow() )
1489 m_nFloats++;
1490 if (!getDisplay()->GetCaptureFrame())
1492 grabPointer(true, true);
1493 addGrabLevel();
1495 // #i44068# reset parent's IM context
1496 if( m_pParent )
1497 m_pParent->EndExtTextInput(EndExtTextInputFlags::NONE);
1500 else
1502 if( isFloatGrabWindow() )
1504 m_nFloats--;
1505 if (!getDisplay()->GetCaptureFrame())
1507 removeGrabLevel();
1508 grabPointer(false);
1509 m_pParent->removeGrabLevel();
1510 m_pParent->grabPointer(false);
1513 gtk_widget_hide( m_pWindow );
1514 if( m_pIMHandler )
1515 m_pIMHandler->focusChanged( false );
1520 void GtkSalFrame::setMinMaxSize()
1522 /* #i34504# metacity (and possibly others) do not treat
1523 * _NET_WM_STATE_FULLSCREEN and max_width/height independently;
1524 * whether they should is undefined. So don't set the max size hint
1525 * for a full screen window.
1527 if( m_pWindow && ! isChild() )
1529 GdkGeometry aGeo;
1530 int aHints = 0;
1531 if( m_nStyle & SalFrameStyleFlags::SIZEABLE )
1533 if( m_aMinSize.Width() && m_aMinSize.Height() && ! m_bFullscreen )
1535 aGeo.min_width = m_aMinSize.Width();
1536 aGeo.min_height = m_aMinSize.Height();
1537 aHints |= GDK_HINT_MIN_SIZE;
1539 if( m_aMaxSize.Width() && m_aMaxSize.Height() && ! m_bFullscreen )
1541 aGeo.max_width = m_aMaxSize.Width();
1542 aGeo.max_height = m_aMaxSize.Height();
1543 aHints |= GDK_HINT_MAX_SIZE;
1546 else
1548 if (!m_bFullscreen && m_nWidthRequest && m_nHeightRequest)
1550 aGeo.min_width = m_nWidthRequest;
1551 aGeo.min_height = m_nHeightRequest;
1552 aHints |= GDK_HINT_MIN_SIZE;
1554 aGeo.max_width = m_nWidthRequest;
1555 aGeo.max_height = m_nHeightRequest;
1556 aHints |= GDK_HINT_MAX_SIZE;
1560 if( m_bFullscreen && m_aMaxSize.Width() && m_aMaxSize.Height() )
1562 aGeo.max_width = m_aMaxSize.Width();
1563 aGeo.max_height = m_aMaxSize.Height();
1564 aHints |= GDK_HINT_MAX_SIZE;
1566 if( aHints )
1568 gtk_window_set_geometry_hints( GTK_WINDOW(m_pWindow),
1569 nullptr,
1570 &aGeo,
1571 GdkWindowHints( aHints ) );
1576 void GtkSalFrame::SetMaxClientSize( long nWidth, long nHeight )
1578 if( ! isChild() )
1580 m_aMaxSize = Size( nWidth, nHeight );
1581 setMinMaxSize();
1584 void GtkSalFrame::SetMinClientSize( long nWidth, long nHeight )
1586 if( ! isChild() )
1588 m_aMinSize = Size( nWidth, nHeight );
1589 if( m_pWindow )
1591 widget_set_size_request(nWidth, nHeight);
1592 setMinMaxSize();
1597 void GtkSalFrame::AllocateFrame()
1599 basegfx::B2IVector aFrameSize( maGeometry.nWidth, maGeometry.nHeight );
1600 if (!m_pSurface || m_aFrameSize.getX() != aFrameSize.getX() ||
1601 m_aFrameSize.getY() != aFrameSize.getY() )
1603 if( aFrameSize.getX() == 0 )
1604 aFrameSize.setX( 1 );
1605 if( aFrameSize.getY() == 0 )
1606 aFrameSize.setY( 1 );
1608 if (m_pSurface)
1609 cairo_surface_destroy(m_pSurface);
1611 m_pSurface = gdk_window_create_similar_surface(widget_get_window(m_pWindow),
1612 CAIRO_CONTENT_COLOR_ALPHA,
1613 aFrameSize.getX(),
1614 aFrameSize.getY());
1615 m_aFrameSize = aFrameSize;
1617 cairo_surface_set_user_data(m_pSurface, SvpSalGraphics::getDamageKey(), &m_aDamageHandler, nullptr);
1618 SAL_INFO("vcl.gtk3", "allocated Frame size of " << maGeometry.nWidth << " x " << maGeometry.nHeight);
1620 if (m_pGraphics)
1621 m_pGraphics->setSurface(m_pSurface, m_aFrameSize);
1625 void GtkSalFrame::SetPosSize( long nX, long nY, long nWidth, long nHeight, sal_uInt16 nFlags )
1627 if( !m_pWindow || isChild( true, false ) )
1628 return;
1630 if( (nFlags & ( SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT )) &&
1631 (nWidth > 0 && nHeight > 0 ) // sometimes stupid things happen
1634 m_bDefaultSize = false;
1636 if( isChild( false ) )
1637 widget_set_size_request(nWidth, nHeight);
1638 else if( ! ( m_nState & GDK_WINDOW_STATE_MAXIMIZED ) )
1639 window_resize(nWidth, nHeight);
1641 setMinMaxSize();
1643 else if( m_bDefaultSize )
1644 SetDefaultSize();
1646 m_bDefaultSize = false;
1648 if( nFlags & ( SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ) )
1650 if( m_pParent )
1652 if( AllSettings::GetLayoutRTL() )
1653 nX = m_pParent->maGeometry.nWidth-m_nWidthRequest-1-nX;
1654 nX += m_pParent->maGeometry.nX;
1655 nY += m_pParent->maGeometry.nY;
1658 maGeometry.nX = nX;
1659 maGeometry.nY = nY;
1660 m_bGeometryIsProvisional = true;
1662 m_bDefaultPos = false;
1664 moveWindow(nX, nY);
1666 updateScreenNumber();
1668 else if( m_bDefaultPos )
1669 Center();
1671 m_bDefaultPos = false;
1674 void GtkSalFrame::GetClientSize( long& rWidth, long& rHeight )
1676 if( m_pWindow && !(m_nState & GDK_WINDOW_STATE_ICONIFIED) )
1678 rWidth = maGeometry.nWidth;
1679 rHeight = maGeometry.nHeight;
1681 else
1682 rWidth = rHeight = 0;
1685 void GtkSalFrame::GetWorkArea( tools::Rectangle& rRect )
1687 GdkScreen *pScreen = gtk_window_get_screen(GTK_WINDOW(m_pWindow));
1688 tools::Rectangle aRetRect;
1689 int max = gdk_screen_get_n_monitors (pScreen);
1690 for (int i = 0; i < max; ++i)
1692 GdkRectangle aRect;
1693 gdk_screen_get_monitor_workarea(pScreen, i, &aRect);
1694 tools::Rectangle aMonitorRect(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height);
1695 aRetRect.Union(aMonitorRect);
1697 rRect = aRetRect;
1700 SalFrame* GtkSalFrame::GetParent() const
1702 return m_pParent;
1705 void GtkSalFrame::SetWindowState( const SalFrameState* pState )
1707 if( ! m_pWindow || ! pState || isChild( true, false ) )
1708 return;
1710 const WindowStateMask nMaxGeometryMask =
1711 WindowStateMask::X | WindowStateMask::Y |
1712 WindowStateMask::Width | WindowStateMask::Height |
1713 WindowStateMask::MaximizedX | WindowStateMask::MaximizedY |
1714 WindowStateMask::MaximizedWidth | WindowStateMask::MaximizedHeight;
1716 if( (pState->mnMask & WindowStateMask::State) &&
1717 ! ( m_nState & GDK_WINDOW_STATE_MAXIMIZED ) &&
1718 (pState->mnState & WindowStateState::Maximized) &&
1719 (pState->mnMask & nMaxGeometryMask) == nMaxGeometryMask )
1721 resizeWindow( pState->mnWidth, pState->mnHeight );
1722 moveWindow( pState->mnX, pState->mnY );
1723 m_bDefaultPos = m_bDefaultSize = false;
1725 updateScreenNumber();
1727 m_nState = GdkWindowState( m_nState | GDK_WINDOW_STATE_MAXIMIZED );
1728 m_aRestorePosSize = tools::Rectangle( Point( pState->mnX, pState->mnY ),
1729 Size( pState->mnWidth, pState->mnHeight ) );
1731 else if( pState->mnMask & (WindowStateMask::X | WindowStateMask::Y |
1732 WindowStateMask::Width | WindowStateMask::Height ) )
1734 sal_uInt16 nPosSizeFlags = 0;
1735 long nX = pState->mnX - (m_pParent ? m_pParent->maGeometry.nX : 0);
1736 long nY = pState->mnY - (m_pParent ? m_pParent->maGeometry.nY : 0);
1737 if( pState->mnMask & WindowStateMask::X )
1738 nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
1739 else
1740 nX = maGeometry.nX - (m_pParent ? m_pParent->maGeometry.nX : 0);
1741 if( pState->mnMask & WindowStateMask::Y )
1742 nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
1743 else
1744 nY = maGeometry.nY - (m_pParent ? m_pParent->maGeometry.nY : 0);
1745 if( pState->mnMask & WindowStateMask::Width )
1746 nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
1747 if( pState->mnMask & WindowStateMask::Height )
1748 nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
1749 SetPosSize( nX, nY, pState->mnWidth, pState->mnHeight, nPosSizeFlags );
1751 if( pState->mnMask & WindowStateMask::State && ! isChild() )
1753 if( pState->mnState & WindowStateState::Maximized )
1754 gtk_window_maximize( GTK_WINDOW(m_pWindow) );
1755 else
1756 gtk_window_unmaximize( GTK_WINDOW(m_pWindow) );
1757 /* #i42379# there is no rollup state in GDK; and rolled up windows are
1758 * (probably depending on the WM) reported as iconified. If we iconify a
1759 * window here that was e.g. a dialog, then it will be unmapped but still
1760 * not be displayed in the task list, so it's an iconified window that
1761 * the user cannot get out of this state. So do not set the iconified state
1762 * on windows with a parent (that is transient frames) since these tend
1763 * to not be represented in an icon task list.
1765 if( (pState->mnState & WindowStateState::Minimized)
1766 && ! m_pParent )
1767 gtk_window_iconify( GTK_WINDOW(m_pWindow) );
1768 else
1769 gtk_window_deiconify( GTK_WINDOW(m_pWindow) );
1771 TriggerPaintEvent();
1774 namespace
1776 void GetPosAndSize(GtkWindow *pWindow, long& rX, long &rY, long &rWidth, long &rHeight)
1778 gint root_x, root_y;
1779 gtk_window_get_position(GTK_WINDOW(pWindow), &root_x, &root_y);
1780 rX = root_x;
1781 rY = root_y;
1782 gint width, height;
1783 gtk_window_get_size(GTK_WINDOW(pWindow), &width, &height);
1784 rWidth = width;
1785 rHeight = height;
1788 tools::Rectangle GetPosAndSize(GtkWindow *pWindow)
1790 long nX, nY, nWidth, nHeight;
1791 GetPosAndSize(pWindow, nX, nY, nWidth, nHeight);
1792 return tools::Rectangle(nX, nY, nX + nWidth, nY + nHeight);
1796 bool GtkSalFrame::GetWindowState( SalFrameState* pState )
1798 pState->mnState = WindowStateState::Normal;
1799 pState->mnMask = WindowStateMask::State;
1800 // rollup ? gtk 2.2 does not seem to support the shaded state
1801 if( m_nState & GDK_WINDOW_STATE_ICONIFIED )
1802 pState->mnState |= WindowStateState::Minimized;
1803 if( m_nState & GDK_WINDOW_STATE_MAXIMIZED )
1805 pState->mnState |= WindowStateState::Maximized;
1806 pState->mnX = m_aRestorePosSize.Left();
1807 pState->mnY = m_aRestorePosSize.Top();
1808 pState->mnWidth = m_aRestorePosSize.GetWidth();
1809 pState->mnHeight = m_aRestorePosSize.GetHeight();
1810 GetPosAndSize(GTK_WINDOW(m_pWindow), pState->mnMaximizedX, pState->mnMaximizedY,
1811 pState->mnMaximizedWidth, pState->mnMaximizedHeight);
1812 pState->mnMask |= WindowStateMask::MaximizedX |
1813 WindowStateMask::MaximizedY |
1814 WindowStateMask::MaximizedWidth |
1815 WindowStateMask::MaximizedHeight;
1817 else
1819 GetPosAndSize(GTK_WINDOW(m_pWindow), pState->mnX, pState->mnY,
1820 pState->mnWidth, pState->mnHeight);
1822 pState->mnMask |= WindowStateMask::X |
1823 WindowStateMask::Y |
1824 WindowStateMask::Width |
1825 WindowStateMask::Height;
1827 return true;
1830 void GtkSalFrame::SetScreen( unsigned int nNewScreen, SetType eType, tools::Rectangle const *pSize )
1832 if( !m_pWindow )
1833 return;
1835 if (maGeometry.nDisplayScreenNumber == nNewScreen && eType == SetType::RetainSize)
1836 return;
1838 int nX = maGeometry.nX, nY = maGeometry.nY,
1839 nWidth = maGeometry.nWidth, nHeight = maGeometry.nHeight;
1840 GdkScreen *pScreen = nullptr;
1841 GdkRectangle aNewMonitor;
1843 bool bSpanAllScreens = nNewScreen == static_cast<unsigned int>(-1);
1844 m_bSpanMonitorsWhenFullscreen = bSpanAllScreens && getDisplay()->getSystem()->GetDisplayScreenCount() > 1;
1845 gint nMonitor = -1;
1846 if (m_bSpanMonitorsWhenFullscreen) //span all screens
1848 pScreen = gtk_widget_get_screen( m_pWindow );
1849 aNewMonitor.x = 0;
1850 aNewMonitor.y = 0;
1851 aNewMonitor.width = gdk_screen_get_width(pScreen);
1852 aNewMonitor.height = gdk_screen_get_height(pScreen);
1854 else
1856 bool bSameMonitor = false;
1858 if (!bSpanAllScreens)
1860 pScreen = getDisplay()->getSystem()->getScreenMonitorFromIdx( nNewScreen, nMonitor );
1861 if (!pScreen)
1863 g_warning ("Attempt to move GtkSalFrame to invalid screen %d => "
1864 "fallback to current\n", nNewScreen);
1868 if (!pScreen)
1870 pScreen = gtk_widget_get_screen( m_pWindow );
1871 bSameMonitor = true;
1874 // Heavy lifting, need to move screen ...
1875 if( pScreen != gtk_widget_get_screen( m_pWindow ))
1876 gtk_window_set_screen( GTK_WINDOW( m_pWindow ), pScreen );
1878 gint nOldMonitor = gdk_screen_get_monitor_at_window(
1879 pScreen, widget_get_window( m_pWindow ) );
1880 if (bSameMonitor)
1881 nMonitor = nOldMonitor;
1883 #if OSL_DEBUG_LEVEL > 1
1884 if( nMonitor == nOldMonitor )
1885 g_warning( "An apparently pointless SetScreen - should we elide it ?" );
1886 #endif
1888 GdkRectangle aOldMonitor;
1889 gdk_screen_get_monitor_geometry( pScreen, nOldMonitor, &aOldMonitor );
1890 gdk_screen_get_monitor_geometry( pScreen, nMonitor, &aNewMonitor );
1892 nX = aNewMonitor.x + nX - aOldMonitor.x;
1893 nY = aNewMonitor.y + nY - aOldMonitor.y;
1896 bool bResize = false;
1897 bool bVisible = IS_WIDGET_MAPPED( m_pWindow );
1898 if( bVisible )
1899 Show( false );
1901 if( eType == SetType::Fullscreen )
1903 nX = aNewMonitor.x;
1904 nY = aNewMonitor.y;
1905 nWidth = aNewMonitor.width;
1906 nHeight = aNewMonitor.height;
1907 m_nStyle |= SalFrameStyleFlags::PARTIAL_FULLSCREEN;
1908 bResize = true;
1910 // #i110881# for the benefit of compiz set a max size here
1911 // else setting to fullscreen fails for unknown reasons
1912 m_aMaxSize.setWidth( aNewMonitor.width );
1913 m_aMaxSize.setHeight( aNewMonitor.height );
1916 if( pSize && eType == SetType::UnFullscreen )
1918 nX = pSize->Left();
1919 nY = pSize->Top();
1920 nWidth = pSize->GetWidth();
1921 nHeight = pSize->GetHeight();
1922 m_nStyle &= ~SalFrameStyleFlags::PARTIAL_FULLSCREEN;
1923 bResize = true;
1926 if (bResize)
1928 // temporarily re-sizeable
1929 if( !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
1930 gtk_window_set_resizable( GTK_WINDOW(m_pWindow), TRUE );
1931 window_resize(nWidth, nHeight);
1934 gtk_window_move(GTK_WINDOW(m_pWindow), nX, nY);
1936 gdk_window_set_fullscreen_mode( widget_get_window(m_pWindow), m_bSpanMonitorsWhenFullscreen
1937 ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR );
1939 GtkWidget* pMenuBarContainerWidget = m_pSalMenu ? m_pSalMenu->GetMenuBarContainerWidget() : nullptr;
1940 if( eType == SetType::Fullscreen )
1942 if (pMenuBarContainerWidget)
1943 gtk_widget_hide(pMenuBarContainerWidget);
1944 if (m_bSpanMonitorsWhenFullscreen)
1945 gtk_window_fullscreen(GTK_WINDOW(m_pWindow));
1946 else
1948 gtk_window_fullscreen_on_monitor(GTK_WINDOW(m_pWindow), pScreen, nMonitor);
1952 else if( eType == SetType::UnFullscreen )
1954 if (pMenuBarContainerWidget)
1955 gtk_widget_show(pMenuBarContainerWidget);
1956 gtk_window_unfullscreen( GTK_WINDOW( m_pWindow ) );
1959 if( eType == SetType::UnFullscreen &&
1960 !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
1961 gtk_window_set_resizable( GTK_WINDOW( m_pWindow ), FALSE );
1963 // FIXME: we should really let gtk+ handle our widget hierarchy ...
1964 if( m_pParent && gtk_widget_get_screen( m_pParent->m_pWindow ) != pScreen )
1965 SetParent( nullptr );
1966 std::list< GtkSalFrame* > aChildren = m_aChildren;
1967 for (auto const& child : aChildren)
1968 child->SetScreen( nNewScreen, SetType::RetainSize );
1970 m_bDefaultPos = m_bDefaultSize = false;
1971 updateScreenNumber();
1973 if( bVisible )
1974 Show( true );
1977 void GtkSalFrame::SetScreenNumber( unsigned int nNewScreen )
1979 SetScreen( nNewScreen, SetType::RetainSize );
1982 void GtkSalFrame::updateWMClass()
1984 OString aResClass = OUStringToOString(m_sWMClass, RTL_TEXTENCODING_ASCII_US);
1985 const char *pResClass = !aResClass.isEmpty() ? aResClass.getStr() :
1986 SalGenericSystem::getFrameClassName();
1987 Display *display;
1989 if (!getDisplay()->IsX11Display())
1990 return;
1992 display = GDK_DISPLAY_XDISPLAY(getGdkDisplay());
1994 if( IS_WIDGET_REALIZED( m_pWindow ) )
1996 XClassHint* pClass = XAllocClassHint();
1997 OString aResName = SalGenericSystem::getFrameResName();
1998 pClass->res_name = const_cast<char*>(aResName.getStr());
1999 pClass->res_class = const_cast<char*>(pResClass);
2000 XSetClassHint( display,
2001 widget_get_xid(m_pWindow),
2002 pClass );
2003 XFree( pClass );
2007 void GtkSalFrame::SetApplicationID( const OUString &rWMClass )
2009 if( rWMClass != m_sWMClass && ! isChild() )
2011 m_sWMClass = rWMClass;
2012 updateWMClass();
2014 for (auto const& child : m_aChildren)
2015 child->SetApplicationID(rWMClass);
2019 void GtkSalFrame::ShowFullScreen( bool bFullScreen, sal_Int32 nScreen )
2021 m_bFullscreen = bFullScreen;
2023 if( !m_pWindow || isChild() )
2024 return;
2026 if( bFullScreen )
2028 m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(m_pWindow));
2029 SetScreen( nScreen, SetType::Fullscreen );
2031 else
2033 SetScreen( nScreen, SetType::UnFullscreen,
2034 !m_aRestorePosSize.IsEmpty() ? &m_aRestorePosSize : nullptr );
2035 m_aRestorePosSize = tools::Rectangle();
2039 void GtkSalFrame::StartPresentation( bool bStart )
2041 boost::optional<guint> aWindow;
2042 boost::optional<Display*> aDisplay;
2043 if( getDisplay()->IsX11Display() )
2045 aWindow = widget_get_xid(m_pWindow);
2046 aDisplay = GDK_DISPLAY_XDISPLAY( getGdkDisplay() );
2049 m_ScreenSaverInhibitor.inhibit( bStart,
2050 "presentation",
2051 getDisplay()->IsX11Display(),
2052 aWindow,
2053 aDisplay );
2056 void GtkSalFrame::SetAlwaysOnTop( bool bOnTop )
2058 if( m_pWindow )
2059 gtk_window_set_keep_above( GTK_WINDOW( m_pWindow ), bOnTop );
2062 static guint32 nLastUserInputTime = GDK_CURRENT_TIME;
2064 guint32 GtkSalFrame::GetLastInputEventTime()
2066 return nLastUserInputTime;
2069 void GtkSalFrame::UpdateLastInputEventTime(guint32 nUserInputTime)
2071 //gtk3 can generate a synthetic crossing event with a useless 0
2072 //(GDK_CURRENT_TIME) timestamp on showing a menu from the main
2073 //menubar, which is unhelpful, so ignore the 0 timestamps
2074 if (nUserInputTime == GDK_CURRENT_TIME)
2075 return;
2076 nLastUserInputTime = nUserInputTime;
2079 void GtkSalFrame::ToTop( SalFrameToTop nFlags )
2081 if( m_pWindow )
2083 if( isChild( false ) )
2084 gtk_widget_grab_focus( m_pWindow );
2085 else if( IS_WIDGET_MAPPED( m_pWindow ) )
2087 if (!(nFlags & SalFrameToTop::GrabFocusOnly))
2088 gtk_window_present_with_time(GTK_WINDOW(m_pWindow), GetLastInputEventTime());
2089 else
2090 gdk_window_focus(widget_get_window(m_pWindow), GetLastInputEventTime());
2092 else
2094 if( nFlags & SalFrameToTop::RestoreWhenMin )
2095 gtk_window_present( GTK_WINDOW(m_pWindow) );
2100 void GtkSalFrame::SetPointer( PointerStyle ePointerStyle )
2102 if( m_pWindow && ePointerStyle != m_ePointerStyle )
2104 m_ePointerStyle = ePointerStyle;
2105 GdkCursor *pCursor = getDisplay()->getCursor( ePointerStyle );
2106 gdk_window_set_cursor( widget_get_window(m_pWindow), pCursor );
2107 m_pCurrentCursor = pCursor;
2109 // #i80791# use grabPointer the same way as CaptureMouse, respective float grab
2110 if( getDisplay()->MouseCaptured( this ) )
2111 grabPointer( true );
2112 else if( m_nFloats > 0 )
2113 grabPointer( true, true );
2117 void GtkSalFrame::grabPointer( bool bGrab, bool bOwnerEvents )
2119 static const char* pEnv = getenv( "SAL_NO_MOUSEGRABS" );
2120 if (pEnv && *pEnv)
2121 return;
2123 if (!m_pWindow)
2124 return;
2126 #if GTK_CHECK_VERSION(3, 20, 0)
2127 if (gtk_check_version(3, 20, 0) == nullptr)
2129 GdkSeat* pSeat = gdk_display_get_default_seat(getGdkDisplay());
2130 if (bGrab)
2132 gdk_seat_grab(pSeat, widget_get_window(getMouseEventWidget()), GDK_SEAT_CAPABILITY_ALL_POINTING,
2133 bOwnerEvents, nullptr, nullptr, nullptr, nullptr);
2135 else
2137 gdk_seat_ungrab(pSeat);
2139 return;
2141 #endif
2143 //else older gtk3
2144 const int nMask = (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
2146 GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(getGdkDisplay());
2147 GdkDevice* pPointer = gdk_device_manager_get_client_pointer(pDeviceManager);
2148 if (bGrab)
2150 gdk_device_grab(pPointer, widget_get_window(getMouseEventWidget()), GDK_OWNERSHIP_NONE,
2151 bOwnerEvents, GdkEventMask(nMask), m_pCurrentCursor, gtk_get_current_event_time());
2153 else
2155 gdk_device_ungrab(pPointer, gtk_get_current_event_time());
2159 void GtkSalFrame::CaptureMouse( bool bCapture )
2161 getDisplay()->CaptureMouse( bCapture ? this : nullptr );
2164 void GtkSalFrame::SetPointerPos( long nX, long nY )
2166 GtkSalFrame* pFrame = this;
2167 while( pFrame && pFrame->isChild( false ) )
2168 pFrame = pFrame->m_pParent;
2169 if( ! pFrame )
2170 return;
2172 GdkScreen *pScreen = gtk_window_get_screen( GTK_WINDOW(pFrame->m_pWindow) );
2173 GdkDisplay *pDisplay = gdk_screen_get_display( pScreen );
2175 /* when the application tries to center the mouse in the dialog the
2176 * window isn't mapped already. So use coordinates relative to the root window.
2178 unsigned int nWindowLeft = maGeometry.nX + nX;
2179 unsigned int nWindowTop = maGeometry.nY + nY;
2181 GdkDeviceManager* pManager = gdk_display_get_device_manager(pDisplay);
2182 gdk_device_warp(gdk_device_manager_get_client_pointer(pManager), pScreen, nWindowLeft, nWindowTop);
2184 // #i38648# ask for the next motion hint
2185 gint x, y;
2186 GdkModifierType mask;
2187 gdk_window_get_pointer( widget_get_window(pFrame->m_pWindow) , &x, &y, &mask );
2190 void GtkSalFrame::Flush()
2192 gdk_display_flush( getGdkDisplay() );
2195 void GtkSalFrame::KeyCodeToGdkKey(const vcl::KeyCode& rKeyCode,
2196 guint* pGdkKeyCode, GdkModifierType *pGdkModifiers)
2198 if ( pGdkKeyCode == nullptr || pGdkModifiers == nullptr )
2199 return;
2201 // Get GDK key modifiers
2202 GdkModifierType nModifiers = GdkModifierType(0);
2204 if ( rKeyCode.IsShift() )
2205 nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_SHIFT_MASK );
2207 if ( rKeyCode.IsMod1() )
2208 nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_CONTROL_MASK );
2210 if ( rKeyCode.IsMod2() )
2211 nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_MOD1_MASK );
2213 *pGdkModifiers = nModifiers;
2215 // Get GDK keycode.
2216 guint nKeyCode = 0;
2218 guint nCode = rKeyCode.GetCode();
2220 if ( nCode >= KEY_0 && nCode <= KEY_9 )
2221 nKeyCode = ( nCode - KEY_0 ) + GDK_KEY_0;
2222 else if ( nCode >= KEY_A && nCode <= KEY_Z )
2223 nKeyCode = ( nCode - KEY_A ) + GDK_KEY_A;
2224 else if ( nCode >= KEY_F1 && nCode <= KEY_F26 )
2225 nKeyCode = ( nCode - KEY_F1 ) + GDK_KEY_F1;
2226 else
2228 switch (nCode)
2230 case KEY_DOWN: nKeyCode = GDK_KEY_Down; break;
2231 case KEY_UP: nKeyCode = GDK_KEY_Up; break;
2232 case KEY_LEFT: nKeyCode = GDK_KEY_Left; break;
2233 case KEY_RIGHT: nKeyCode = GDK_KEY_Right; break;
2234 case KEY_HOME: nKeyCode = GDK_KEY_Home; break;
2235 case KEY_END: nKeyCode = GDK_KEY_End; break;
2236 case KEY_PAGEUP: nKeyCode = GDK_KEY_Page_Up; break;
2237 case KEY_PAGEDOWN: nKeyCode = GDK_KEY_Page_Down; break;
2238 case KEY_RETURN: nKeyCode = GDK_KEY_Return; break;
2239 case KEY_ESCAPE: nKeyCode = GDK_KEY_Escape; break;
2240 case KEY_TAB: nKeyCode = GDK_KEY_Tab; break;
2241 case KEY_BACKSPACE: nKeyCode = GDK_KEY_BackSpace; break;
2242 case KEY_SPACE: nKeyCode = GDK_KEY_space; break;
2243 case KEY_INSERT: nKeyCode = GDK_KEY_Insert; break;
2244 case KEY_DELETE: nKeyCode = GDK_KEY_Delete; break;
2245 case KEY_ADD: nKeyCode = GDK_KEY_plus; break;
2246 case KEY_SUBTRACT: nKeyCode = GDK_KEY_minus; break;
2247 case KEY_MULTIPLY: nKeyCode = GDK_KEY_asterisk; break;
2248 case KEY_DIVIDE: nKeyCode = GDK_KEY_slash; break;
2249 case KEY_POINT: nKeyCode = GDK_KEY_period; break;
2250 case KEY_COMMA: nKeyCode = GDK_KEY_comma; break;
2251 case KEY_LESS: nKeyCode = GDK_KEY_less; break;
2252 case KEY_GREATER: nKeyCode = GDK_KEY_greater; break;
2253 case KEY_EQUAL: nKeyCode = GDK_KEY_equal; break;
2254 case KEY_FIND: nKeyCode = GDK_KEY_Find; break;
2255 case KEY_CONTEXTMENU: nKeyCode = GDK_KEY_Menu; break;
2256 case KEY_HELP: nKeyCode = GDK_KEY_Help; break;
2257 case KEY_UNDO: nKeyCode = GDK_KEY_Undo; break;
2258 case KEY_REPEAT: nKeyCode = GDK_KEY_Redo; break;
2259 case KEY_DECIMAL: nKeyCode = GDK_KEY_KP_Decimal; break;
2260 case KEY_TILDE: nKeyCode = GDK_KEY_asciitilde; break;
2261 case KEY_QUOTELEFT: nKeyCode = GDK_KEY_quoteleft; break;
2262 case KEY_BRACKETLEFT: nKeyCode = GDK_KEY_bracketleft; break;
2263 case KEY_BRACKETRIGHT: nKeyCode = GDK_KEY_bracketright; break;
2264 case KEY_SEMICOLON: nKeyCode = GDK_KEY_semicolon; break;
2265 case KEY_QUOTERIGHT: nKeyCode = GDK_KEY_quoteright; break;
2267 // Special cases
2268 case KEY_COPY: nKeyCode = GDK_KEY_Copy; break;
2269 case KEY_CUT: nKeyCode = GDK_KEY_Cut; break;
2270 case KEY_PASTE: nKeyCode = GDK_KEY_Paste; break;
2271 case KEY_OPEN: nKeyCode = GDK_KEY_Open; break;
2275 *pGdkKeyCode = nKeyCode;
2278 OUString GtkSalFrame::GetKeyName( sal_uInt16 nKeyCode )
2280 guint nGtkKeyCode;
2281 GdkModifierType nGtkModifiers;
2282 KeyCodeToGdkKey(nKeyCode, &nGtkKeyCode, &nGtkModifiers );
2284 gchar* pName = gtk_accelerator_get_label(nGtkKeyCode, nGtkModifiers);
2285 OUString aRet(pName, rtl_str_getLength(pName), RTL_TEXTENCODING_UTF8);
2286 g_free(pName);
2287 return aRet;
2290 GdkDisplay *GtkSalFrame::getGdkDisplay()
2292 return GetGtkSalData()->GetGdkDisplay();
2295 GtkSalDisplay *GtkSalFrame::getDisplay()
2297 return GetGtkSalData()->GetGtkDisplay();
2300 SalFrame::SalPointerState GtkSalFrame::GetPointerState()
2302 SalPointerState aState;
2303 GdkScreen* pScreen;
2304 gint x, y;
2305 GdkModifierType aMask;
2306 gdk_display_get_pointer( getGdkDisplay(), &pScreen, &x, &y, &aMask );
2307 aState.maPos = Point( x - maGeometry.nX, y - maGeometry.nY );
2308 aState.mnState = GetMouseModCode( aMask );
2309 return aState;
2312 KeyIndicatorState GtkSalFrame::GetIndicatorState()
2314 KeyIndicatorState nState = KeyIndicatorState::NONE;
2316 GdkKeymap *pKeyMap = gdk_keymap_get_for_display(getGdkDisplay());
2318 if (gdk_keymap_get_caps_lock_state(pKeyMap))
2319 nState |= KeyIndicatorState::CAPSLOCK;
2320 if (gdk_keymap_get_num_lock_state(pKeyMap))
2321 nState |= KeyIndicatorState::NUMLOCK;
2322 if (gdk_keymap_get_scroll_lock_state(pKeyMap))
2323 nState |= KeyIndicatorState::SCROLLLOCK;
2325 return nState;
2328 void GtkSalFrame::SimulateKeyPress( sal_uInt16 nKeyCode )
2330 g_warning ("missing simulate keypress %d", nKeyCode);
2333 void GtkSalFrame::SetInputContext( SalInputContext* pContext )
2335 if( ! pContext )
2336 return;
2338 if( ! (pContext->mnOptions & InputContextFlags::Text) )
2339 return;
2341 // create a new im context
2342 if( ! m_pIMHandler )
2343 m_pIMHandler.reset( new IMHandler( this ) );
2346 void GtkSalFrame::EndExtTextInput( EndExtTextInputFlags nFlags )
2348 if( m_pIMHandler )
2349 m_pIMHandler->endExtTextInput( nFlags );
2352 bool GtkSalFrame::MapUnicodeToKeyCode( sal_Unicode , LanguageType , vcl::KeyCode& )
2354 // not supported yet
2355 return false;
2358 LanguageType GtkSalFrame::GetInputLanguage()
2360 return LANGUAGE_DONTKNOW;
2363 void GtkSalFrame::UpdateSettings( AllSettings& rSettings )
2365 if( ! m_pWindow )
2366 return;
2368 GtkSalGraphics* pGraphics = m_pGraphics.get();
2369 bool bFreeGraphics = false;
2370 if( ! pGraphics )
2372 pGraphics = static_cast<GtkSalGraphics*>(AcquireGraphics());
2373 if ( !pGraphics )
2375 SAL_WARN("vcl.gtk3", "Could not get graphics - unable to update settings");
2376 return;
2378 bFreeGraphics = true;
2381 pGraphics->updateSettings( rSettings );
2383 if( bFreeGraphics )
2384 ReleaseGraphics( pGraphics );
2387 void GtkSalFrame::Beep()
2389 gdk_display_beep( getGdkDisplay() );
2392 const SystemEnvData* GtkSalFrame::GetSystemData() const
2394 return &m_aSystemData;
2397 void GtkSalFrame::SetParent( SalFrame* pNewParent )
2399 if (m_pParent)
2401 gtk_window_group_remove_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), GTK_WINDOW(m_pWindow));
2402 m_pParent->m_aChildren.remove(this);
2404 m_pParent = static_cast<GtkSalFrame*>(pNewParent);
2405 if (m_pParent)
2407 m_pParent->m_aChildren.push_back(this);
2408 gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), GTK_WINDOW(m_pWindow));
2410 if( ! isChild() )
2411 gtk_window_set_transient_for( GTK_WINDOW(m_pWindow),
2412 (m_pParent && ! m_pParent->isChild(true,false)) ? GTK_WINDOW(m_pParent->m_pWindow) : nullptr
2416 bool GtkSalFrame::SetPluginParent( SystemParentData* )
2418 //FIXME: no SetPluginParent impl. for gtk3
2419 return false;
2422 void GtkSalFrame::ResetClipRegion()
2424 if( m_pWindow )
2425 gdk_window_shape_combine_region( widget_get_window( m_pWindow ), nullptr, 0, 0 );
2428 void GtkSalFrame::BeginSetClipRegion( sal_uLong )
2430 if( m_pRegion )
2431 cairo_region_destroy( m_pRegion );
2432 m_pRegion = cairo_region_create();
2435 void GtkSalFrame::UnionClipRegion( long nX, long nY, long nWidth, long nHeight )
2437 if( m_pRegion )
2439 GdkRectangle aRect;
2440 aRect.x = nX;
2441 aRect.y = nY;
2442 aRect.width = nWidth;
2443 aRect.height = nHeight;
2444 cairo_region_union_rectangle( m_pRegion, &aRect );
2448 void GtkSalFrame::EndSetClipRegion()
2450 if( m_pWindow && m_pRegion )
2451 gdk_window_shape_combine_region( widget_get_window(m_pWindow), m_pRegion, 0, 0 );
2454 void GtkSalFrame::PositionByToolkit(const tools::Rectangle& rRect, FloatWinPopupFlags nFlags)
2456 if (ImplGetSVData()->maNWFData.mbCanDetermineWindowPosition)
2457 return;
2459 m_aFloatRect = rRect;
2460 m_nFloatFlags = nFlags;
2461 m_bFloatPositioned = true;
2464 void GtkSalFrame::SetModal(bool bModal)
2466 if (!m_pWindow)
2467 return;
2468 gtk_window_set_modal(GTK_WINDOW(m_pWindow), bModal);
2471 gboolean GtkSalFrame::signalTooltipQuery(GtkWidget*, gint /*x*/, gint /*y*/,
2472 gboolean /*keyboard_mode*/, GtkTooltip *tooltip,
2473 gpointer frame)
2475 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
2476 if (pThis->m_aTooltip.isEmpty())
2477 return false;
2478 gtk_tooltip_set_text(tooltip,
2479 OUStringToOString(pThis->m_aTooltip, RTL_TEXTENCODING_UTF8).getStr());
2480 GdkRectangle aHelpArea;
2481 aHelpArea.x = pThis->m_aHelpArea.Left();
2482 aHelpArea.y = pThis->m_aHelpArea.Top();
2483 aHelpArea.width = pThis->m_aHelpArea.GetWidth();
2484 aHelpArea.height = pThis->m_aHelpArea.GetHeight();
2485 if (AllSettings::GetLayoutRTL())
2486 aHelpArea.x = pThis->maGeometry.nWidth-aHelpArea.width-1-aHelpArea.x;
2487 gtk_tooltip_set_tip_area(tooltip, &aHelpArea);
2488 return true;
2491 bool GtkSalFrame::ShowTooltip(const OUString& rHelpText, const tools::Rectangle& rHelpArea)
2493 m_aTooltip = rHelpText;
2494 m_aHelpArea = rHelpArea;
2495 gtk_widget_trigger_tooltip_query(getMouseEventWidget());
2496 return true;
2499 void GtkSalFrame::HideTooltip()
2501 m_aTooltip.clear();
2502 gtk_widget_trigger_tooltip_query(getMouseEventWidget());
2505 namespace
2507 void set_pointing_to(GtkPopover *pPopOver, vcl::Window* pParent, const tools::Rectangle& rHelpArea, const SalFrameGeometry& rGeometry)
2509 GdkRectangle aRect;
2510 aRect.x = FloatingWindow::ImplConvertToAbsPos(pParent, rHelpArea).Left() - rGeometry.nX;
2511 aRect.y = rHelpArea.Top();
2512 aRect.width = 1;
2513 aRect.height = 1;
2515 GtkPositionType ePos = gtk_popover_get_position(pPopOver);
2516 switch (ePos)
2518 case GTK_POS_BOTTOM:
2519 case GTK_POS_TOP:
2520 aRect.width = rHelpArea.GetWidth();
2521 break;
2522 case GTK_POS_RIGHT:
2523 case GTK_POS_LEFT:
2524 aRect.height = rHelpArea.GetHeight();
2525 break;
2528 gtk_popover_set_pointing_to(pPopOver, &aRect);
2532 void* GtkSalFrame::ShowPopover(const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea, QuickHelpFlags nFlags)
2534 GtkWidget *pWidget = gtk_popover_new(getMouseEventWidget());
2535 OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
2536 GtkWidget *pLabel = gtk_label_new(sUTF.getStr());
2537 gtk_container_add(GTK_CONTAINER(pWidget), pLabel);
2539 if (nFlags & QuickHelpFlags::Top)
2540 gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_BOTTOM);
2541 else if (nFlags & QuickHelpFlags::Bottom)
2542 gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_TOP);
2543 else if (nFlags & QuickHelpFlags::Left)
2544 gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_RIGHT);
2545 else if (nFlags & QuickHelpFlags::Right)
2546 gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_LEFT);
2548 set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry);
2550 gtk_popover_set_modal(GTK_POPOVER(pWidget), false);
2552 gtk_widget_show_all(pWidget);
2554 return pWidget;
2557 bool GtkSalFrame::UpdatePopover(void* nId, const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea)
2559 GtkWidget *pWidget = static_cast<GtkWidget*>(nId);
2561 set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry);
2563 GtkWidget *pLabel = gtk_bin_get_child(GTK_BIN(pWidget));
2564 OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
2565 gtk_label_set_text(GTK_LABEL(pLabel), sUTF.getStr());
2567 return true;
2570 bool GtkSalFrame::HidePopover(void* nId)
2572 GtkWidget *pWidget = static_cast<GtkWidget*>(nId);
2573 gtk_widget_destroy(pWidget);
2574 return true;
2577 void GtkSalFrame::addGrabLevel()
2579 if (m_nGrabLevel == 0)
2580 gtk_grab_add(getMouseEventWidget());
2581 ++m_nGrabLevel;
2584 void GtkSalFrame::removeGrabLevel()
2586 if (m_nGrabLevel > 0)
2588 --m_nGrabLevel;
2589 if (m_nGrabLevel == 0)
2590 gtk_grab_remove(getMouseEventWidget());
2594 void GtkSalFrame::closePopup()
2596 if (!m_nFloats)
2597 return;
2598 ImplSVData* pSVData = ImplGetSVData();
2599 if (!pSVData->maWinData.mpFirstFloat)
2600 return;
2601 if (pSVData->maWinData.mpFirstFloat->ImplGetFrame() != this)
2602 return;
2603 pSVData->maWinData.mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll);
2606 namespace
2608 //tdf#117981 translate embedded video window mouse events to parent coordinates
2609 void translate_coords(GdkWindow* pSourceWindow, GtkWidget* pTargetWidget, int& rEventX, int& rEventY)
2611 gpointer user_data=nullptr;
2612 gdk_window_get_user_data(pSourceWindow, &user_data);
2613 GtkWidget* pRealEventWidget = static_cast<GtkWidget*>(user_data);
2614 if (pRealEventWidget)
2616 gtk_widget_translate_coordinates(pRealEventWidget, pTargetWidget, rEventX, rEventY, &rEventX, &rEventY);
2621 gboolean GtkSalFrame::signalButton( GtkWidget*, GdkEventButton* pEvent, gpointer frame )
2623 UpdateLastInputEventTime(pEvent->time);
2625 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
2626 GtkWidget* pEventWidget = pThis->getMouseEventWidget();
2627 bool bDifferentEventWindow = pEvent->window != widget_get_window(pEventWidget);
2629 // tdf#120764 It isn't allowed under wayland to have two visible popups that share
2630 // the same top level parent. The problem is that since gtk 3.24 tooltips are also
2631 // implemented as popups, which means that we cannot show any popup if there is a
2632 // visible tooltip. In fact, gtk will hide the tooltip by itself after this handler,
2633 // in case of a button press event. But if we intend to show a popup on button press
2634 // it will be too late, so just do it here:
2635 if (pEvent->type == GDK_BUTTON_PRESS)
2636 pThis->HideTooltip();
2638 SalMouseEvent aEvent;
2639 SalEvent nEventType = SalEvent::NONE;
2640 switch( pEvent->type )
2642 case GDK_BUTTON_PRESS:
2643 nEventType = SalEvent::MouseButtonDown;
2644 break;
2645 case GDK_BUTTON_RELEASE:
2646 nEventType = SalEvent::MouseButtonUp;
2647 break;
2648 default:
2649 return false;
2651 switch( pEvent->button )
2653 case 1: aEvent.mnButton = MOUSE_LEFT; break;
2654 case 2: aEvent.mnButton = MOUSE_MIDDLE; break;
2655 case 3: aEvent.mnButton = MOUSE_RIGHT; break;
2656 default: return false;
2659 vcl::DeletionListener aDel( pThis );
2661 if (pThis->isFloatGrabWindow())
2663 //rhbz#1505379 if the window that got the event isn't our one, or there's none
2664 //of our windows under the mouse then close this popup window
2665 if (bDifferentEventWindow ||
2666 gdk_device_get_window_at_position(pEvent->device, nullptr, nullptr) == nullptr)
2668 if (pEvent->type == GDK_BUTTON_PRESS)
2669 pThis->closePopup();
2670 else if (pEvent->type == GDK_BUTTON_RELEASE)
2671 return true;
2675 int nEventX = pEvent->x;
2676 int nEventY = pEvent->y;
2678 if (bDifferentEventWindow)
2679 translate_coords(pEvent->window, pEventWidget, nEventX, nEventY);
2681 if (!aDel.isDeleted())
2683 int frame_x = static_cast<int>(pEvent->x_root - nEventX);
2684 int frame_y = static_cast<int>(pEvent->y_root - nEventY);
2685 if (pThis->m_bGeometryIsProvisional || frame_x != pThis->maGeometry.nX || frame_y != pThis->maGeometry.nY)
2687 pThis->m_bGeometryIsProvisional = false;
2688 pThis->maGeometry.nX = frame_x;
2689 pThis->maGeometry.nY = frame_y;
2690 ImplSVData* pSVData = ImplGetSVData();
2691 if (pSVData->maNWFData.mbCanDetermineWindowPosition)
2692 pThis->CallCallbackExc(SalEvent::Move, nullptr);
2696 if (!aDel.isDeleted())
2698 aEvent.mnTime = pEvent->time;
2699 aEvent.mnX = static_cast<long>(pEvent->x_root) - pThis->maGeometry.nX;
2700 aEvent.mnY = static_cast<long>(pEvent->y_root) - pThis->maGeometry.nY;
2701 aEvent.mnCode = GetMouseModCode( pEvent->state );
2703 if( AllSettings::GetLayoutRTL() )
2704 aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;
2706 pThis->CallCallbackExc( nEventType, &aEvent );
2709 return true;
2712 void GtkSalFrame::LaunchAsyncScroll(GdkEvent const * pEvent)
2714 //if we don't match previous pending states, flush that queue now
2715 if (!m_aPendingScrollEvents.empty() && pEvent->scroll.state != m_aPendingScrollEvents.back()->scroll.state)
2717 m_aSmoothScrollIdle.Stop();
2718 m_aSmoothScrollIdle.Invoke();
2719 assert(m_aPendingScrollEvents.empty());
2721 //add scroll event to queue
2722 m_aPendingScrollEvents.push_back(gdk_event_copy(pEvent));
2723 if (!m_aSmoothScrollIdle.IsActive())
2724 m_aSmoothScrollIdle.Start();
2727 IMPL_LINK_NOARG(GtkSalFrame, AsyncScroll, Timer *, void)
2729 assert(!m_aPendingScrollEvents.empty());
2731 SalWheelMouseEvent aEvent;
2733 GdkEvent* pEvent = m_aPendingScrollEvents.back();
2735 aEvent.mnTime = pEvent->scroll.time;
2736 aEvent.mnX = static_cast<sal_uLong>(pEvent->scroll.x);
2737 // --- RTL --- (mirror mouse pos)
2738 if (AllSettings::GetLayoutRTL())
2739 aEvent.mnX = maGeometry.nWidth - 1 - aEvent.mnX;
2740 aEvent.mnY = static_cast<sal_uLong>(pEvent->scroll.y);
2741 aEvent.mnCode = GetMouseModCode( pEvent->scroll.state );
2743 double delta_x(0.0), delta_y(0.0);
2744 for (auto pSubEvent : m_aPendingScrollEvents)
2746 delta_x += pSubEvent->scroll.delta_x;
2747 delta_y += pSubEvent->scroll.delta_y;
2748 gdk_event_free(pSubEvent);
2750 m_aPendingScrollEvents.clear();
2752 // rhbz#1344042 "Traditionally" in gtk3 we tool a single up/down event as
2753 // equating to 3 scroll lines and a delta of 120. So scale the delta here
2754 // by 120 where a single mouse wheel click is an incoming delta_x of 1
2755 // and divide that by 40 to get the number of scroll lines
2756 if (delta_x != 0.0)
2758 aEvent.mnDelta = -delta_x * 120;
2759 aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
2760 if (aEvent.mnDelta == 0)
2761 aEvent.mnDelta = aEvent.mnNotchDelta;
2762 aEvent.mbHorz = true;
2763 aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0;
2764 CallCallbackExc(SalEvent::WheelMouse, &aEvent);
2767 if (delta_y != 0.0)
2769 aEvent.mnDelta = -delta_y * 120;
2770 aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
2771 if (aEvent.mnDelta == 0)
2772 aEvent.mnDelta = aEvent.mnNotchDelta;
2773 aEvent.mbHorz = false;
2774 aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0;
2775 CallCallbackExc(SalEvent::WheelMouse, &aEvent);
2779 gboolean GtkSalFrame::signalScroll(GtkWidget*, GdkEvent* pInEvent, gpointer frame)
2781 GdkEventScroll& rEvent = pInEvent->scroll;
2783 UpdateLastInputEventTime(rEvent.time);
2785 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
2787 if (rEvent.direction == GDK_SCROLL_SMOOTH)
2789 pThis->LaunchAsyncScroll(pInEvent);
2790 return true;
2793 //if we have smooth scrolling previous pending states, flush that queue now
2794 if (!pThis->m_aPendingScrollEvents.empty())
2796 pThis->m_aSmoothScrollIdle.Stop();
2797 pThis->m_aSmoothScrollIdle.Invoke();
2798 assert(pThis->m_aPendingScrollEvents.empty());
2801 SalWheelMouseEvent aEvent;
2803 aEvent.mnTime = rEvent.time;
2804 aEvent.mnX = static_cast<sal_uLong>(rEvent.x);
2805 // --- RTL --- (mirror mouse pos)
2806 if (AllSettings::GetLayoutRTL())
2807 aEvent.mnX = pThis->maGeometry.nWidth - 1 - aEvent.mnX;
2808 aEvent.mnY = static_cast<sal_uLong>(rEvent.y);
2809 aEvent.mnCode = GetMouseModCode(rEvent.state);
2811 switch (rEvent.direction)
2813 case GDK_SCROLL_UP:
2814 aEvent.mnDelta = 120;
2815 aEvent.mnNotchDelta = 1;
2816 aEvent.mnScrollLines = 3;
2817 aEvent.mbHorz = false;
2818 pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
2819 break;
2821 case GDK_SCROLL_DOWN:
2822 aEvent.mnDelta = -120;
2823 aEvent.mnNotchDelta = -1;
2824 aEvent.mnScrollLines = 3;
2825 aEvent.mbHorz = false;
2826 pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
2827 break;
2829 case GDK_SCROLL_LEFT:
2830 aEvent.mnDelta = 120;
2831 aEvent.mnNotchDelta = 1;
2832 aEvent.mnScrollLines = 3;
2833 aEvent.mbHorz = true;
2834 pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
2835 break;
2837 case GDK_SCROLL_RIGHT:
2838 aEvent.mnDelta = -120;
2839 aEvent.mnNotchDelta = -1;
2840 aEvent.mnScrollLines = 3;
2841 aEvent.mbHorz = true;
2842 pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
2843 break;
2844 default:
2845 break;
2848 return true;
2851 void GtkSalFrame::gestureSwipe(GtkGestureSwipe* gesture, gdouble velocity_x, gdouble velocity_y, gpointer frame)
2853 gdouble x, y;
2854 GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
2855 //I feel I want the first point of the sequence, not the last point which
2856 //the docs say this gives, but for the moment assume we start and end
2857 //within the same vcl window
2858 if (gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y))
2860 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
2862 SalSwipeEvent aEvent;
2863 aEvent.mnVelocityX = velocity_x;
2864 aEvent.mnVelocityY = velocity_y;
2865 aEvent.mnX = x;
2866 aEvent.mnY = y;
2868 pThis->CallCallbackExc(SalEvent::Swipe, &aEvent);
2872 void GtkSalFrame::gestureLongPress(GtkGestureLongPress* gesture, gpointer frame)
2874 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
2876 if(pThis)
2878 SalLongPressEvent aEvent;
2880 gdouble x, y;
2881 GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
2882 gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y);
2883 aEvent.mnX = x;
2884 aEvent.mnY = y;
2886 pThis->CallCallbackExc(SalEvent::LongPress, &aEvent);
2890 gboolean GtkSalFrame::signalMotion( GtkWidget*, GdkEventMotion* pEvent, gpointer frame )
2892 UpdateLastInputEventTime(pEvent->time);
2894 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
2895 GtkWidget* pEventWidget = pThis->getMouseEventWidget();
2896 bool bDifferentEventWindow = pEvent->window != widget_get_window(pEventWidget);
2898 //If a menu, e.g. font name dropdown, is open, then under wayland moving the
2899 //mouse in the top left corner of the toplevel window in a
2900 //0,0,float-width,float-height area generates motion events which are
2901 //delivered to the dropdown
2902 if (pThis->isFloatGrabWindow() && bDifferentEventWindow)
2903 return true;
2905 vcl::DeletionListener aDel( pThis );
2907 int nEventX = pEvent->x;
2908 int nEventY = pEvent->y;
2910 if (bDifferentEventWindow)
2911 translate_coords(pEvent->window, pEventWidget, nEventX, nEventY);
2913 int frame_x = static_cast<int>(pEvent->x_root - nEventX);
2914 int frame_y = static_cast<int>(pEvent->y_root - nEventY);
2916 if (pThis->m_bGeometryIsProvisional || frame_x != pThis->maGeometry.nX || frame_y != pThis->maGeometry.nY)
2918 pThis->m_bGeometryIsProvisional = false;
2919 pThis->maGeometry.nX = frame_x;
2920 pThis->maGeometry.nY = frame_y;
2921 ImplSVData* pSVData = ImplGetSVData();
2922 if (pSVData->maNWFData.mbCanDetermineWindowPosition)
2923 pThis->CallCallbackExc(SalEvent::Move, nullptr);
2926 if (!aDel.isDeleted())
2928 SalMouseEvent aEvent;
2929 aEvent.mnTime = pEvent->time;
2930 aEvent.mnX = static_cast<long>(pEvent->x_root) - pThis->maGeometry.nX;
2931 aEvent.mnY = static_cast<long>(pEvent->y_root) - pThis->maGeometry.nY;
2932 aEvent.mnCode = GetMouseModCode( pEvent->state );
2933 aEvent.mnButton = 0;
2935 if( AllSettings::GetLayoutRTL() )
2936 aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;
2938 pThis->CallCallbackExc( SalEvent::MouseMove, &aEvent );
2941 if (!aDel.isDeleted())
2943 // ask for the next hint
2944 gint x, y;
2945 GdkModifierType mask;
2946 gdk_window_get_pointer( widget_get_window(GTK_WIDGET(pThis->m_pWindow)), &x, &y, &mask );
2949 return true;
2952 gboolean GtkSalFrame::signalCrossing( GtkWidget*, GdkEventCrossing* pEvent, gpointer frame )
2954 UpdateLastInputEventTime(pEvent->time);
2956 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
2957 SalMouseEvent aEvent;
2958 aEvent.mnTime = pEvent->time;
2959 aEvent.mnX = static_cast<long>(pEvent->x_root) - pThis->maGeometry.nX;
2960 aEvent.mnY = static_cast<long>(pEvent->y_root) - pThis->maGeometry.nY;
2961 aEvent.mnCode = GetMouseModCode( pEvent->state );
2962 aEvent.mnButton = 0;
2964 if (AllSettings::GetLayoutRTL())
2965 aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;
2967 pThis->CallCallbackExc( (pEvent->type == GDK_ENTER_NOTIFY) ? SalEvent::MouseMove : SalEvent::MouseLeave, &aEvent );
2969 return true;
2972 cairo_t* GtkSalFrame::getCairoContext() const
2974 cairo_t* cr = cairo_create(m_pSurface);
2975 assert(cr);
2976 return cr;
2979 void GtkSalFrame::damaged(sal_Int32 nExtentsX, sal_Int32 nExtentsY,
2980 sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight) const
2982 #if OSL_DEBUG_LEVEL > 0
2983 if (dumpframes)
2985 static int frame;
2986 OString tmp("/tmp/frame" + OString::number(frame++) + ".png");
2987 cairo_t* cr = getCairoContext();
2988 cairo_surface_write_to_png(cairo_get_target(cr), tmp.getStr());
2989 cairo_destroy(cr);
2991 #endif
2993 gtk_widget_queue_draw_area(GTK_WIDGET(m_pFixedContainer),
2994 nExtentsX, nExtentsY,
2995 nExtentsWidth, nExtentsHeight);
2998 // blit our backing cairo surface to the target cairo context
2999 gboolean GtkSalFrame::signalDraw(GtkWidget*, cairo_t *cr, gpointer frame)
3001 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3003 cairo_set_source_surface(cr, pThis->m_pSurface, 0, 0);
3004 cairo_paint(cr);
3006 return false;
3009 void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, GdkRectangle *pAllocation, gpointer frame)
3011 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3012 pThis->maGeometry.nWidth = pAllocation->width;
3013 pThis->maGeometry.nHeight = pAllocation->height;
3014 bool bRealized = gtk_widget_get_realized(pWidget);
3015 if (bRealized)
3016 pThis->AllocateFrame();
3017 pThis->CallCallbackExc( SalEvent::Resize, nullptr );
3018 if (bRealized && !pThis->m_bSalObjectSetPosSize)
3019 pThis->TriggerPaintEvent();
3022 #if GTK_CHECK_VERSION(3,23,0)
3023 namespace {
3025 void swapDirection(GdkGravity& gravity)
3027 if (gravity == GDK_GRAVITY_NORTH_WEST)
3028 gravity = GDK_GRAVITY_NORTH_EAST;
3029 else if (gravity == GDK_GRAVITY_NORTH_EAST)
3030 gravity = GDK_GRAVITY_NORTH_WEST;
3031 else if (gravity == GDK_GRAVITY_SOUTH_WEST)
3032 gravity = GDK_GRAVITY_SOUTH_EAST;
3033 else if (gravity == GDK_GRAVITY_SOUTH_EAST)
3034 gravity = GDK_GRAVITY_SOUTH_WEST;
3038 #endif
3040 void GtkSalFrame::signalRealize(GtkWidget*, gpointer frame)
3042 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3043 pThis->AllocateFrame();
3044 if (pThis->m_bSalObjectSetPosSize)
3045 return;
3046 pThis->TriggerPaintEvent();
3048 #if GTK_CHECK_VERSION(3,23,0)
3049 if (gtk_check_version(3, 23, 0) == nullptr && pThis->m_bFloatPositioned)
3051 GdkGravity rect_anchor = GDK_GRAVITY_SOUTH_WEST, menu_anchor = GDK_GRAVITY_NORTH_WEST;
3053 if (pThis->m_nFloatFlags & FloatWinPopupFlags::Left)
3055 rect_anchor = GDK_GRAVITY_NORTH_WEST;
3056 menu_anchor = GDK_GRAVITY_NORTH_EAST;
3058 else if (pThis->m_nFloatFlags & FloatWinPopupFlags::Up)
3060 rect_anchor = GDK_GRAVITY_NORTH_WEST;
3061 menu_anchor = GDK_GRAVITY_SOUTH_WEST;
3063 else if (pThis->m_nFloatFlags & FloatWinPopupFlags::Right)
3065 rect_anchor = GDK_GRAVITY_NORTH_EAST;
3068 VclPtr<vcl::Window> pVclParent = pThis->GetWindow()->GetParent();
3069 if (pVclParent->HasMirroredGraphics() && pVclParent->IsRTLEnabled())
3071 swapDirection(rect_anchor);
3072 swapDirection(menu_anchor);
3075 tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pVclParent, pThis->m_aFloatRect);
3076 if (gdk_window_get_window_type(widget_get_window(pThis->m_pParent->m_pWindow)) != GDK_WINDOW_TOPLEVEL)
3077 aFloatRect.Move(-pThis->m_pParent->maGeometry.nX, -pThis->m_pParent->maGeometry.nY);
3079 GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
3080 static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
3082 GdkWindow* gdkWindow = widget_get_window(pThis->m_pWindow);
3083 gdk_window_move_to_rect(gdkWindow, &rect, rect_anchor, menu_anchor, GDK_ANCHOR_FLIP, 0, 0);
3085 #endif
3088 gboolean GtkSalFrame::signalConfigure(GtkWidget*, GdkEventConfigure* pEvent, gpointer frame)
3090 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3092 bool bMoved = false;
3093 int x = pEvent->x, y = pEvent->y;
3095 /* #i31785# claims we cannot trust the x,y members of the event;
3096 * they are e.g. not set correctly on maximize/demaximize;
3097 * yet the gdkdisplay-x11.c code handling configure_events has
3098 * done this XTranslateCoordinates work since the day ~zero.
3100 if (pThis->m_bGeometryIsProvisional || x != pThis->maGeometry.nX || y != pThis->maGeometry.nY )
3102 bMoved = true;
3103 pThis->m_bGeometryIsProvisional = false;
3104 pThis->maGeometry.nX = x;
3105 pThis->maGeometry.nY = y;
3108 // update decoration hints
3109 GdkRectangle aRect;
3110 gdk_window_get_frame_extents( widget_get_window(GTK_WIDGET(pThis->m_pWindow)), &aRect );
3111 pThis->maGeometry.nTopDecoration = y - aRect.y;
3112 pThis->maGeometry.nBottomDecoration = aRect.y + aRect.height - y - pEvent->height;
3113 pThis->maGeometry.nLeftDecoration = x - aRect.x;
3114 pThis->maGeometry.nRightDecoration = aRect.x + aRect.width - x - pEvent->width;
3115 pThis->updateScreenNumber();
3117 if (bMoved)
3119 ImplSVData* pSVData = ImplGetSVData();
3120 if (pSVData->maNWFData.mbCanDetermineWindowPosition)
3121 pThis->CallCallbackExc(SalEvent::Move, nullptr);
3124 return false;
3127 void GtkSalFrame::TriggerPaintEvent()
3129 //Under gtk2 we can basically paint directly into the XWindow and on
3130 //additional "expose-event" events we can re-render the missing pieces
3132 //Under gtk3 we have to keep our own buffer up to date and flush it into
3133 //the given cairo context on "draw". So we emit a paint event on
3134 //opportune resize trigger events to initially fill our backbuffer and then
3135 //keep it up to date with our direct paints and tell gtk those regions
3136 //have changed and then blit them into the provided cairo context when
3137 //we get the "draw"
3139 //The other alternative was to always paint everything on "draw", but
3140 //that duplicates the amount of drawing and is hideously slow
3141 SAL_INFO("vcl.gtk3", "force painting" << 0 << "," << 0 << " " << maGeometry.nWidth << "x" << maGeometry.nHeight);
3142 SalPaintEvent aPaintEvt(0, 0, maGeometry.nWidth, maGeometry.nHeight, true);
3143 CallCallbackExc(SalEvent::Paint, &aPaintEvt);
3144 gtk_widget_queue_draw(GTK_WIDGET(m_pFixedContainer));
3147 gboolean GtkSalFrame::signalFocus( GtkWidget*, GdkEventFocus* pEvent, gpointer frame )
3149 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3151 SalGenericInstance *pSalInstance =
3152 static_cast< SalGenericInstance* >(GetSalData()->m_pInstance);
3154 // check if printers have changed (analogous to salframe focus handler)
3155 pSalInstance->updatePrinterUpdate();
3157 if( !pEvent->in )
3158 pThis->m_nKeyModifiers = ModKeyFlags::NONE;
3160 if( pThis->m_pIMHandler )
3161 pThis->m_pIMHandler->focusChanged( pEvent->in != 0 );
3163 // ask for changed printers like generic implementation
3164 if( pEvent->in && pSalInstance->isPrinterInit() )
3165 pSalInstance->updatePrinterUpdate();
3167 // FIXME: find out who the hell steals the focus from our frame
3168 // while we have the pointer grabbed, this should not come from
3169 // the window manager. Is this an event that was still queued ?
3170 // 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 )
3174 pThis->CallCallbackExc( pEvent->in ? SalEvent::GetFocus : SalEvent::LoseFocus, nullptr );
3176 return false;
3179 gboolean GtkSalFrame::signalMap(GtkWidget *, GdkEvent*, gpointer frame)
3181 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3183 pThis->CallCallbackExc( SalEvent::Resize, nullptr );
3184 pThis->TriggerPaintEvent();
3186 return false;
3189 gboolean GtkSalFrame::signalUnmap( GtkWidget*, GdkEvent*, gpointer frame )
3191 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3193 pThis->CallCallbackExc( SalEvent::Resize, nullptr );
3195 if (pThis->m_bFloatPositioned)
3197 // Unrealize is needed for cases where we reuse the same popup
3198 // (e.g. the font name control), making the realize signal fire
3199 // again on next show.
3200 gtk_widget_unrealize(pThis->m_pWindow);
3201 pThis->m_bFloatPositioned = false;
3204 return false;
3207 gboolean GtkSalFrame::signalKey(GtkWidget* pWidget, GdkEventKey* pEvent, gpointer frame)
3209 UpdateLastInputEventTime(pEvent->time);
3211 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3213 if (pThis->isFloatGrabWindow())
3214 return signalKey(pWidget, pEvent, pThis->m_pParent);
3216 vcl::DeletionListener aDel( pThis );
3218 if( pThis->m_pIMHandler )
3220 if( pThis->m_pIMHandler->handleKeyEvent( pEvent ) )
3221 return true;
3224 bool bStopProcessingKey = false;
3226 // handle modifiers
3227 if( pEvent->keyval == GDK_KEY_Shift_L || pEvent->keyval == GDK_KEY_Shift_R ||
3228 pEvent->keyval == GDK_KEY_Control_L || pEvent->keyval == GDK_KEY_Control_R ||
3229 pEvent->keyval == GDK_KEY_Alt_L || pEvent->keyval == GDK_KEY_Alt_R ||
3230 pEvent->keyval == GDK_KEY_Meta_L || pEvent->keyval == GDK_KEY_Meta_R ||
3231 pEvent->keyval == GDK_KEY_Super_L || pEvent->keyval == GDK_KEY_Super_R )
3233 sal_uInt16 nModCode = GetKeyModCode( pEvent->state );
3234 ModKeyFlags nExtModMask = ModKeyFlags::NONE;
3235 sal_uInt16 nModMask = 0;
3236 // pressing just the ctrl key leads to a keysym of XK_Control but
3237 // the event state does not contain ControlMask. In the release
3238 // event it's the other way round: it does contain the Control mask.
3239 // The modifier mode therefore has to be adapted manually.
3240 switch( pEvent->keyval )
3242 case GDK_KEY_Control_L:
3243 nExtModMask = ModKeyFlags::LeftMod1;
3244 nModMask = KEY_MOD1;
3245 break;
3246 case GDK_KEY_Control_R:
3247 nExtModMask = ModKeyFlags::RightMod1;
3248 nModMask = KEY_MOD1;
3249 break;
3250 case GDK_KEY_Alt_L:
3251 nExtModMask = ModKeyFlags::LeftMod2;
3252 nModMask = KEY_MOD2;
3253 break;
3254 case GDK_KEY_Alt_R:
3255 nExtModMask = ModKeyFlags::RightMod2;
3256 nModMask = KEY_MOD2;
3257 break;
3258 case GDK_KEY_Shift_L:
3259 nExtModMask = ModKeyFlags::LeftShift;
3260 nModMask = KEY_SHIFT;
3261 break;
3262 case GDK_KEY_Shift_R:
3263 nExtModMask = ModKeyFlags::RightShift;
3264 nModMask = KEY_SHIFT;
3265 break;
3266 // Map Meta/Super to MOD3 modifier on all Unix systems
3267 // except Mac OS X
3268 case GDK_KEY_Meta_L:
3269 case GDK_KEY_Super_L:
3270 nExtModMask = ModKeyFlags::LeftMod3;
3271 nModMask = KEY_MOD3;
3272 break;
3273 case GDK_KEY_Meta_R:
3274 case GDK_KEY_Super_R:
3275 nExtModMask = ModKeyFlags::RightMod3;
3276 nModMask = KEY_MOD3;
3277 break;
3280 SalKeyModEvent aModEvt;
3281 aModEvt.mbDown = pEvent->type == GDK_KEY_PRESS;
3282 aModEvt.mnCode = nModCode;
3284 if( pEvent->type == GDK_KEY_RELEASE )
3286 aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
3287 nModCode &= ~nModMask;
3288 pThis->m_nKeyModifiers &= ~nExtModMask;
3290 else
3292 nModCode |= nModMask;
3293 pThis->m_nKeyModifiers |= nExtModMask;
3294 aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
3297 pThis->CallCallbackExc( SalEvent::KeyModChange, &aModEvt );
3299 else
3301 bStopProcessingKey = pThis->doKeyCallback(pEvent->state,
3302 pEvent->keyval,
3303 pEvent->hardware_keycode,
3304 pEvent->group,
3305 sal_Unicode(gdk_keyval_to_unicode( pEvent->keyval )),
3306 (pEvent->type == GDK_KEY_PRESS),
3307 false);
3308 if( ! aDel.isDeleted() )
3309 pThis->m_nKeyModifiers = ModKeyFlags::NONE;
3312 if( !aDel.isDeleted() && pThis->m_pIMHandler )
3313 pThis->m_pIMHandler->updateIMSpotLocation();
3315 return bStopProcessingKey;
3318 gboolean GtkSalFrame::signalDelete( GtkWidget*, GdkEvent*, gpointer frame )
3320 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3322 //If we went into the backdrop we disabled the toplevel window, if we
3323 //receive a delete here, re-enable so we can process it
3324 bool bBackDrop = (gtk_widget_get_state_flags(GTK_WIDGET(pThis->m_pWindow)) & GTK_STATE_FLAG_BACKDROP);
3325 if (bBackDrop)
3326 pThis->GetWindow()->Enable();
3328 pThis->CallCallbackExc( SalEvent::Close, nullptr );
3330 return true;
3333 void GtkSalFrame::signalStyleUpdated(GtkWidget*, gpointer frame)
3335 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3337 // note: settings changed for multiple frames is avoided in winproc.cxx ImplHandleSettings
3338 GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::SettingsChanged );
3340 // fire off font-changed when the system cairo font hints change
3341 GtkInstance *pInstance = static_cast<GtkInstance*>(GetSalData()->m_pInstance);
3342 const cairo_font_options_t* pLastCairoFontOptions = pInstance->GetLastSeenCairoFontOptions();
3343 const cairo_font_options_t* pCurrentCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
3344 bool bFontSettingsChanged = true;
3345 if (pLastCairoFontOptions && pCurrentCairoFontOptions)
3346 bFontSettingsChanged = !cairo_font_options_equal(pLastCairoFontOptions, pCurrentCairoFontOptions);
3347 else if (!pLastCairoFontOptions && !pCurrentCairoFontOptions)
3348 bFontSettingsChanged = false;
3349 if (bFontSettingsChanged)
3351 pInstance->ResetLastSeenCairoFontOptions(pCurrentCairoFontOptions);
3352 GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::FontChanged );
3356 gboolean GtkSalFrame::signalWindowState( GtkWidget*, GdkEvent* pEvent, gpointer frame )
3358 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3359 if( (pThis->m_nState & GDK_WINDOW_STATE_ICONIFIED) != (pEvent->window_state.new_window_state & GDK_WINDOW_STATE_ICONIFIED ) )
3361 GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize );
3362 pThis->TriggerPaintEvent();
3365 if ((pEvent->window_state.new_window_state & GDK_WINDOW_STATE_MAXIMIZED) &&
3366 !(pThis->m_nState & GDK_WINDOW_STATE_MAXIMIZED))
3368 pThis->m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(pThis->m_pWindow));
3371 if ((pEvent->window_state.new_window_state & GDK_WINDOW_STATE_WITHDRAWN) &&
3372 !(pThis->m_nState & GDK_WINDOW_STATE_WITHDRAWN))
3374 if (pThis->isFloatGrabWindow())
3375 pThis->closePopup();
3378 pThis->m_nState = pEvent->window_state.new_window_state;
3380 #if OSL_DEBUG_LEVEL > 1
3381 if( (pEvent->window_state.changed_mask & GDK_WINDOW_STATE_FULLSCREEN) )
3383 fprintf( stderr, "window %p %s full screen state\n",
3384 pThis,
3385 (pEvent->window_state.new_window_state & GDK_WINDOW_STATE_FULLSCREEN) ? "enters" : "leaves");
3387 #endif
3389 return false;
3392 gboolean GtkSalFrame::signalVisibility( GtkWidget*, GdkEventVisibility* /*pEvent*/, gpointer /*frame*/ )
3394 return true;
3397 namespace
3399 GdkDragAction VclToGdk(sal_Int8 dragOperation)
3401 GdkDragAction eRet(static_cast<GdkDragAction>(0));
3402 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
3403 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_COPY);
3404 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
3405 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_MOVE);
3406 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
3407 eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_LINK);
3408 return eRet;
3411 sal_Int8 GdkToVcl(GdkDragAction dragOperation)
3413 sal_Int8 nRet(0);
3414 if (dragOperation & GDK_ACTION_COPY)
3415 nRet |= css::datatransfer::dnd::DNDConstants::ACTION_COPY;
3416 if (dragOperation & GDK_ACTION_MOVE)
3417 nRet |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
3418 if (dragOperation & GDK_ACTION_LINK)
3419 nRet |= css::datatransfer::dnd::DNDConstants::ACTION_LINK;
3420 return nRet;
3424 namespace
3426 GdkDragAction getPreferredDragAction(sal_Int8 dragOperation)
3428 GdkDragAction eAct(static_cast<GdkDragAction>(0));
3430 if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
3431 eAct = GDK_ACTION_MOVE;
3432 else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
3433 eAct = GDK_ACTION_COPY;
3434 else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
3435 eAct = GDK_ACTION_LINK;
3437 return eAct;
3441 static bool g_DropSuccessSet = false;
3442 static bool g_DropSuccess = false;
3444 class GtkDropTargetDropContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDropContext>
3446 GdkDragContext *m_pContext;
3447 guint m_nTime;
3448 public:
3449 GtkDropTargetDropContext(GdkDragContext *pContext, guint nTime)
3450 : m_pContext(pContext)
3451 , m_nTime(nTime)
3455 // XDropTargetDropContext
3456 virtual void SAL_CALL acceptDrop(sal_Int8 dragOperation) override
3458 gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
3461 virtual void SAL_CALL rejectDrop() override
3463 gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
3466 virtual void SAL_CALL dropComplete(sal_Bool bSuccess) override
3468 gtk_drag_finish(m_pContext, bSuccess, false, m_nTime);
3469 if (GtkDragSource::g_ActiveDragSource)
3471 g_DropSuccessSet = true;
3472 g_DropSuccess = bSuccess;
3477 class GtkDnDTransferable : public GtkTransferable
3479 GdkDragContext *m_pContext;
3480 guint m_nTime;
3481 GtkWidget *m_pWidget;
3482 GtkSalFrame *m_pFrame;
3483 GMainLoop *m_pLoop;
3484 GtkSelectionData *m_pData;
3485 public:
3486 GtkDnDTransferable(GdkDragContext *pContext, guint nTime, GtkWidget *pWidget, GtkSalFrame *pFrame)
3487 : m_pContext(pContext)
3488 , m_nTime(nTime)
3489 , m_pWidget(pWidget)
3490 , m_pFrame(pFrame)
3491 , m_pLoop(nullptr)
3492 , m_pData(nullptr)
3496 virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override
3498 css::datatransfer::DataFlavor aFlavor(rFlavor);
3499 if (aFlavor.MimeType == "text/plain;charset=utf-16")
3500 aFlavor.MimeType = "text/plain;charset=utf-8";
3502 auto it = m_aMimeTypeToAtom.find(aFlavor.MimeType);
3503 if (it == m_aMimeTypeToAtom.end())
3504 return css::uno::Any();
3506 /* like gtk_clipboard_wait_for_contents run a sub loop
3507 * waiting for drag-data-received triggered from
3508 * gtk_drag_get_data
3511 m_pLoop = g_main_loop_new(nullptr, true);
3512 m_pFrame->SetFormatConversionRequest(this);
3514 gtk_drag_get_data(m_pWidget, m_pContext, it->second, m_nTime);
3516 if (g_main_loop_is_running(m_pLoop))
3518 gdk_threads_leave();
3519 g_main_loop_run(m_pLoop);
3520 gdk_threads_enter();
3523 g_main_loop_unref(m_pLoop);
3524 m_pLoop = nullptr;
3525 m_pFrame->SetFormatConversionRequest(nullptr);
3528 css::uno::Any aRet;
3530 if (aFlavor.MimeType == "text/plain;charset=utf-8")
3532 OUString aStr;
3533 gchar *pText = reinterpret_cast<gchar*>(gtk_selection_data_get_text(m_pData));
3534 if (pText)
3535 aStr = OUString(pText, rtl_str_getLength(pText), RTL_TEXTENCODING_UTF8);
3536 g_free(pText);
3537 aRet <<= aStr.replaceAll("\r\n", "\n");
3539 else
3541 gint length(0);
3542 const guchar *rawdata = gtk_selection_data_get_data_with_length(m_pData,
3543 &length);
3544 css::uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
3545 aRet <<= aSeq;
3548 gtk_selection_data_free(m_pData);
3550 return aRet;
3553 virtual std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector() override
3555 std::vector<GdkAtom> targets;
3556 for (GList* l = gdk_drag_context_list_targets(m_pContext); l; l = l->next)
3557 targets.push_back(static_cast<GdkAtom>(l->data));
3558 return GtkTransferable::getTransferDataFlavorsAsVector(targets.data(), targets.size());
3561 void LoopEnd(GtkSelectionData *pData)
3563 m_pData = pData;
3564 g_main_loop_quit(m_pLoop);
3568 // For LibreOffice internal D&D we provide the Transferable without Gtk
3569 // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
3570 GtkDragSource* GtkDragSource::g_ActiveDragSource;
3572 gboolean GtkSalFrame::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time, gpointer frame)
3574 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3576 if (!pThis->m_pDropTarget)
3577 return false;
3579 css::datatransfer::dnd::DropTargetDropEvent aEvent;
3580 aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(pThis->m_pDropTarget);
3581 aEvent.Context = new GtkDropTargetDropContext(context, time);
3582 aEvent.LocationX = x;
3583 aEvent.LocationY = y;
3584 aEvent.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
3585 // ACTION_DEFAULT is documented as...
3586 // 'This means the user did not press any key during the Drag and Drop operation
3587 // and the action that was combined with ACTION_DEFAULT is the system default action'
3588 // in tdf#107031 writer won't insert a link when a heading is dragged from the
3589 // navigator unless this is set. Its unclear really what ACTION_DEFAULT means,
3590 // there is a deprecated 'GDK_ACTION_DEFAULT Means nothing, and should not be used'
3591 // possible equivalent in gtk.
3592 // So (tdf#109227) set ACTION_DEFAULT if no modifier key is held down
3593 GdkModifierType mask;
3594 gdk_window_get_pointer(widget_get_window(pWidget), nullptr, nullptr, &mask);
3595 if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
3596 aEvent.DropAction |= css::datatransfer::dnd::DNDConstants::ACTION_DEFAULT;
3597 aEvent.SourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
3598 css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
3599 // For LibreOffice internal D&D we provide the Transferable without Gtk
3600 // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
3601 if (GtkDragSource::g_ActiveDragSource)
3602 xTransferable = GtkDragSource::g_ActiveDragSource->GetTransferrable();
3603 else
3604 xTransferable = new GtkDnDTransferable(context, time, pWidget, pThis);
3605 aEvent.Transferable = xTransferable;
3607 pThis->m_pDropTarget->fire_drop(aEvent);
3609 return true;
3612 class GtkDropTargetDragContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDragContext>
3614 GdkDragContext *m_pContext;
3615 guint m_nTime;
3616 public:
3617 GtkDropTargetDragContext(GdkDragContext *pContext, guint nTime)
3618 : m_pContext(pContext)
3619 , m_nTime(nTime)
3623 virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) override
3625 gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
3628 virtual void SAL_CALL rejectDrag() override
3630 gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
3634 void GtkSalFrame::signalDragDropReceived(GtkWidget* /*pWidget*/, GdkDragContext * /*context*/, gint /*x*/, gint /*y*/, GtkSelectionData* data, guint /*ttype*/, guint /*time*/, gpointer frame)
3636 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3639 * If we get a drop, then we will call like gtk_clipboard_wait_for_contents
3640 * with a loop inside a loop to get the right format, so if this is the
3641 * case return to the outer loop here with a copy of the desired data
3643 * don't look at me like that.
3645 if (!pThis->m_pFormatConversionRequest)
3646 return;
3648 pThis->m_pFormatConversionRequest->LoopEnd(gtk_selection_data_copy(data));
3651 gboolean GtkSalFrame::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time, gpointer frame)
3653 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3655 if (!pThis->m_pDropTarget)
3656 return false;
3658 if (!pThis->m_bInDrag)
3659 gtk_drag_highlight(pWidget);
3661 css::datatransfer::dnd::DropTargetDragEnterEvent aEvent;
3662 aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(pThis->m_pDropTarget);
3663 GtkDropTargetDragContext* pContext = new GtkDropTargetDragContext(context, time);
3664 //preliminary accept the Drag and select the preferred action, the fire_* will
3665 //inform the original caller of our choice and the callsite can decide
3666 //to overrule this choice. i.e. typically here we default to ACTION_MOVE
3667 sal_Int8 nSourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
3668 GdkModifierType mask;
3669 gdk_window_get_pointer(widget_get_window(pWidget), nullptr, nullptr, &mask);
3671 // tdf#109227 if a modifier is held down, default to the matching
3672 // action for that modifier combo, otherwise pick the preferred
3673 // default from the possible source actions
3674 sal_Int8 nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
3675 if ((mask & GDK_SHIFT_MASK) && !(mask & GDK_CONTROL_MASK))
3676 nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
3677 else if ((mask & GDK_CONTROL_MASK) && !(mask & GDK_SHIFT_MASK))
3678 nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
3679 else if ((mask & GDK_SHIFT_MASK) && (mask & GDK_CONTROL_MASK) )
3680 nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_LINK;
3681 nNewDropAction &= nSourceActions;
3683 GdkDragAction eAction;
3684 if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) && !nNewDropAction)
3685 eAction = getPreferredDragAction(nSourceActions);
3686 else
3687 eAction = getPreferredDragAction(nNewDropAction);
3689 gdk_drag_status(context, eAction, time);
3690 aEvent.Context = pContext;
3691 aEvent.LocationX = x;
3692 aEvent.LocationY = y;
3693 //under wayland at least, the action selected by gdk_drag_status on the
3694 //context is not immediately available via gdk_drag_context_get_selected_action
3695 //so here we set the DropAction from what we selected on the context, not
3696 //what the context says is selected
3697 aEvent.DropAction = GdkToVcl(eAction);
3698 aEvent.SourceActions = nSourceActions;
3700 if (!pThis->m_bInDrag)
3702 css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
3703 // For LibreOffice internal D&D we provide the Transferable without Gtk
3704 // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
3705 if (GtkDragSource::g_ActiveDragSource)
3706 xTransferable = GtkDragSource::g_ActiveDragSource->GetTransferrable();
3707 else
3708 xTransferable = new GtkDnDTransferable(context, time, pWidget, pThis);
3709 css::uno::Sequence<css::datatransfer::DataFlavor> aFormats = xTransferable->getTransferDataFlavors();
3710 aEvent.SupportedDataFlavors = aFormats;
3711 pThis->m_pDropTarget->fire_dragEnter(aEvent);
3712 pThis->m_bInDrag = true;
3714 else
3716 pThis->m_pDropTarget->fire_dragOver(aEvent);
3719 return true;
3722 void GtkSalFrame::signalDragLeave(GtkWidget *pWidget, GdkDragContext * /*context*/, guint /*time*/, gpointer frame)
3724 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3725 if (!pThis->m_pDropTarget)
3726 return;
3727 pThis->m_bInDrag = false;
3728 gtk_drag_unhighlight(pWidget);
3731 void GtkSalFrame::signalDestroy( GtkWidget* pObj, gpointer frame )
3733 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3734 if( pObj == pThis->m_pWindow )
3736 pThis->m_pFixedContainer = nullptr;
3737 pThis->m_pEventBox = nullptr;
3738 pThis->m_pTopLevelGrid = nullptr;
3739 pThis->m_pWindow = nullptr;
3740 pThis->m_xFrameWeld.reset();
3741 pThis->InvalidateGraphics();
3745 // GtkSalFrame::IMHandler
3747 GtkSalFrame::IMHandler::IMHandler( GtkSalFrame* pFrame )
3748 : m_pFrame(pFrame),
3749 m_nPrevKeyPresses( 0 ),
3750 m_pIMContext( nullptr ),
3751 m_bFocused( true ),
3752 m_bPreeditJustChanged( false )
3754 m_aInputEvent.mpTextAttr = nullptr;
3755 createIMContext();
3758 GtkSalFrame::IMHandler::~IMHandler()
3760 // cancel an eventual event posted to begin preedit again
3761 GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
3762 deleteIMContext();
3765 void GtkSalFrame::IMHandler::createIMContext()
3767 if( m_pIMContext )
3768 return;
3770 m_pIMContext = gtk_im_multicontext_new ();
3771 g_signal_connect( m_pIMContext, "commit",
3772 G_CALLBACK (signalIMCommit), this );
3773 g_signal_connect( m_pIMContext, "preedit_changed",
3774 G_CALLBACK (signalIMPreeditChanged), this );
3775 g_signal_connect( m_pIMContext, "retrieve_surrounding",
3776 G_CALLBACK (signalIMRetrieveSurrounding), this );
3777 g_signal_connect( m_pIMContext, "delete_surrounding",
3778 G_CALLBACK (signalIMDeleteSurrounding), this );
3779 g_signal_connect( m_pIMContext, "preedit_start",
3780 G_CALLBACK (signalIMPreeditStart), this );
3781 g_signal_connect( m_pIMContext, "preedit_end",
3782 G_CALLBACK (signalIMPreeditEnd), this );
3784 GetGenericUnixSalData()->ErrorTrapPush();
3785 gtk_im_context_set_client_window(m_pIMContext, widget_get_window(m_pFrame->getMouseEventWidget()));
3786 gtk_im_context_focus_in( m_pIMContext );
3787 GetGenericUnixSalData()->ErrorTrapPop();
3788 m_bFocused = true;
3792 void GtkSalFrame::IMHandler::deleteIMContext()
3794 if( m_pIMContext )
3796 // first give IC a chance to deinitialize
3797 GetGenericUnixSalData()->ErrorTrapPush();
3798 gtk_im_context_set_client_window( m_pIMContext, nullptr );
3799 GetGenericUnixSalData()->ErrorTrapPop();
3800 // destroy old IC
3801 g_object_unref( m_pIMContext );
3802 m_pIMContext = nullptr;
3806 void GtkSalFrame::IMHandler::doCallEndExtTextInput()
3808 m_aInputEvent.mpTextAttr = nullptr;
3809 m_pFrame->CallCallbackExc( SalEvent::EndExtTextInput, nullptr );
3812 void GtkSalFrame::IMHandler::updateIMSpotLocation()
3814 SalExtTextInputPosEvent aPosEvent;
3815 m_pFrame->CallCallbackExc( SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent) );
3816 GdkRectangle aArea;
3817 aArea.x = aPosEvent.mnX;
3818 aArea.y = aPosEvent.mnY;
3819 aArea.width = aPosEvent.mnWidth;
3820 aArea.height = aPosEvent.mnHeight;
3821 GetGenericUnixSalData()->ErrorTrapPush();
3822 gtk_im_context_set_cursor_location( m_pIMContext, &aArea );
3823 GetGenericUnixSalData()->ErrorTrapPop();
3826 void GtkSalFrame::IMHandler::sendEmptyCommit()
3828 vcl::DeletionListener aDel( m_pFrame );
3830 SalExtTextInputEvent aEmptyEv;
3831 aEmptyEv.mpTextAttr = nullptr;
3832 aEmptyEv.maText.clear();
3833 aEmptyEv.mnCursorPos = 0;
3834 aEmptyEv.mnCursorFlags = 0;
3835 m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&aEmptyEv) );
3836 if( ! aDel.isDeleted() )
3837 m_pFrame->CallCallbackExc( SalEvent::EndExtTextInput, nullptr );
3840 void GtkSalFrame::IMHandler::endExtTextInput( EndExtTextInputFlags /*nFlags*/ )
3842 gtk_im_context_reset ( m_pIMContext );
3844 if( m_aInputEvent.mpTextAttr )
3846 vcl::DeletionListener aDel( m_pFrame );
3847 // delete preedit in sal (commit an empty string)
3848 sendEmptyCommit();
3849 if( ! aDel.isDeleted() )
3851 // mark previous preedit state again (will e.g. be sent at focus gain)
3852 m_aInputEvent.mpTextAttr = &m_aInputFlags[0];
3853 if( m_bFocused )
3855 // begin preedit again
3856 GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
3862 void GtkSalFrame::IMHandler::focusChanged( bool bFocusIn )
3864 m_bFocused = bFocusIn;
3865 if( bFocusIn )
3867 GetGenericUnixSalData()->ErrorTrapPush();
3868 gtk_im_context_focus_in( m_pIMContext );
3869 GetGenericUnixSalData()->ErrorTrapPop();
3870 if( m_aInputEvent.mpTextAttr )
3872 sendEmptyCommit();
3873 // begin preedit again
3874 GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
3877 else
3879 GetGenericUnixSalData()->ErrorTrapPush();
3880 gtk_im_context_focus_out( m_pIMContext );
3881 GetGenericUnixSalData()->ErrorTrapPop();
3882 // cancel an eventual event posted to begin preedit again
3883 GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
3887 bool GtkSalFrame::IMHandler::handleKeyEvent( GdkEventKey* pEvent )
3889 vcl::DeletionListener aDel( m_pFrame );
3891 if( pEvent->type == GDK_KEY_PRESS )
3893 // Add this key press event to the list of previous key presses
3894 // to which we compare key release events. If a later key release
3895 // event has a matching key press event in this list, we swallow
3896 // the key release because some GTK Input Methods don't swallow it
3897 // for us.
3898 m_aPrevKeyPresses.emplace_back(pEvent );
3899 m_nPrevKeyPresses++;
3901 // Also pop off the earliest key press event if there are more than 10
3902 // already.
3903 while (m_nPrevKeyPresses > 10)
3905 m_aPrevKeyPresses.pop_front();
3906 m_nPrevKeyPresses--;
3909 GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) );
3911 // #i51353# update spot location on every key input since we cannot
3912 // know which key may activate a preedit choice window
3913 updateIMSpotLocation();
3914 if( aDel.isDeleted() )
3915 return true;
3917 gboolean bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
3918 g_object_unref( pRef );
3920 if( aDel.isDeleted() )
3921 return true;
3923 m_bPreeditJustChanged = false;
3925 if( bResult )
3926 return true;
3927 else
3929 SAL_WARN_IF( m_nPrevKeyPresses <= 0, "vcl.gtk3", "key press has vanished !" );
3930 if( ! m_aPrevKeyPresses.empty() ) // sanity check
3932 // event was not swallowed, do not filter a following
3933 // key release event
3934 // note: this relies on gtk_im_context_filter_keypress
3935 // returning without calling a handler (in the "not swallowed"
3936 // case ) which might change the previous key press list so
3937 // we would pop the wrong event here
3938 m_aPrevKeyPresses.pop_back();
3939 m_nPrevKeyPresses--;
3944 // Determine if we got an earlier key press event corresponding to this key release
3945 if (pEvent->type == GDK_KEY_RELEASE)
3947 GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) );
3948 gboolean bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
3949 g_object_unref( pRef );
3951 if( aDel.isDeleted() )
3952 return true;
3954 m_bPreeditJustChanged = false;
3956 std::list<PreviousKeyPress>::iterator iter = m_aPrevKeyPresses.begin();
3957 std::list<PreviousKeyPress>::iterator iter_end = m_aPrevKeyPresses.end();
3958 while (iter != iter_end)
3960 // If we found a corresponding previous key press event, swallow the release
3961 // and remove the earlier key press from our list
3962 if (*iter == pEvent)
3964 m_aPrevKeyPresses.erase(iter);
3965 m_nPrevKeyPresses--;
3966 return true;
3968 ++iter;
3971 if( bResult )
3972 return true;
3975 return false;
3978 /* FIXME:
3979 * #122282# still more hacking: some IMEs never start a preedit but simply commit
3980 * in this case we cannot commit a single character. Workaround: do not do the
3981 * single key hack for enter or space if the unicode committed does not match
3984 static bool checkSingleKeyCommitHack( guint keyval, sal_Unicode cCode )
3986 bool bRet = true;
3987 switch( keyval )
3989 case GDK_KEY_KP_Enter:
3990 case GDK_KEY_Return:
3991 if( cCode != '\n' && cCode != '\r' )
3992 bRet = false;
3993 break;
3994 case GDK_KEY_space:
3995 case GDK_KEY_KP_Space:
3996 if( cCode != ' ' )
3997 bRet = false;
3998 break;
3999 default:
4000 break;
4002 return bRet;
4005 void GtkSalFrame::IMHandler::signalIMCommit( GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler )
4007 GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
4009 SolarMutexGuard aGuard;
4010 vcl::DeletionListener aDel( pThis->m_pFrame );
4012 const bool bWasPreedit =
4013 (pThis->m_aInputEvent.mpTextAttr != nullptr) ||
4014 pThis->m_bPreeditJustChanged;
4016 pThis->m_aInputEvent.mpTextAttr = nullptr;
4017 pThis->m_aInputEvent.maText = OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 );
4018 pThis->m_aInputEvent.mnCursorPos = pThis->m_aInputEvent.maText.getLength();
4019 pThis->m_aInputEvent.mnCursorFlags = 0;
4021 pThis->m_aInputFlags.clear();
4023 /* necessary HACK: all keyboard input comes in here as soon as a IMContext is set
4024 * which is logical and consequent. But since even simple input like
4025 * <space> comes through the commit signal instead of signalKey
4026 * and all kinds of windows only implement KeyInput (e.g. PushButtons,
4027 * RadioButtons and a lot of other Controls), will send a single
4028 * KeyInput/KeyUp sequence instead of an ExtText event if there
4029 * never was a preedit and the text is only one character.
4031 * In this case there the last ExtText event must have been
4032 * SalEvent::EndExtTextInput, either because of a regular commit
4033 * or because there never was a preedit.
4035 bool bSingleCommit = false;
4036 if( ! bWasPreedit
4037 && pThis->m_aInputEvent.maText.getLength() == 1
4038 && ! pThis->m_aPrevKeyPresses.empty()
4041 const PreviousKeyPress& rKP = pThis->m_aPrevKeyPresses.back();
4042 sal_Unicode aOrigCode = pThis->m_aInputEvent.maText[0];
4044 if( checkSingleKeyCommitHack( rKP.keyval, aOrigCode ) )
4046 pThis->m_pFrame->doKeyCallback( rKP.state, rKP.keyval, rKP.hardware_keycode, rKP.group, aOrigCode, true, true );
4047 bSingleCommit = true;
4050 if( ! bSingleCommit )
4052 pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
4053 if( ! aDel.isDeleted() )
4054 pThis->doCallEndExtTextInput();
4056 if( ! aDel.isDeleted() )
4058 // reset input event
4059 pThis->m_aInputEvent.maText.clear();
4060 pThis->m_aInputEvent.mnCursorPos = 0;
4061 pThis->updateIMSpotLocation();
4066 void GtkSalFrame::IMHandler::signalIMPreeditChanged( GtkIMContext*, gpointer im_handler )
4068 GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
4070 char* pText = nullptr;
4071 PangoAttrList* pAttrs = nullptr;
4072 gint nCursorPos = 0;
4074 gtk_im_context_get_preedit_string( pThis->m_pIMContext,
4075 &pText,
4076 &pAttrs,
4077 &nCursorPos );
4078 if( pText && ! *pText ) // empty string
4080 // change from nothing to nothing -> do not start preedit
4081 // e.g. this will activate input into a calc cell without
4082 // user input
4083 if( pThis->m_aInputEvent.maText.getLength() == 0 )
4085 g_free( pText );
4086 pango_attr_list_unref( pAttrs );
4087 return;
4091 pThis->m_bPreeditJustChanged = true;
4093 bool bEndPreedit = (!pText || !*pText) && pThis->m_aInputEvent.mpTextAttr != nullptr;
4094 pThis->m_aInputEvent.maText = pText ? OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 ) : OUString();
4095 pThis->m_aInputEvent.mnCursorPos = nCursorPos;
4096 pThis->m_aInputEvent.mnCursorFlags = 0;
4098 pThis->m_aInputFlags = std::vector<ExtTextInputAttr>( std::max( 1, static_cast<int>(pThis->m_aInputEvent.maText.getLength()) ), ExtTextInputAttr::NONE );
4100 PangoAttrIterator *iter = pango_attr_list_get_iterator(pAttrs);
4103 GSList *attr_list = nullptr;
4104 GSList *tmp_list = nullptr;
4105 gint start, end;
4106 ExtTextInputAttr sal_attr = ExtTextInputAttr::NONE;
4108 pango_attr_iterator_range (iter, &start, &end);
4109 if (start == G_MAXINT || end == G_MAXINT)
4111 auto len = pText ? g_utf8_strlen(pText, -1) : 0;
4112 if (end == G_MAXINT)
4113 end = len;
4114 if (start == G_MAXINT)
4115 start = len;
4117 if (end == start)
4118 continue;
4120 start = g_utf8_pointer_to_offset (pText, pText + start);
4121 end = g_utf8_pointer_to_offset (pText, pText + end);
4123 tmp_list = attr_list = pango_attr_iterator_get_attrs (iter);
4124 while (tmp_list)
4126 PangoAttribute *pango_attr = static_cast<PangoAttribute *>(tmp_list->data);
4128 switch (pango_attr->klass->type)
4130 case PANGO_ATTR_BACKGROUND:
4131 sal_attr |= ExtTextInputAttr::Highlight;
4132 pThis->m_aInputEvent.mnCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
4133 break;
4134 case PANGO_ATTR_UNDERLINE:
4135 sal_attr |= ExtTextInputAttr::Underline;
4136 break;
4137 case PANGO_ATTR_STRIKETHROUGH:
4138 sal_attr |= ExtTextInputAttr::RedText;
4139 break;
4140 default:
4141 break;
4143 pango_attribute_destroy (pango_attr);
4144 tmp_list = tmp_list->next;
4146 if (sal_attr == ExtTextInputAttr::NONE)
4147 sal_attr |= ExtTextInputAttr::Underline;
4148 g_slist_free (attr_list);
4150 // Set the sal attributes on our text
4151 for (int i = start; i < end; ++i)
4153 SAL_WARN_IF(i >= static_cast<int>(pThis->m_aInputFlags.size()),
4154 "vcl.gtk3", "pango attrib out of range. Broken range: "
4155 << start << "," << end << " Legal range: 0,"
4156 << pThis->m_aInputFlags.size());
4157 if (i >= static_cast<int>(pThis->m_aInputFlags.size()))
4158 continue;
4159 pThis->m_aInputFlags[i] |= sal_attr;
4161 } while (pango_attr_iterator_next (iter));
4162 pango_attr_iterator_destroy(iter);
4164 pThis->m_aInputEvent.mpTextAttr = &pThis->m_aInputFlags[0];
4166 g_free( pText );
4167 pango_attr_list_unref( pAttrs );
4169 SolarMutexGuard aGuard;
4170 vcl::DeletionListener aDel( pThis->m_pFrame );
4172 pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
4173 if( bEndPreedit && ! aDel.isDeleted() )
4174 pThis->doCallEndExtTextInput();
4175 if( ! aDel.isDeleted() )
4176 pThis->updateIMSpotLocation();
4179 void GtkSalFrame::IMHandler::signalIMPreeditStart( GtkIMContext*, gpointer /*im_handler*/ )
4183 void GtkSalFrame::IMHandler::signalIMPreeditEnd( GtkIMContext*, gpointer im_handler )
4185 GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
4187 pThis->m_bPreeditJustChanged = true;
4189 SolarMutexGuard aGuard;
4190 vcl::DeletionListener aDel( pThis->m_pFrame );
4191 pThis->doCallEndExtTextInput();
4192 if( ! aDel.isDeleted() )
4193 pThis->updateIMSpotLocation();
4196 uno::Reference<accessibility::XAccessibleEditableText>
4197 FindFocus(uno::Reference< accessibility::XAccessibleContext > const & xContext)
4199 if (!xContext.is())
4200 return uno::Reference< accessibility::XAccessibleEditableText >();
4202 uno::Reference<accessibility::XAccessibleStateSet> xState = xContext->getAccessibleStateSet();
4203 if (xState.is())
4205 if (xState->contains(accessibility::AccessibleStateType::FOCUSED))
4207 uno::Reference< accessibility::XAccessibleEditableText > xText =
4208 uno::Reference<accessibility::XAccessibleEditableText>(xContext, uno::UNO_QUERY);
4209 if (xText.is())
4210 return xText;
4211 if (xState->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS))
4212 return uno::Reference< accessibility::XAccessibleEditableText >();
4216 for (sal_Int32 i = 0; i < xContext->getAccessibleChildCount(); ++i)
4218 uno::Reference< accessibility::XAccessible > xChild = xContext->getAccessibleChild(i);
4219 if (!xChild.is())
4220 continue;
4221 uno::Reference< accessibility::XAccessibleContext > xChildContext = xChild->getAccessibleContext();
4222 if (!xChildContext.is())
4223 continue;
4224 uno::Reference< accessibility::XAccessibleEditableText > xText = FindFocus(xChildContext);
4225 if (xText.is())
4226 return xText;
4228 return uno::Reference< accessibility::XAccessibleEditableText >();
4231 static uno::Reference<accessibility::XAccessibleEditableText> lcl_GetxText(vcl::Window *pFocusWin)
4233 uno::Reference<accessibility::XAccessibleEditableText> xText;
4236 uno::Reference< accessibility::XAccessible > xAccessible( pFocusWin->GetAccessible() );
4237 if (xAccessible.is())
4238 xText = FindFocus(xAccessible->getAccessibleContext());
4240 catch(const uno::Exception& e)
4242 SAL_WARN( "vcl.gtk3", "Exception in getting input method surrounding text: " << e);
4244 return xText;
4247 gboolean GtkSalFrame::IMHandler::signalIMRetrieveSurrounding( GtkIMContext* pContext, gpointer /*im_handler*/ )
4249 vcl::Window *pFocusWin = Application::GetFocusWindow();
4250 if (!pFocusWin)
4251 return true;
4253 uno::Reference<accessibility::XAccessibleEditableText> xText = lcl_GetxText(pFocusWin);
4254 if (xText.is())
4256 sal_Int32 nPosition = xText->getCaretPosition();
4257 OUString sAllText = xText->getText();
4258 OString sUTF = OUStringToOString(sAllText, RTL_TEXTENCODING_UTF8);
4259 OUString sCursorText(sAllText.copy(0, nPosition));
4260 gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
4261 OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
4262 return true;
4265 return false;
4268 gboolean GtkSalFrame::IMHandler::signalIMDeleteSurrounding( GtkIMContext*, gint offset, gint nchars,
4269 gpointer /*im_handler*/ )
4271 vcl::Window *pFocusWin = Application::GetFocusWindow();
4272 if (!pFocusWin)
4273 return true;
4275 uno::Reference<accessibility::XAccessibleEditableText> xText = lcl_GetxText(pFocusWin);
4276 if (xText.is())
4278 sal_Int32 nPosition = xText->getCaretPosition();
4279 // #i111768# range checking
4280 sal_Int32 nDeletePos = nPosition + offset;
4281 sal_Int32 nDeleteEnd = nDeletePos + nchars;
4282 if (nDeletePos < 0)
4283 nDeletePos = 0;
4284 if (nDeleteEnd < 0)
4285 nDeleteEnd = 0;
4286 if (nDeleteEnd > xText->getCharacterCount())
4287 nDeleteEnd = xText->getCharacterCount();
4289 xText->deleteText(nDeletePos, nDeleteEnd);
4290 //tdf91641 adjust cursor if deleted chars shift it forward (normal case)
4291 if (nDeletePos < nPosition)
4293 if (nDeleteEnd <= nPosition)
4294 nPosition = nPosition - (nDeleteEnd - nDeletePos);
4295 else
4296 nPosition = nDeletePos;
4298 if (xText->getCharacterCount() >= nPosition)
4299 xText->setCaretPosition( nPosition );
4301 return true;
4304 return false;
4307 Size GtkSalDisplay::GetScreenSize( int nDisplayScreen )
4309 tools::Rectangle aRect = m_pSys->GetDisplayScreenPosSizePixel( nDisplayScreen );
4310 return Size( aRect.GetWidth(), aRect.GetHeight() );
4313 sal_uIntPtr GtkSalFrame::GetNativeWindowHandle(GtkWidget *pWidget)
4315 (void) this; // Silence loplugin:staticmethods
4316 GdkDisplay *pDisplay = getGdkDisplay();
4317 GdkWindow *pWindow = gtk_widget_get_window(pWidget);
4319 #if defined(GDK_WINDOWING_X11)
4320 if (GDK_IS_X11_DISPLAY(pDisplay))
4322 return GDK_WINDOW_XID(pWindow);
4324 #endif
4325 #if defined(GDK_WINDOWING_WAYLAND)
4326 if (GDK_IS_WAYLAND_DISPLAY(pDisplay))
4328 return reinterpret_cast<sal_uIntPtr>(gdk_wayland_window_get_wl_surface(pWindow));
4330 #endif
4331 return 0;
4334 sal_uIntPtr GtkSalFrame::GetNativeWindowHandle()
4336 return GetNativeWindowHandle(m_pWindow);
4339 void GtkDragSource::startDrag(const datatransfer::dnd::DragGestureEvent& rEvent,
4340 sal_Int8 sourceActions, sal_Int32 /*cursor*/, sal_Int32 /*image*/,
4341 const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
4342 const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener)
4344 m_xListener = rListener;
4345 m_xTrans = rTrans;
4347 if (m_pFrame)
4349 css::uno::Sequence<css::datatransfer::DataFlavor> aFormats = rTrans->getTransferDataFlavors();
4350 std::vector<GtkTargetEntry> aGtkTargets(m_aConversionHelper.FormatsToGtk(aFormats));
4351 GtkTargetList *pTargetList = gtk_target_list_new(aGtkTargets.data(), aGtkTargets.size());
4353 gint nDragButton = 1; // default to left button
4354 css::awt::MouseEvent aEvent;
4355 if (rEvent.Event >>= aEvent)
4357 if (aEvent.Buttons & css::awt::MouseButton::LEFT )
4358 nDragButton = 1;
4359 else if (aEvent.Buttons & css::awt::MouseButton::RIGHT)
4360 nDragButton = 3;
4361 else if (aEvent.Buttons & css::awt::MouseButton::MIDDLE)
4362 nDragButton = 2;
4365 // For LibreOffice internal D&D we provide the Transferable without Gtk
4366 // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
4367 g_ActiveDragSource = this;
4368 g_DropSuccessSet = false;
4369 g_DropSuccess = false;
4371 m_pFrame->startDrag(nDragButton, rEvent.DragOriginX, rEvent.DragOriginY,
4372 VclToGdk(sourceActions), pTargetList);
4374 gtk_target_list_unref(pTargetList);
4375 for (auto &a : aGtkTargets)
4376 g_free(a.target);
4378 else
4379 dragFailed();
4382 void GtkSalFrame::startDrag(gint nButton, gint nDragOriginX, gint nDragOriginY,
4383 GdkDragAction sourceActions, GtkTargetList* pTargetList)
4385 SolarMutexGuard aGuard;
4387 assert(m_pDragSource);
4389 GdkEvent aFakeEvent;
4390 memset(&aFakeEvent, 0, sizeof(GdkEvent));
4391 aFakeEvent.type = GDK_BUTTON_PRESS;
4392 aFakeEvent.button.window = widget_get_window(getMouseEventWidget());
4393 aFakeEvent.button.time = GDK_CURRENT_TIME;
4394 GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(getGdkDisplay());
4395 aFakeEvent.button.device = gdk_device_manager_get_client_pointer(pDeviceManager);
4397 GdkDragContext *pContext = gtk_drag_begin_with_coordinates(getMouseEventWidget(),
4398 pTargetList,
4399 sourceActions,
4400 nButton,
4401 &aFakeEvent,
4402 nDragOriginX,
4403 nDragOriginY);
4405 if (!pContext)
4406 m_pDragSource->dragFailed();
4409 void GtkDragSource::dragFailed()
4411 if (m_xListener.is())
4413 datatransfer::dnd::DragSourceDropEvent aEv;
4414 aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_NONE;
4415 aEv.DropSuccess = false;
4416 auto xListener = m_xListener;
4417 m_xListener.clear();
4418 xListener->dragDropEnd(aEv);
4422 gboolean GtkSalFrame::signalDragFailed(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkDragResult /*result*/, gpointer frame)
4424 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4425 if (!pThis->m_pDragSource)
4426 return false;
4427 pThis->m_pDragSource->dragFailed();
4428 return false;
4431 void GtkDragSource::dragDelete()
4433 if (m_xListener.is())
4435 datatransfer::dnd::DragSourceDropEvent aEv;
4436 aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_MOVE;
4437 aEv.DropSuccess = true;
4438 auto xListener = m_xListener;
4439 m_xListener.clear();
4440 xListener->dragDropEnd(aEv);
4444 void GtkSalFrame::signalDragDelete(GtkWidget* /*widget*/, GdkDragContext* /*context*/, gpointer frame)
4446 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4447 if (!pThis->m_pDragSource)
4448 return;
4449 pThis->m_pDragSource->dragDelete();
4452 void GtkDragSource::dragEnd(GdkDragContext* context)
4454 if (m_xListener.is())
4456 datatransfer::dnd::DragSourceDropEvent aEv;
4457 aEv.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
4458 // an internal drop can accept the drop but fail with dropComplete( false )
4459 // this is different than the GTK API
4460 if (g_DropSuccessSet)
4461 aEv.DropSuccess = g_DropSuccess;
4462 else
4463 aEv.DropSuccess = true;
4464 auto xListener = m_xListener;
4465 m_xListener.clear();
4466 xListener->dragDropEnd(aEv);
4468 g_ActiveDragSource = nullptr;
4471 void GtkSalFrame::signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer frame)
4473 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4474 if (!pThis->m_pDragSource)
4475 return;
4476 pThis->m_pDragSource->dragEnd(context);
4479 void GtkDragSource::dragDataGet(GtkSelectionData *data, guint info)
4481 m_aConversionHelper.setSelectionData(m_xTrans, data, info);
4484 void GtkSalFrame::signalDragDataGet(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkSelectionData *data, guint info,
4485 guint /*time*/, gpointer frame)
4487 GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4488 if (!pThis->m_pDragSource)
4489 return;
4490 pThis->m_pDragSource->dragDataGet(data, info);
4493 bool GtkSalFrame::CallCallbackExc(SalEvent nEvent, const void* pEvent) const
4495 bool nRet = false;
4498 nRet = CallCallback(nEvent, pEvent);
4500 catch (const css::uno::Exception&)
4502 auto e = cppu::getCaughtException();
4503 GtkSalData *pSalData = static_cast<GtkSalData*>(GetSalData());
4504 pSalData->setException(e);
4506 catch (std::exception & e)
4508 static_cast<GtkSalData *>(GetSalData())->setException(
4509 css::uno::Any(
4510 css::uno::RuntimeException(
4511 "wrapped std::exception "
4512 + o3tl::runtimeToOUString(e.what()))));
4514 catch (...)
4516 static_cast<GtkSalData *>(GetSalData())->setException(
4517 css::uno::Any(
4518 css::uno::RuntimeException("wrapped unknown exception")));
4520 return nRet;
4523 void GtkSalFrame::nopaint_container_resize_children(GtkContainer *pContainer)
4525 m_bSalObjectSetPosSize = true;
4526 gtk_container_resize_children(pContainer);
4527 m_bSalObjectSetPosSize = false;
4530 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */