/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if OSL_DEBUG_LEVEL > 1 # include #endif #include #include #include #include #include #if !GTK_CHECK_VERSION(4, 0, 0) # define GDK_ALT_MASK GDK_MOD1_MASK # define GDK_TOPLEVEL_STATE_MAXIMIZED GDK_WINDOW_STATE_MAXIMIZED # define GDK_TOPLEVEL_STATE_MINIMIZED GDK_WINDOW_STATE_ICONIFIED # define gdk_wayland_surface_get_wl_surface gdk_wayland_window_get_wl_surface # define gdk_x11_surface_get_xid gdk_x11_window_get_xid #endif using namespace com::sun::star; int GtkSalFrame::m_nFloats = 0; static GDBusConnection* pSessionBus = nullptr; sal_uInt16 GtkSalFrame::GetKeyModCode( guint state ) { sal_uInt16 nCode = 0; if( state & GDK_SHIFT_MASK ) nCode |= KEY_SHIFT; if( state & GDK_CONTROL_MASK ) nCode |= KEY_MOD1; if (state & GDK_ALT_MASK) nCode |= KEY_MOD2; if( state & GDK_SUPER_MASK ) nCode |= KEY_MOD3; return nCode; } sal_uInt16 GtkSalFrame::GetMouseModCode( guint state ) { sal_uInt16 nCode = GetKeyModCode( state ); if( state & GDK_BUTTON1_MASK ) nCode |= MOUSE_LEFT; if( state & GDK_BUTTON2_MASK ) nCode |= MOUSE_MIDDLE; if( state & GDK_BUTTON3_MASK ) nCode |= MOUSE_RIGHT; return nCode; } // KEY_F26 is the last function key known to keycodes.hxx static bool IsFunctionKeyVal(guint keyval) { return keyval >= GDK_KEY_F1 && keyval <= GDK_KEY_F26; } sal_uInt16 GtkSalFrame::GetKeyCode(guint keyval) { sal_uInt16 nCode = 0; if( keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9 ) nCode = KEY_0 + (keyval-GDK_KEY_0); else if( keyval >= GDK_KEY_KP_0 && keyval <= GDK_KEY_KP_9 ) nCode = KEY_0 + (keyval-GDK_KEY_KP_0); else if( keyval >= GDK_KEY_A && keyval <= GDK_KEY_Z ) nCode = KEY_A + (keyval-GDK_KEY_A ); else if( keyval >= GDK_KEY_a && keyval <= GDK_KEY_z ) nCode = KEY_A + (keyval-GDK_KEY_a ); else if (IsFunctionKeyVal(keyval)) { switch( keyval ) { // - - - - - Sun keyboard, see vcl/unx/source/app/saldisp.cxx // although GDK_KEY_F1 ... GDK_KEY_L10 are known to // gdk/gdkkeysyms.h, they are unlikely to be generated // except possibly by Solaris systems // this whole section needs review case GDK_KEY_L2: nCode = KEY_F12; break; case GDK_KEY_L3: nCode = KEY_PROPERTIES; break; case GDK_KEY_L4: nCode = KEY_UNDO; break; case GDK_KEY_L6: nCode = KEY_COPY; break; // KEY_F16 case GDK_KEY_L8: nCode = KEY_PASTE; break; // KEY_F18 case GDK_KEY_L10: nCode = KEY_CUT; break; // KEY_F20 default: nCode = KEY_F1 + (keyval-GDK_KEY_F1); break; } } else { switch( keyval ) { case GDK_KEY_KP_Down: case GDK_KEY_Down: nCode = KEY_DOWN; break; case GDK_KEY_KP_Up: case GDK_KEY_Up: nCode = KEY_UP; break; case GDK_KEY_KP_Left: case GDK_KEY_Left: nCode = KEY_LEFT; break; case GDK_KEY_KP_Right: case GDK_KEY_Right: nCode = KEY_RIGHT; break; case GDK_KEY_KP_Begin: case GDK_KEY_KP_Home: case GDK_KEY_Begin: case GDK_KEY_Home: nCode = KEY_HOME; break; case GDK_KEY_KP_End: case GDK_KEY_End: nCode = KEY_END; break; case GDK_KEY_KP_Page_Up: case GDK_KEY_Page_Up: nCode = KEY_PAGEUP; break; case GDK_KEY_KP_Page_Down: case GDK_KEY_Page_Down: nCode = KEY_PAGEDOWN; break; case GDK_KEY_KP_Enter: case GDK_KEY_Return: nCode = KEY_RETURN; break; case GDK_KEY_Escape: nCode = KEY_ESCAPE; break; case GDK_KEY_ISO_Left_Tab: case GDK_KEY_KP_Tab: case GDK_KEY_Tab: nCode = KEY_TAB; break; case GDK_KEY_BackSpace: nCode = KEY_BACKSPACE; break; case GDK_KEY_KP_Space: case GDK_KEY_space: nCode = KEY_SPACE; break; case GDK_KEY_KP_Insert: case GDK_KEY_Insert: nCode = KEY_INSERT; break; case GDK_KEY_KP_Delete: case GDK_KEY_Delete: nCode = KEY_DELETE; break; case GDK_KEY_plus: case GDK_KEY_KP_Add: nCode = KEY_ADD; break; case GDK_KEY_minus: case GDK_KEY_KP_Subtract: nCode = KEY_SUBTRACT; break; case GDK_KEY_asterisk: case GDK_KEY_KP_Multiply: nCode = KEY_MULTIPLY; break; case GDK_KEY_slash: case GDK_KEY_KP_Divide: nCode = KEY_DIVIDE; break; case GDK_KEY_period: nCode = KEY_POINT; break; case GDK_KEY_decimalpoint: nCode = KEY_POINT; break; case GDK_KEY_comma: nCode = KEY_COMMA; break; case GDK_KEY_less: nCode = KEY_LESS; break; case GDK_KEY_greater: nCode = KEY_GREATER; break; case GDK_KEY_KP_Equal: case GDK_KEY_equal: nCode = KEY_EQUAL; break; case GDK_KEY_Find: nCode = KEY_FIND; break; case GDK_KEY_Menu: nCode = KEY_CONTEXTMENU;break; case GDK_KEY_Help: nCode = KEY_HELP; break; case GDK_KEY_Undo: nCode = KEY_UNDO; break; case GDK_KEY_Redo: nCode = KEY_REPEAT; break; // on a sun keyboard this actually is usually SunXK_Stop = 0x0000FF69 (XK_Cancel), // but VCL doesn't have a key definition for that case GDK_KEY_Cancel: nCode = KEY_F11; break; case GDK_KEY_KP_Decimal: case GDK_KEY_KP_Separator: nCode = KEY_DECIMAL; break; case GDK_KEY_asciitilde: nCode = KEY_TILDE; break; case GDK_KEY_leftsinglequotemark: case GDK_KEY_quoteleft: nCode = KEY_QUOTELEFT; break; case GDK_KEY_bracketleft: nCode = KEY_BRACKETLEFT; break; case GDK_KEY_bracketright: nCode = KEY_BRACKETRIGHT; break; case GDK_KEY_semicolon: nCode = KEY_SEMICOLON; break; case GDK_KEY_quoteright: nCode = KEY_QUOTERIGHT; break; // some special cases, also see saldisp.cxx // - - - - - - - - - - - - - Apollo - - - - - - - - - - - - - 0x1000 // These can be found in ap_keysym.h case 0x1000FF02: // apXK_Copy nCode = KEY_COPY; break; case 0x1000FF03: // apXK_Cut nCode = KEY_CUT; break; case 0x1000FF04: // apXK_Paste nCode = KEY_PASTE; break; case 0x1000FF14: // apXK_Repeat nCode = KEY_REPEAT; break; // Exit, Save // - - - - - - - - - - - - - - D E C - - - - - - - - - - - - - 0x1000 // These can be found in DECkeysym.h case 0x1000FF00: nCode = KEY_DELETE; break; // - - - - - - - - - - - - - - H P - - - - - - - - - - - - - 0x1000 // These can be found in HPkeysym.h case 0x1000FF73: // hpXK_DeleteChar nCode = KEY_DELETE; break; case 0x1000FF74: // hpXK_BackTab case 0x1000FF75: // hpXK_KP_BackTab nCode = KEY_TAB; break; // - - - - - - - - - - - - - - I B M - - - - - - - - - - - - - // - - - - - - - - - - - - - - O S F - - - - - - - - - - - - - 0x1004 // These also can be found in HPkeysym.h case 0x1004FF02: // osfXK_Copy nCode = KEY_COPY; break; case 0x1004FF03: // osfXK_Cut nCode = KEY_CUT; break; case 0x1004FF04: // osfXK_Paste nCode = KEY_PASTE; break; case 0x1004FF07: // osfXK_BackTab nCode = KEY_TAB; break; case 0x1004FF08: // osfXK_BackSpace nCode = KEY_BACKSPACE; break; case 0x1004FF1B: // osfXK_Escape nCode = KEY_ESCAPE; break; // Up, Down, Left, Right, PageUp, PageDown // - - - - - - - - - - - - - - S C O - - - - - - - - - - - - - // - - - - - - - - - - - - - - S G I - - - - - - - - - - - - - 0x1007 // - - - - - - - - - - - - - - S N I - - - - - - - - - - - - - // - - - - - - - - - - - - - - S U N - - - - - - - - - - - - - 0x1005 // These can be found in Sunkeysym.h case 0x1005FF10: // SunXK_F36 nCode = KEY_F11; break; case 0x1005FF11: // SunXK_F37 nCode = KEY_F12; break; case 0x1005FF70: // SunXK_Props nCode = KEY_PROPERTIES; break; case 0x1005FF71: // SunXK_Front nCode = KEY_FRONT; break; case 0x1005FF72: // SunXK_Copy nCode = KEY_COPY; break; case 0x1005FF73: // SunXK_Open nCode = KEY_OPEN; break; case 0x1005FF74: // SunXK_Paste nCode = KEY_PASTE; break; case 0x1005FF75: // SunXK_Cut nCode = KEY_CUT; break; // - - - - - - - - - - - - - X F 8 6 - - - - - - - - - - - - - 0x1008 // These can be found in XF86keysym.h // but more importantly they are also available GTK/Gdk version 3 // and hence are already provided in gdk/gdkkeysyms.h, and hence // in gdk/gdk.h case GDK_KEY_Copy: nCode = KEY_COPY; break; // 0x1008ff57 case GDK_KEY_Cut: nCode = KEY_CUT; break; // 0x1008ff58 case GDK_KEY_Open: nCode = KEY_OPEN; break; // 0x1008ff6b case GDK_KEY_Paste: nCode = KEY_PASTE; break; // 0x1008ff6d } } return nCode; } #if !GTK_CHECK_VERSION(4, 0, 0) guint GtkSalFrame::GetKeyValFor(GdkKeymap* pKeyMap, guint16 hardware_keycode, guint8 group) { guint updated_keyval = 0; gdk_keymap_translate_keyboard_state(pKeyMap, hardware_keycode, GdkModifierType(0), group, &updated_keyval, nullptr, nullptr, nullptr); return updated_keyval; } #endif namespace { // F10 means either KEY_F10 or KEY_MENU, which has to be decided // in the independent part. struct KeyAlternate { sal_uInt16 nKeyCode; sal_Unicode nCharCode; KeyAlternate() : nKeyCode( 0 ), nCharCode( 0 ) {} KeyAlternate( sal_uInt16 nKey, sal_Unicode nChar = 0 ) : nKeyCode( nKey ), nCharCode( nChar ) {} }; } static KeyAlternate GetAlternateKeyCode( const sal_uInt16 nKeyCode ) { KeyAlternate aAlternate; switch( nKeyCode ) { case KEY_F10: aAlternate = KeyAlternate( KEY_MENU );break; case KEY_F24: aAlternate = KeyAlternate( KEY_SUBTRACT, '-' );break; } return aAlternate; } #if OSL_DEBUG_LEVEL > 0 static bool dumpframes = false; #endif bool GtkSalFrame::doKeyCallback( guint state, guint keyval, guint16 hardware_keycode, guint8 group, sal_Unicode aOrigCode, bool bDown, bool bSendRelease ) { SalKeyEvent aEvent; aEvent.mnCharCode = aOrigCode; aEvent.mnRepeat = 0; vcl::DeletionListener aDel( this ); #if OSL_DEBUG_LEVEL > 0 const char* pKeyDebug = getenv("VCL_GTK3_PAINTDEBUG"); if (pKeyDebug && *pKeyDebug == '1') { if (bDown) { // shift-zero forces a re-draw and event is swallowed if (keyval == GDK_KEY_0) { SAL_INFO("vcl.gtk3", "force widget_queue_draw."); gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea)); return false; } else if (keyval == GDK_KEY_1) { SAL_INFO("vcl.gtk3", "force repaint all."); TriggerPaintEvent(); return false; } else if (keyval == GDK_KEY_2) { dumpframes = !dumpframes; SAL_INFO("vcl.gtk3", "toggle dump frames to " << dumpframes); return false; } } } #endif /* * #i42122# translate all keys with Ctrl and/or Alt to group 0 else * shortcuts (e.g. Ctrl-o) will not work but be inserted by the * application * * #i52338# do this for all keys that the independent part has no key code * for * * fdo#41169 rather than use group 0, detect if there is a group which can * be used to input Latin text and use that if possible */ aEvent.mnCode = GetKeyCode( keyval ); #if !GTK_CHECK_VERSION(4, 0, 0) if( aEvent.mnCode == 0 ) { gint best_group = SAL_MAX_INT32; // Try and find Latin layout GdkKeymap* keymap = gdk_keymap_get_default(); GdkKeymapKey *keys; gint n_keys; if (gdk_keymap_get_entries_for_keyval(keymap, GDK_KEY_A, &keys, &n_keys)) { // Find the lowest group that supports Latin layout for (gint i = 0; i < n_keys; ++i) { if (keys[i].level != 0 && keys[i].level != 1) continue; best_group = std::min(best_group, keys[i].group); if (best_group == 0) break; } g_free(keys); } //Unavailable, go with original group then I suppose if (best_group == SAL_MAX_INT32) best_group = group; guint updated_keyval = GetKeyValFor(keymap, hardware_keycode, best_group); aEvent.mnCode = GetKeyCode(updated_keyval); } #else (void)hardware_keycode; (void)group; #endif aEvent.mnCode |= GetKeyModCode( state ); bool bStopProcessingKey; if (bDown) { bStopProcessingKey = CallCallbackExc(SalEvent::KeyInput, &aEvent); // #i46889# copy AlternateKeyCode handling from generic plugin if (!bStopProcessingKey) { KeyAlternate aAlternate = GetAlternateKeyCode( aEvent.mnCode ); if( aAlternate.nKeyCode ) { aEvent.mnCode = aAlternate.nKeyCode; if( aAlternate.nCharCode ) aEvent.mnCharCode = aAlternate.nCharCode; bStopProcessingKey = CallCallbackExc(SalEvent::KeyInput, &aEvent); } } if( bSendRelease && ! aDel.isDeleted() ) { CallCallbackExc(SalEvent::KeyUp, &aEvent); } } else bStopProcessingKey = CallCallbackExc(SalEvent::KeyUp, &aEvent); return bStopProcessingKey; } GtkSalFrame::GtkSalFrame( SalFrame* pParent, SalFrameStyleFlags nStyle ) : m_nXScreen( getDisplay()->GetDefaultXScreen() ) , m_pHeaderBar(nullptr) , m_bGraphics(false) , m_nSetFocusSignalId(0) #if !GTK_CHECK_VERSION(4, 0, 0) , m_aSmoothScrollIdle("GtkSalFrame m_aSmoothScrollIdle") #endif { getDisplay()->registerFrame( this ); m_bDefaultPos = true; m_bDefaultSize = ( (nStyle & SalFrameStyleFlags::SIZEABLE) && ! pParent ); Init( pParent, nStyle ); } GtkSalFrame::GtkSalFrame( SystemParentData* pSysData ) : m_nXScreen( getDisplay()->GetDefaultXScreen() ) , m_pHeaderBar(nullptr) , m_bGraphics(false) , m_nSetFocusSignalId(0) #if !GTK_CHECK_VERSION(4, 0, 0) , m_aSmoothScrollIdle("GtkSalFrame m_aSmoothScrollIdle") #endif { getDisplay()->registerFrame( this ); // permanently ignore errors from our unruly children ... GetGenericUnixSalData()->ErrorTrapPush(); m_bDefaultPos = true; m_bDefaultSize = true; Init( pSysData ); } // AppMenu watch functions. static void ObjectDestroyedNotify( gpointer data ) { if ( data ) { g_object_unref( data ); } } #if !GTK_CHECK_VERSION(4,0,0) static void hud_activated( gboolean hud_active, gpointer user_data ) { if ( hud_active ) { SolarMutexGuard aGuard; GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data ); GtkSalMenu* pSalMenu = reinterpret_cast< GtkSalMenu* >( pSalFrame->GetMenu() ); if ( pSalMenu ) pSalMenu->UpdateFull(); } } #endif static void attach_menu_model(GtkSalFrame* pSalFrame) { GtkWidget* pWidget = pSalFrame->getWindow(); GdkSurface* gdkWindow = widget_get_surface(pWidget); if ( gdkWindow == nullptr || g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) != nullptr ) return; // Create menu model and action group attached to this frame. GMenuModel* pMenuModel = G_MENU_MODEL( g_lo_menu_new() ); GActionGroup* pActionGroup = reinterpret_cast(g_lo_action_group_new()); // Set window properties. g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-menubar", pMenuModel, ObjectDestroyedNotify ); g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-action-group", pActionGroup, ObjectDestroyedNotify ); #if !GTK_CHECK_VERSION(4,0,0) // Get a DBus session connection. if (!pSessionBus) pSessionBus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, nullptr); if (!pSessionBus) return; // Generate menu paths. sal_uIntPtr windowId = GtkSalFrame::GetNativeWindowHandle(pWidget); gchar* aDBusWindowPath = g_strdup_printf( "/org/libreoffice/window/%lu", windowId ); gchar* aDBusMenubarPath = g_strdup_printf( "/org/libreoffice/window/%lu/menus/menubar", windowId ); GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay(); #if defined(GDK_WINDOWING_X11) if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay)) { gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_ID", "org.libreoffice" ); gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_MENUBAR_OBJECT_PATH", aDBusMenubarPath ); gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_WINDOW_OBJECT_PATH", aDBusWindowPath ); gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_OBJECT_PATH", "/org/libreoffice" ); gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_UNIQUE_BUS_NAME", g_dbus_connection_get_unique_name( pSessionBus ) ); } #endif #if defined(GDK_WINDOWING_WAYLAND) if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay)) { gdk_wayland_window_set_dbus_properties_libgtk_only(gdkWindow, "org.libreoffice", nullptr, aDBusMenubarPath, aDBusWindowPath, "/org/libreoffice", g_dbus_connection_get_unique_name( pSessionBus )); } #endif // Publish the menu model and the action group. SAL_INFO("vcl.unity", "exporting menu model at " << pMenuModel << " for window " << windowId); pSalFrame->m_nMenuExportId = g_dbus_connection_export_menu_model (pSessionBus, aDBusMenubarPath, pMenuModel, nullptr); SAL_INFO("vcl.unity", "exporting action group at " << pActionGroup << " for window " << windowId); pSalFrame->m_nActionGroupExportId = g_dbus_connection_export_action_group( pSessionBus, aDBusWindowPath, pActionGroup, nullptr); pSalFrame->m_nHudAwarenessId = hud_awareness_register( pSessionBus, aDBusMenubarPath, hud_activated, pSalFrame, nullptr, nullptr ); g_free( aDBusWindowPath ); g_free( aDBusMenubarPath ); #endif } void on_registrar_available( GDBusConnection * /*connection*/, const gchar * /*name*/, const gchar * /*name_owner*/, gpointer user_data ) { SolarMutexGuard aGuard; GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data ); SalMenu* pSalMenu = pSalFrame->GetMenu(); if ( pSalMenu != nullptr ) { GtkSalMenu* pGtkSalMenu = static_cast(pSalMenu); pGtkSalMenu->EnableUnity(true); } } // This is called when the registrar becomes unavailable. It shows the menubar. void on_registrar_unavailable( GDBusConnection * /*connection*/, const gchar * /*name*/, gpointer user_data ) { SolarMutexGuard aGuard; SAL_INFO("vcl.unity", "on_registrar_unavailable"); //pSessionBus = NULL; GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data ); SalMenu* pSalMenu = pSalFrame->GetMenu(); if ( pSalMenu ) { GtkSalMenu* pGtkSalMenu = static_cast< GtkSalMenu* >( pSalMenu ); pGtkSalMenu->EnableUnity(false); } } void GtkSalFrame::EnsureAppMenuWatch() { if ( m_nWatcherId ) return; // Get a DBus session connection. if ( pSessionBus == nullptr ) { pSessionBus = g_bus_get_sync( G_BUS_TYPE_SESSION, nullptr, nullptr ); if ( pSessionBus == nullptr ) return; } // Publish the menu only if AppMenu registrar is available. m_nWatcherId = g_bus_watch_name_on_connection( pSessionBus, "com.canonical.AppMenu.Registrar", G_BUS_NAME_WATCHER_FLAGS_NONE, on_registrar_available, on_registrar_unavailable, this, nullptr ); } void GtkSalFrame::InvalidateGraphics() { if( m_pGraphics ) { m_bGraphics = false; } } GtkSalFrame::~GtkSalFrame() { #if !GTK_CHECK_VERSION(4,0,0) m_aSmoothScrollIdle.Stop(); m_aSmoothScrollIdle.ClearInvokeHandler(); #endif if (m_pDropTarget) { m_pDropTarget->deinitialize(); m_pDropTarget = nullptr; } if (m_pDragSource) { m_pDragSource->deinitialize(); m_pDragSource= nullptr; } InvalidateGraphics(); if (m_pParent) { m_pParent->m_aChildren.remove( this ); } getDisplay()->deregisterFrame( this ); if( m_pRegion ) { cairo_region_destroy( m_pRegion ); } m_pIMHandler.reset(); //tdf#108705 remove grabs on event widget before //destroying event widget while (m_nGrabLevel) removeGrabLevel(); { SolarMutexGuard aGuard; if(m_nWatcherId) g_bus_unwatch_name(m_nWatcherId); } GtkWidget *pEventWidget = getMouseEventWidget(); for (auto handler_id : m_aMouseSignalIds) g_signal_handler_disconnect(G_OBJECT(pEventWidget), handler_id); #if !GTK_CHECK_VERSION(4, 0, 0) if( m_pFixedContainer ) gtk_widget_destroy( GTK_WIDGET( m_pFixedContainer ) ); if( m_pEventBox ) gtk_widget_destroy( GTK_WIDGET(m_pEventBox) ); if( m_pTopLevelGrid ) gtk_widget_destroy( GTK_WIDGET(m_pTopLevelGrid) ); #else g_signal_handler_disconnect(G_OBJECT(gtk_widget_get_display(pEventWidget)), m_nSettingChangedSignalId); #endif { SolarMutexGuard aGuard; if( m_pWindow ) { g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", nullptr ); if ( pSessionBus ) { if ( m_nHudAwarenessId ) hud_awareness_unregister( pSessionBus, m_nHudAwarenessId ); if ( m_nMenuExportId ) g_dbus_connection_unexport_menu_model( pSessionBus, m_nMenuExportId ); if ( m_nActionGroupExportId ) g_dbus_connection_unexport_action_group( pSessionBus, m_nActionGroupExportId ); } m_xFrameWeld.reset(); #if !GTK_CHECK_VERSION(4,0,0) gtk_widget_destroy( m_pWindow ); #else if (GTK_IS_WINDOW(m_pWindow)) gtk_window_destroy(GTK_WINDOW(m_pWindow)); else g_clear_pointer(&m_pWindow, gtk_widget_unparent); #endif } } #if !GTK_CHECK_VERSION(4,0,0) if( m_pForeignParent ) g_object_unref( G_OBJECT( m_pForeignParent ) ); if( m_pForeignTopLevel ) g_object_unref( G_OBJECT( m_pForeignTopLevel) ); #endif m_pGraphics.reset(); if (m_pSurface) cairo_surface_destroy(m_pSurface); } void GtkSalFrame::moveWindow( tools::Long nX, tools::Long nY ) { if( isChild( false ) ) { GtkWidget* pParent = m_pParent ? gtk_widget_get_parent(m_pWindow) : nullptr; // tdf#130414 it's possible that we were reparented and are no longer inside // our original GtkFixed parent if (pParent && GTK_IS_FIXED(pParent)) { gtk_fixed_move( GTK_FIXED(pParent), m_pWindow, nX - m_pParent->maGeometry.nX, nY - m_pParent->maGeometry.nY ); } } #if !GTK_CHECK_VERSION(4,0,0) else gtk_window_move( GTK_WINDOW(m_pWindow), nX, nY ); #endif } void GtkSalFrame::widget_set_size_request(tools::Long nWidth, tools::Long nHeight) { gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), nWidth, nHeight ); } void GtkSalFrame::window_resize(tools::Long nWidth, tools::Long nHeight) { m_nWidthRequest = nWidth; m_nHeightRequest = nHeight; gtk_window_set_default_size(GTK_WINDOW(m_pWindow), nWidth, nHeight); #if !GTK_CHECK_VERSION(4,0,0) if (gtk_widget_get_visible(m_pWindow)) gtk_window_resize(GTK_WINDOW(m_pWindow), nWidth, nHeight); #endif } void GtkSalFrame::resizeWindow( tools::Long nWidth, tools::Long nHeight ) { if( isChild( false ) ) { widget_set_size_request(nWidth, nHeight); } else if( ! isChild( true, false ) ) window_resize(nWidth, nHeight); } #if !GTK_CHECK_VERSION(4,0,0) // tdf#124694 GtkFixed takes the max size of all its children as its // preferred size, causing it to not clip its child, but grow instead. static void ooo_fixed_get_preferred_height(GtkWidget*, gint *minimum, gint *natural) { *minimum = 0; *natural = 0; } static void ooo_fixed_get_preferred_width(GtkWidget*, gint *minimum, gint *natural) { *minimum = 0; *natural = 0; } #endif static void ooo_fixed_class_init(GtkFixedClass *klass) { #if !GTK_CHECK_VERSION(4,0,0) GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); widget_class->get_accessible = ooo_fixed_get_accessible; widget_class->get_preferred_height = ooo_fixed_get_preferred_height; widget_class->get_preferred_width = ooo_fixed_get_preferred_width; #else (void)klass; #endif } /* * Always use a sub-class of GtkFixed we can tag for a11y. This allows us to * utilize GAIL for the toplevel window and toolkit implementation incl. * key event listener support .. */ GType ooo_fixed_get_type() { static GType type = 0; if (!type) { static const GTypeInfo tinfo = { sizeof (GtkFixedClass), nullptr, /* base init */ nullptr, /* base finalize */ reinterpret_cast(ooo_fixed_class_init), /* class init */ nullptr, /* class finalize */ nullptr, /* class data */ sizeof (GtkFixed), /* instance size */ 0, /* nb preallocs */ nullptr, /* instance init */ nullptr /* value table */ }; type = g_type_register_static( GTK_TYPE_FIXED, "OOoFixed", &tinfo, GTypeFlags(0)); } return type; } void GtkSalFrame::updateScreenNumber() { #if !GTK_CHECK_VERSION(4,0,0) int nScreen = 0; GdkScreen *pScreen = gtk_widget_get_screen( m_pWindow ); if( pScreen ) nScreen = getDisplay()->getSystem()->getScreenMonitorIdx( pScreen, maGeometry.nX, maGeometry.nY ); maGeometry.nDisplayScreenNumber = nScreen; #endif } GtkWidget *GtkSalFrame::getMouseEventWidget() const { #if !GTK_CHECK_VERSION(4,0,0) return GTK_WIDGET(m_pEventBox); #else return GTK_WIDGET(m_pFixedContainer); #endif } static void damaged(void *handle, sal_Int32 nExtentsX, sal_Int32 nExtentsY, sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight) { GtkSalFrame* pThis = static_cast(handle); pThis->damaged(nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight); } void GtkSalFrame::InitCommon() { m_pSurface = nullptr; m_nGrabLevel = 0; m_bSalObjectSetPosSize = false; m_aDamageHandler.handle = this; m_aDamageHandler.damaged = ::damaged; #if !GTK_CHECK_VERSION(4,0,0) m_aSmoothScrollIdle.SetInvokeHandler(LINK(this, GtkSalFrame, AsyncScroll)); #endif m_pTopLevelGrid = GTK_GRID(gtk_grid_new()); container_add(m_pWindow, GTK_WIDGET(m_pTopLevelGrid)); #if !GTK_CHECK_VERSION(4,0,0) m_pEventBox = GTK_EVENT_BOX(gtk_event_box_new()); gtk_widget_add_events( GTK_WIDGET(m_pEventBox), GDK_ALL_EVENTS_MASK ); gtk_widget_set_vexpand(GTK_WIDGET(m_pEventBox), true); gtk_widget_set_hexpand(GTK_WIDGET(m_pEventBox), true); gtk_grid_attach(m_pTopLevelGrid, GTK_WIDGET(m_pEventBox), 0, 0, 1, 1); #endif // add the fixed container child, // fixed is needed since we have to position plugin windows #if !GTK_CHECK_VERSION(4,0,0) m_pFixedContainer = GTK_FIXED(g_object_new( ooo_fixed_get_type(), nullptr )); m_pDrawingArea = m_pFixedContainer; #else m_pOverlay = GTK_OVERLAY(gtk_overlay_new()); m_pFixedContainer = GTK_FIXED(gtk_fixed_new()); m_pDrawingArea = GTK_DRAWING_AREA(gtk_drawing_area_new()); #endif gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), true); gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), 1, 1); #if !GTK_CHECK_VERSION(4,0,0) gtk_container_add( GTK_CONTAINER(m_pEventBox), GTK_WIDGET(m_pFixedContainer) ); #else gtk_widget_set_vexpand(GTK_WIDGET(m_pOverlay), true); gtk_widget_set_hexpand(GTK_WIDGET(m_pOverlay), true); gtk_grid_attach(m_pTopLevelGrid, GTK_WIDGET(m_pOverlay), 0, 0, 1, 1); gtk_overlay_set_child(m_pOverlay, GTK_WIDGET(m_pDrawingArea)); gtk_overlay_add_overlay(m_pOverlay, GTK_WIDGET(m_pFixedContainer)); #endif GtkWidget *pEventWidget = getMouseEventWidget(); #if !GTK_CHECK_VERSION(4,0,0) gtk_widget_set_app_paintable(GTK_WIDGET(m_pFixedContainer), true); gtk_widget_set_redraw_on_allocate(GTK_WIDGET(m_pFixedContainer), false); #endif #if GTK_CHECK_VERSION(4,0,0) m_nSettingChangedSignalId = g_signal_connect(G_OBJECT(gtk_widget_get_display(pEventWidget)), "setting-changed", G_CALLBACK(signalStyleUpdated), this); #else // use pEventWidget instead of m_pWindow to avoid infinite event loop under Linux Mint Mate 18.3 g_signal_connect(G_OBJECT(pEventWidget), "style-updated", G_CALLBACK(signalStyleUpdated), this); #endif gtk_widget_set_has_tooltip(pEventWidget, true); // connect signals m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "query-tooltip", G_CALLBACK(signalTooltipQuery), this )); #if !GTK_CHECK_VERSION(4,0,0) m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-press-event", G_CALLBACK(signalButton), this )); m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-release-event", G_CALLBACK(signalButton), this )); m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "motion-notify-event", G_CALLBACK(signalMotion), this )); m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "leave-notify-event", G_CALLBACK(signalCrossing), this )); m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "enter-notify-event", G_CALLBACK(signalCrossing), this )); m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "scroll-event", G_CALLBACK(signalScroll), this )); #else GtkGesture *pClick = gtk_gesture_click_new(); gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pClick), 0); // use GTK_PHASE_TARGET instead of default GTK_PHASE_BUBBLE because I don't // want click events in gtk widgets inside the overlay, e.g. the font size // combobox GtkEntry in the toolbar, to be propagated down to this widget gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(pClick), GTK_PHASE_TARGET); gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pClick)); g_signal_connect(pClick, "pressed", G_CALLBACK(gesturePressed), this); g_signal_connect(pClick, "released", G_CALLBACK(gestureReleased), this); GtkEventController* pMotionController = gtk_event_controller_motion_new(); g_signal_connect(pMotionController, "motion", G_CALLBACK(signalMotion), this); g_signal_connect(pMotionController, "enter", G_CALLBACK(signalEnter), this); g_signal_connect(pMotionController, "leave", G_CALLBACK(signalLeave), this); gtk_event_controller_set_propagation_phase(pMotionController, GTK_PHASE_TARGET); gtk_widget_add_controller(pEventWidget, pMotionController); GtkEventController* pScrollController = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES); g_signal_connect(pScrollController, "scroll", G_CALLBACK(signalScroll), this); gtk_widget_add_controller(pEventWidget, pScrollController); #endif //Drop Target Stuff #if GTK_CHECK_VERSION(4,0,0) GtkDropTargetAsync* pDropTarget = gtk_drop_target_async_new(nullptr, GdkDragAction(GDK_ACTION_ALL)); g_signal_connect(G_OBJECT(pDropTarget), "drag-enter", G_CALLBACK(signalDragMotion), this); g_signal_connect(G_OBJECT(pDropTarget), "drag-motion", G_CALLBACK(signalDragMotion), this); g_signal_connect(G_OBJECT(pDropTarget), "drag-leave", G_CALLBACK(signalDragLeave), this); g_signal_connect(G_OBJECT(pDropTarget), "drop", G_CALLBACK(signalDragDrop), this); gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pDropTarget)); #else gtk_drag_dest_set(GTK_WIDGET(pEventWidget), GtkDestDefaults(0), nullptr, 0, GdkDragAction(0)); gtk_drag_dest_set_track_motion(GTK_WIDGET(pEventWidget), true); m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-motion", G_CALLBACK(signalDragMotion), this )); m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-drop", G_CALLBACK(signalDragDrop), this )); m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-received", G_CALLBACK(signalDragDropReceived), this )); m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-leave", G_CALLBACK(signalDragLeave), this )); #endif #if !GTK_CHECK_VERSION(4,0,0) //Drag Source Stuff m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-end", G_CALLBACK(signalDragEnd), this )); m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-failed", G_CALLBACK(signalDragFailed), this )); m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-delete", G_CALLBACK(signalDragDelete), this )); m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-get", G_CALLBACK(signalDragDataGet), this )); #endif #if !GTK_CHECK_VERSION(4,0,0) g_signal_connect( G_OBJECT(m_pFixedContainer), "draw", G_CALLBACK(signalDraw), this ); g_signal_connect( G_OBJECT(m_pFixedContainer), "size-allocate", G_CALLBACK(sizeAllocated), this ); #else gtk_drawing_area_set_draw_func(m_pDrawingArea, signalDraw, this, nullptr); g_signal_connect(G_OBJECT(m_pDrawingArea), "resize", G_CALLBACK(sizeAllocated), this); #endif g_signal_connect(G_OBJECT(m_pFixedContainer), "realize", G_CALLBACK(signalRealize), this); #if !GTK_CHECK_VERSION(4,0,0) GtkGesture *pSwipe = gtk_gesture_swipe_new(pEventWidget); g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast(g_object_unref), pSwipe); #else GtkGesture *pSwipe = gtk_gesture_swipe_new(); gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pSwipe)); #endif g_signal_connect(pSwipe, "swipe", G_CALLBACK(gestureSwipe), this); gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pSwipe), GTK_PHASE_TARGET); #if !GTK_CHECK_VERSION(4,0,0) GtkGesture *pLongPress = gtk_gesture_long_press_new(pEventWidget); g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast(g_object_unref), pLongPress); #else GtkGesture *pLongPress = gtk_gesture_long_press_new(); gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pLongPress)); #endif g_signal_connect(pLongPress, "pressed", G_CALLBACK(gestureLongPress), this); gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pLongPress), GTK_PHASE_TARGET); #if !GTK_CHECK_VERSION(4,0,0) g_signal_connect_after( G_OBJECT(m_pWindow), "focus-in-event", G_CALLBACK(signalFocus), this ); g_signal_connect_after( G_OBJECT(m_pWindow), "focus-out-event", G_CALLBACK(signalFocus), this ); if (GTK_IS_WINDOW(m_pWindow)) // i.e. not if it's a GtkEventBox which doesn't have the signal m_nSetFocusSignalId = g_signal_connect( G_OBJECT(m_pWindow), "set-focus", G_CALLBACK(signalSetFocus), this ); #else GtkEventController* pFocusController = gtk_event_controller_focus_new(); g_signal_connect(pFocusController, "enter", G_CALLBACK(signalFocusEnter), this); g_signal_connect(pFocusController, "leave", G_CALLBACK(signalFocusLeave), this); gtk_widget_set_focusable(pEventWidget, true); gtk_widget_add_controller(pEventWidget, pFocusController); if (GTK_IS_WINDOW(m_pWindow)) // i.e. not if it's a GtkEventBox which doesn't have the property (presumably?) m_nSetFocusSignalId = g_signal_connect( G_OBJECT(m_pWindow), "notify::focus-widget", G_CALLBACK(signalSetFocus), this ); #endif #if !GTK_CHECK_VERSION(4,0,0) g_signal_connect( G_OBJECT(m_pWindow), "map-event", G_CALLBACK(signalMap), this ); g_signal_connect( G_OBJECT(m_pWindow), "unmap-event", G_CALLBACK(signalUnmap), this ); g_signal_connect( G_OBJECT(m_pWindow), "delete-event", G_CALLBACK(signalDelete), this ); #else g_signal_connect( G_OBJECT(m_pWindow), "map", G_CALLBACK(signalMap), this ); g_signal_connect( G_OBJECT(m_pWindow), "unmap", G_CALLBACK(signalUnmap), this ); if (GTK_IS_WINDOW(m_pWindow)) g_signal_connect( G_OBJECT(m_pWindow), "close-request", G_CALLBACK(signalDelete), this ); #endif #if !GTK_CHECK_VERSION(4,0,0) g_signal_connect( G_OBJECT(m_pWindow), "configure-event", G_CALLBACK(signalConfigure), this ); #endif #if !GTK_CHECK_VERSION(4,0,0) g_signal_connect( G_OBJECT(m_pWindow), "key-press-event", G_CALLBACK(signalKey), this ); g_signal_connect( G_OBJECT(m_pWindow), "key-release-event", G_CALLBACK(signalKey), this ); #else m_pKeyController = GTK_EVENT_CONTROLLER_KEY(gtk_event_controller_key_new()); g_signal_connect(m_pKeyController, "key-pressed", G_CALLBACK(signalKeyPressed), this); g_signal_connect(m_pKeyController, "key-released", G_CALLBACK(signalKeyReleased), this); gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(m_pKeyController)); #endif g_signal_connect( G_OBJECT(m_pWindow), "destroy", G_CALLBACK(signalDestroy), this ); // init members m_nKeyModifiers = ModKeyFlags::NONE; m_bFullscreen = false; #if GTK_CHECK_VERSION(4,0,0) m_nState = static_cast(0); #else m_nState = GDK_WINDOW_STATE_WITHDRAWN; #endif m_pIMHandler = nullptr; m_pRegion = nullptr; m_pDropTarget = nullptr; m_pDragSource = nullptr; m_bGeometryIsProvisional = false; m_bIconSetWhileUnmapped = false; m_bTooltipBlocked = false; m_ePointerStyle = static_cast(0xffff); m_pSalMenu = nullptr; m_nWatcherId = 0; m_nMenuExportId = 0; m_nActionGroupExportId = 0; m_nHudAwarenessId = 0; #if !GTK_CHECK_VERSION(4,0,0) gtk_widget_add_events( m_pWindow, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_SCROLL_MASK ); #endif // show the widgets #if !GTK_CHECK_VERSION(4,0,0) gtk_widget_show_all(GTK_WIDGET(m_pTopLevelGrid)); #else gtk_widget_show(GTK_WIDGET(m_pTopLevelGrid)); #endif // realize the window, we need an XWindow id gtk_widget_realize( m_pWindow ); if (GTK_IS_WINDOW(m_pWindow)) { #if !GTK_CHECK_VERSION(4,0,0) g_signal_connect(G_OBJECT(m_pWindow), "window-state-event", G_CALLBACK(signalWindowState), this); #else GdkSurface* gdkWindow = widget_get_surface(m_pWindow); g_signal_connect(G_OBJECT(gdkWindow), "notify::state", G_CALLBACK(signalWindowState), this); #endif } //system data m_aSystemData.SetWindowHandle(GetNativeWindowHandle(m_pWindow)); m_aSystemData.aShellWindow = reinterpret_cast(this); m_aSystemData.pSalFrame = this; m_aSystemData.pWidget = m_pWindow; m_aSystemData.nScreen = m_nXScreen.getXScreen(); m_aSystemData.toolkit = SystemEnvData::Toolkit::Gtk; #if defined(GDK_WINDOWING_X11) GdkDisplay *pDisplay = getGdkDisplay(); if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay)) { m_aSystemData.pDisplay = gdk_x11_display_get_xdisplay(pDisplay); m_aSystemData.platform = SystemEnvData::Platform::Xcb; #if !GTK_CHECK_VERSION(4,0,0) GdkScreen* pScreen = gtk_widget_get_screen(m_pWindow); GdkVisual* pVisual = gdk_screen_get_system_visual(pScreen); m_aSystemData.pVisual = gdk_x11_visual_get_xvisual(pVisual); #endif } #endif #if defined(GDK_WINDOWING_WAYLAND) if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay)) { m_aSystemData.pDisplay = gdk_wayland_display_get_wl_display(pDisplay); m_aSystemData.platform = SystemEnvData::Platform::Wayland; } #endif m_bGraphics = false; m_pGraphics = nullptr; m_nFloatFlags = FloatWinPopupFlags::NONE; m_bFloatPositioned = false; m_nWidthRequest = 0; m_nHeightRequest = 0; // fake an initial geometry, gets updated via configure event or SetPosSize if (m_bDefaultPos || m_bDefaultSize) { Size aDefSize = calcDefaultSize(); maGeometry.nX = -1; maGeometry.nY = -1; maGeometry.nWidth = aDefSize.Width(); maGeometry.nHeight = aDefSize.Height(); maGeometry.nTopDecoration = 0; maGeometry.nBottomDecoration = 0; maGeometry.nLeftDecoration = 0; maGeometry.nRightDecoration = 0; } updateScreenNumber(); SetIcon(SV_ICON_ID_OFFICE); } GtkSalFrame *GtkSalFrame::getFromWindow( GtkWidget *pWindow ) { return static_cast(g_object_get_data( G_OBJECT( pWindow ), "SalFrame" )); } void GtkSalFrame::DisallowCycleFocusOut() { if (!m_nSetFocusSignalId) return; // don't enable/disable can-focus as control enters and leaves // embedded native gtk widgets g_signal_handler_disconnect(G_OBJECT(m_pWindow), m_nSetFocusSignalId); m_nSetFocusSignalId = 0; #if !GTK_CHECK_VERSION(4, 0, 0) // gtk3: set container without can-focus and focus will tab between // the native embedded widgets using the default gtk handling for // that // gtk4: no need because the native widgets are the only // thing in the overlay and the drawing widget is underneath so // the natural focus cycle is sufficient gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), false); #endif } bool GtkSalFrame::IsCycleFocusOutDisallowed() const { return m_nSetFocusSignalId == 0; } void GtkSalFrame::AllowCycleFocusOut() { if (m_nSetFocusSignalId) return; #if !GTK_CHECK_VERSION(4,0,0) // enable/disable can-focus as control enters and leaves // embedded native gtk widgets m_nSetFocusSignalId = g_signal_connect(G_OBJECT(m_pWindow), "set-focus", G_CALLBACK(signalSetFocus), this); #else m_nSetFocusSignalId = g_signal_connect(G_OBJECT(m_pWindow), "notify::focus-widget", G_CALLBACK(signalSetFocus), this); #endif #if !GTK_CHECK_VERSION(4, 0, 0) // set container without can-focus and focus will tab between // the native embedded widgets using the default gtk handling for // that // gtk4: no need because the native widgets are the only // thing in the overlay and the drawing widget is underneath so // the natural focus cycle is sufficient gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), true); #endif } void GtkSalFrame::Init( SalFrame* pParent, SalFrameStyleFlags nStyle ) { if( nStyle & SalFrameStyleFlags::DEFAULT ) // ensure default style { nStyle |= SalFrameStyleFlags::MOVEABLE | SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::CLOSEABLE; nStyle &= ~SalFrameStyleFlags::FLOAT; } m_pParent = static_cast(pParent); #if !GTK_CHECK_VERSION(4,0,0) m_pForeignParent = nullptr; m_aForeignParentWindow = None; m_pForeignTopLevel = nullptr; m_aForeignTopLevelWindow = None; #endif m_nStyle = nStyle; bool bPopup = ((nStyle & SalFrameStyleFlags::FLOAT) && !(nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION)); if( nStyle & SalFrameStyleFlags::SYSTEMCHILD ) { #if !GTK_CHECK_VERSION(4,0,0) m_pWindow = gtk_event_box_new(); #else m_pWindow = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); #endif if( m_pParent ) { // insert into container gtk_fixed_put( m_pParent->getFixedContainer(), m_pWindow, 0, 0 ); } } else { #if !GTK_CHECK_VERSION(4,0,0) m_pWindow = gtk_window_new(bPopup ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL); #else m_pWindow = gtk_window_new(); #endif #if !GTK_CHECK_VERSION(4,0,0) // hook up F1 to show help for embedded native gtk widgets GtkAccelGroup *pGroup = gtk_accel_group_new(); GClosure* closure = g_cclosure_new(G_CALLBACK(GtkSalFrame::NativeWidgetHelpPressed), GTK_WINDOW(m_pWindow), nullptr); gtk_accel_group_connect(pGroup, GDK_KEY_F1, static_cast(0), GTK_ACCEL_LOCKED, closure); gtk_window_add_accel_group(GTK_WINDOW(m_pWindow), pGroup); #endif } g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", this ); g_object_set_data( G_OBJECT( m_pWindow ), "libo-version", const_cast(LIBO_VERSION_DOTTED)); // force wm class hint if (!isChild()) { if (m_pParent) m_sWMClass = m_pParent->m_sWMClass; updateWMClass(); } if (GTK_IS_WINDOW(m_pWindow)) { if (m_pParent) { GtkWidget* pTopLevel = widget_get_toplevel(m_pParent->m_pWindow); #if !GTK_CHECK_VERSION(4,0,0) if (!isChild()) gtk_window_set_screen(GTK_WINDOW(m_pWindow), gtk_widget_get_screen(pTopLevel)); #endif if (!(m_pParent->m_nStyle & SalFrameStyleFlags::PLUG)) gtk_window_set_transient_for(GTK_WINDOW(m_pWindow), GTK_WINDOW(pTopLevel)); m_pParent->m_aChildren.push_back( this ); gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pTopLevel)), GTK_WINDOW(m_pWindow)); } else { gtk_window_group_add_window(gtk_window_group_new(), GTK_WINDOW(m_pWindow)); g_object_unref(gtk_window_get_group(GTK_WINDOW(m_pWindow))); } } // set window type bool bDecoHandling = ! isChild() && ( ! (nStyle & SalFrameStyleFlags::FLOAT) || (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) ); if( bDecoHandling ) { #if !GTK_CHECK_VERSION(4,0,0) GdkWindowTypeHint eType = GDK_WINDOW_TYPE_HINT_NORMAL; if( (nStyle & SalFrameStyleFlags::DIALOG) && m_pParent != nullptr ) eType = GDK_WINDOW_TYPE_HINT_DIALOG; #endif if( nStyle & SalFrameStyleFlags::INTRO ) { #if !GTK_CHECK_VERSION(4,0,0) gtk_window_set_role( GTK_WINDOW(m_pWindow), "splashscreen" ); eType = GDK_WINDOW_TYPE_HINT_SPLASHSCREEN; #endif } else if( nStyle & SalFrameStyleFlags::TOOLWINDOW ) { #if !GTK_CHECK_VERSION(4,0,0) eType = GDK_WINDOW_TYPE_HINT_DIALOG; gtk_window_set_skip_taskbar_hint( GTK_WINDOW(m_pWindow), true ); #endif } else if( nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION ) { #if !GTK_CHECK_VERSION(4,0,0) eType = GDK_WINDOW_TYPE_HINT_TOOLBAR; gtk_window_set_focus_on_map(GTK_WINDOW(m_pWindow), false); #endif gtk_window_set_decorated(GTK_WINDOW(m_pWindow), false); } #if !GTK_CHECK_VERSION(4,0,0) gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), eType ); gtk_window_set_gravity( GTK_WINDOW(m_pWindow), GDK_GRAVITY_STATIC ); #endif gtk_window_set_resizable( GTK_WINDOW(m_pWindow), bool(nStyle & SalFrameStyleFlags::SIZEABLE) ); #if !GTK_CHECK_VERSION(4,0,0) #if defined(GDK_WINDOWING_WAYLAND) //rhbz#1392145 under wayland/csd if we've overridden the default widget direction in order to set LibreOffice's //UI to the configured ui language but the system ui locale is a different text direction, then the toplevel //built-in close button of the titlebar follows the overridden direction rather than continue in the same //direction as every other titlebar on the user's desktop. So if they don't match set an explicit //header bar with the desired 'outside' direction if ((eType == GDK_WINDOW_TYPE_HINT_NORMAL || eType == GDK_WINDOW_TYPE_HINT_DIALOG) && DLSYM_GDK_IS_WAYLAND_DISPLAY(GtkSalFrame::getGdkDisplay())) { const bool bDesktopIsRTL = MsLangId::isRightToLeft(MsLangId::getConfiguredSystemUILanguage()); const bool bAppIsRTL = gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL; if (bDesktopIsRTL != bAppIsRTL) { m_pHeaderBar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_set_direction(GTK_WIDGET(m_pHeaderBar), bDesktopIsRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR); gtk_header_bar_set_show_close_button(m_pHeaderBar, true); gtk_window_set_titlebar(GTK_WINDOW(m_pWindow), GTK_WIDGET(m_pHeaderBar)); gtk_widget_show(GTK_WIDGET(m_pHeaderBar)); } } #endif #endif } #if !GTK_CHECK_VERSION(4,0,0) else if( nStyle & SalFrameStyleFlags::FLOAT ) gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), GDK_WINDOW_TYPE_HINT_POPUP_MENU ); #endif InitCommon(); if (!bPopup) { // Enable GMenuModel native menu attach_menu_model(this); } } #if !GTK_CHECK_VERSION(4,0,0) GdkNativeWindow GtkSalFrame::findTopLevelSystemWindow( GdkNativeWindow ) { //FIXME: no findToplevelSystemWindow return 0; } #endif void GtkSalFrame::Init( SystemParentData* pSysData ) { m_pParent = nullptr; #if !GTK_CHECK_VERSION(4,0,0) m_aForeignParentWindow = pSysData->aWindow; m_pForeignParent = nullptr; m_aForeignTopLevelWindow = findTopLevelSystemWindow(pSysData->aWindow); m_pForeignTopLevel = gdk_x11_window_foreign_new_for_display( getGdkDisplay(), m_aForeignTopLevelWindow ); gdk_window_set_events( m_pForeignTopLevel, GDK_STRUCTURE_MASK ); if( pSysData->nSize > sizeof(pSysData->nSize)+sizeof(pSysData->aWindow) && pSysData->bXEmbedSupport ) { m_pWindow = gtk_plug_new_for_display( getGdkDisplay(), pSysData->aWindow ); gtk_widget_set_can_default(m_pWindow, true); gtk_widget_set_can_focus(m_pWindow, true); gtk_widget_set_sensitive(m_pWindow, true); } else { m_pWindow = gtk_window_new( GTK_WINDOW_POPUP ); } #endif m_nStyle = SalFrameStyleFlags::PLUG; InitCommon(); #if !GTK_CHECK_VERSION(4,0,0) m_pForeignParent = gdk_x11_window_foreign_new_for_display( getGdkDisplay(), m_aForeignParentWindow ); gdk_window_set_events( m_pForeignParent, GDK_STRUCTURE_MASK ); #else (void)pSysData; #endif //FIXME: Handling embedded windows, is going to be fun ... } void GtkSalFrame::SetExtendedFrameStyle(SalExtStyle) { } SalGraphics* GtkSalFrame::AcquireGraphics() { if( m_bGraphics ) return nullptr; if( !m_pGraphics ) { m_pGraphics.reset( new GtkSalGraphics( this, m_pWindow ) ); if (!m_pSurface) { AllocateFrame(); TriggerPaintEvent(); } m_pGraphics->setSurface(m_pSurface, m_aFrameSize); } m_bGraphics = true; return m_pGraphics.get(); } void GtkSalFrame::ReleaseGraphics( SalGraphics* pGraphics ) { (void) pGraphics; assert( pGraphics == m_pGraphics.get() ); m_bGraphics = false; } bool GtkSalFrame::PostEvent(std::unique_ptr pData) { getDisplay()->SendInternalEvent( this, pData.release() ); return true; } void GtkSalFrame::SetTitle( const OUString& rTitle ) { if( m_pWindow && ! isChild() ) { OString sTitle(OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8)); gtk_window_set_title(GTK_WINDOW(m_pWindow), sTitle.getStr()); #if !GTK_CHECK_VERSION(4,0,0) if (m_pHeaderBar) gtk_header_bar_set_title(m_pHeaderBar, sTitle.getStr()); #endif } } void GtkSalFrame::SetIcon(const char* appicon) { gtk_window_set_icon_name(GTK_WINDOW(m_pWindow), appicon); #if defined(GDK_WINDOWING_WAYLAND) if (!DLSYM_GDK_IS_WAYLAND_DISPLAY(getGdkDisplay())) return; #if GTK_CHECK_VERSION(4,0,0) GdkSurface* gdkWindow = gtk_native_get_surface(gtk_widget_get_native(m_pWindow)); gdk_wayland_toplevel_set_application_id((GDK_TOPLEVEL(gdkWindow)), appicon); #else static auto set_application_id = reinterpret_cast( dlsym(nullptr, "gdk_wayland_window_set_application_id")); if (set_application_id) { GdkSurface* gdkWindow = widget_get_surface(m_pWindow); set_application_id(gdkWindow, appicon); } #endif // gdk_wayland_window_set_application_id doesn't seem to work before // the window is mapped, so set this for real when/if we are mapped m_bIconSetWhileUnmapped = !gtk_widget_get_mapped(m_pWindow); #endif } void GtkSalFrame::SetIcon( sal_uInt16 nIcon ) { if( (m_nStyle & (SalFrameStyleFlags::PLUG|SalFrameStyleFlags::SYSTEMCHILD|SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::INTRO|SalFrameStyleFlags::OWNERDRAWDECORATION)) || ! m_pWindow ) return; gchar* appicon; if (nIcon == SV_ICON_ID_TEXT) appicon = g_strdup ("libreoffice-writer"); else if (nIcon == SV_ICON_ID_SPREADSHEET) appicon = g_strdup ("libreoffice-calc"); else if (nIcon == SV_ICON_ID_DRAWING) appicon = g_strdup ("libreoffice-draw"); else if (nIcon == SV_ICON_ID_PRESENTATION) appicon = g_strdup ("libreoffice-impress"); else if (nIcon == SV_ICON_ID_DATABASE) appicon = g_strdup ("libreoffice-base"); else if (nIcon == SV_ICON_ID_FORMULA) appicon = g_strdup ("libreoffice-math"); else appicon = g_strdup ("libreoffice-startcenter"); SetIcon(appicon); g_free (appicon); } void GtkSalFrame::SetMenu( SalMenu* pSalMenu ) { m_pSalMenu = static_cast(pSalMenu); } SalMenu* GtkSalFrame::GetMenu() { return m_pSalMenu; } void GtkSalFrame::DrawMenuBar() { } void GtkSalFrame::Center() { if (!GTK_IS_WINDOW(m_pWindow)) return; #if !GTK_CHECK_VERSION(4,0,0) if (m_pParent) gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER_ON_PARENT); else gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER); #endif } Size GtkSalFrame::calcDefaultSize() { Size aScreenSize(getDisplay()->GetScreenSize(GetDisplayScreen())); int scale = gtk_widget_get_scale_factor(m_pWindow); aScreenSize.setWidth( aScreenSize.Width() / scale ); aScreenSize.setHeight( aScreenSize.Height() / scale ); return bestmaxFrameSizeForScreenSize(aScreenSize); } void GtkSalFrame::SetDefaultSize() { Size aDefSize = calcDefaultSize(); SetPosSize( 0, 0, aDefSize.Width(), aDefSize.Height(), SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT ); if( (m_nStyle & SalFrameStyleFlags::DEFAULT) && m_pWindow ) gtk_window_maximize( GTK_WINDOW(m_pWindow) ); } void GtkSalFrame::Show( bool bVisible, bool /*bNoActivate*/ ) { if( !m_pWindow ) return; if( bVisible ) { getDisplay()->startupNotificationCompleted(); if( m_bDefaultPos ) Center(); if( m_bDefaultSize ) SetDefaultSize(); setMinMaxSize(); if (isFloatGrabWindow() && !getDisplay()->GetCaptureFrame()) { m_pParent->grabPointer(true, true, true); m_pParent->addGrabLevel(); } #if defined(GDK_WINDOWING_WAYLAND) /* rhbz#1334915, gnome#779143, tdf#100158 https://gitlab.gnome.org/GNOME/gtk/-/issues/767 before gdk_wayland_window_set_application_id was available gtk under wayland lacked a way to change the app_id of a window, so brute force everything as a startcenter when initially shown to at least get the default LibreOffice icon and not the broken app icon */ static bool bAppIdImmutable = DLSYM_GDK_IS_WAYLAND_DISPLAY(getGdkDisplay()) && !dlsym(nullptr, "gdk_wayland_window_set_application_id"); if (bAppIdImmutable) { OString sOrigName(g_get_prgname()); g_set_prgname("libreoffice-startcenter"); gtk_widget_show(m_pWindow); g_set_prgname(sOrigName.getStr()); } else { gtk_widget_show(m_pWindow); } #else gtk_widget_show(m_pWindow); #endif if( isFloatGrabWindow() ) { m_nFloats++; if (!getDisplay()->GetCaptureFrame()) { grabPointer(true, true, true); addGrabLevel(); } // #i44068# reset parent's IM context if( m_pParent ) m_pParent->EndExtTextInput(EndExtTextInputFlags::NONE); } } else { if( isFloatGrabWindow() ) { m_nFloats--; if (!getDisplay()->GetCaptureFrame()) { removeGrabLevel(); grabPointer(false, true, false); m_pParent->removeGrabLevel(); bool bParentIsFloatGrabWindow = m_pParent->isFloatGrabWindow(); m_pParent->grabPointer(bParentIsFloatGrabWindow, true, bParentIsFloatGrabWindow); } } gtk_widget_hide( m_pWindow ); if( m_pIMHandler ) m_pIMHandler->focusChanged( false ); } } void GtkSalFrame::setMinMaxSize() { /* #i34504# metacity (and possibly others) do not treat * _NET_WM_STATE_FULLSCREEN and max_width/height independently; * whether they should is undefined. So don't set the max size hint * for a full screen window. */ if( !m_pWindow || isChild() ) return; #if !GTK_CHECK_VERSION(4, 0, 0) GdkGeometry aGeo; int aHints = 0; if( m_nStyle & SalFrameStyleFlags::SIZEABLE ) { if( m_aMinSize.Width() && m_aMinSize.Height() && ! m_bFullscreen ) { aGeo.min_width = m_aMinSize.Width(); aGeo.min_height = m_aMinSize.Height(); aHints |= GDK_HINT_MIN_SIZE; } if( m_aMaxSize.Width() && m_aMaxSize.Height() && ! m_bFullscreen ) { aGeo.max_width = m_aMaxSize.Width(); aGeo.max_height = m_aMaxSize.Height(); aHints |= GDK_HINT_MAX_SIZE; } } else { if (!m_bFullscreen && m_nWidthRequest && m_nHeightRequest) { aGeo.min_width = m_nWidthRequest; aGeo.min_height = m_nHeightRequest; aHints |= GDK_HINT_MIN_SIZE; aGeo.max_width = m_nWidthRequest; aGeo.max_height = m_nHeightRequest; aHints |= GDK_HINT_MAX_SIZE; } } if( m_bFullscreen && m_aMaxSize.Width() && m_aMaxSize.Height() ) { aGeo.max_width = m_aMaxSize.Width(); aGeo.max_height = m_aMaxSize.Height(); aHints |= GDK_HINT_MAX_SIZE; } if( aHints ) { gtk_window_set_geometry_hints( GTK_WINDOW(m_pWindow), nullptr, &aGeo, GdkWindowHints( aHints ) ); } #endif } void GtkSalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight ) { if( ! isChild() ) { m_aMaxSize = Size( nWidth, nHeight ); setMinMaxSize(); } } void GtkSalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight ) { if( ! isChild() ) { m_aMinSize = Size( nWidth, nHeight ); if( m_pWindow ) { widget_set_size_request(nWidth, nHeight); setMinMaxSize(); } } } void GtkSalFrame::AllocateFrame() { basegfx::B2IVector aFrameSize( maGeometry.nWidth, maGeometry.nHeight ); if (m_pSurface && m_aFrameSize.getX() == aFrameSize.getX() && m_aFrameSize.getY() == aFrameSize.getY() ) return; if( aFrameSize.getX() == 0 ) aFrameSize.setX( 1 ); if( aFrameSize.getY() == 0 ) aFrameSize.setY( 1 ); if (m_pSurface) cairo_surface_destroy(m_pSurface); m_pSurface = surface_create_similar_surface(widget_get_surface(m_pWindow), CAIRO_CONTENT_COLOR_ALPHA, aFrameSize.getX(), aFrameSize.getY()); m_aFrameSize = aFrameSize; cairo_surface_set_user_data(m_pSurface, SvpSalGraphics::getDamageKey(), &m_aDamageHandler, nullptr); SAL_INFO("vcl.gtk3", "allocated Frame size of " << maGeometry.nWidth << " x " << maGeometry.nHeight); if (m_pGraphics) m_pGraphics->setSurface(m_pSurface, m_aFrameSize); } void GtkSalFrame::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags ) { if( !m_pWindow || isChild( true, false ) ) return; if( (nFlags & ( SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT )) && (nWidth > 0 && nHeight > 0 ) // sometimes stupid things happen ) { m_bDefaultSize = false; // tdf#131031 Just setting maGeometry.nWidth/nHeight will delete // the evtl. implicitly existing space at top for the gtk native MenuBar, // will make the Window too big and the StatusBar at the bottom vanish // and thus breaks the fix in tdf#130841. const int nImplicitMenuBarHeight(m_pSalMenu ? m_pSalMenu->GetMenuBarHeight() : 0); maGeometry.nWidth = nWidth; maGeometry.nHeight = nHeight - nImplicitMenuBarHeight; if( isChild( false ) ) widget_set_size_request(nWidth, nHeight); else if( ! ( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED ) ) window_resize(nWidth, nHeight); setMinMaxSize(); } else if( m_bDefaultSize ) SetDefaultSize(); m_bDefaultSize = false; if( nFlags & ( SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ) ) { if( m_pParent ) { if( AllSettings::GetLayoutRTL() ) nX = m_pParent->maGeometry.nWidth-m_nWidthRequest-1-nX; nX += m_pParent->maGeometry.nX; nY += m_pParent->maGeometry.nY; } if (nFlags & SAL_FRAME_POSSIZE_X) maGeometry.nX = nX; if (nFlags & SAL_FRAME_POSSIZE_Y) maGeometry.nY = nY; m_bGeometryIsProvisional = true; m_bDefaultPos = false; moveWindow(maGeometry.nX, maGeometry.nY); updateScreenNumber(); } else if( m_bDefaultPos ) Center(); m_bDefaultPos = false; } void GtkSalFrame::GetClientSize( tools::Long& rWidth, tools::Long& rHeight ) { if( m_pWindow && !(m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) ) { rWidth = maGeometry.nWidth; rHeight = maGeometry.nHeight; } else rWidth = rHeight = 0; } void GtkSalFrame::GetWorkArea( tools::Rectangle& rRect ) { GdkRectangle aRect; #if !GTK_CHECK_VERSION(4, 0, 0) GdkScreen *pScreen = gtk_widget_get_screen(m_pWindow); tools::Rectangle aRetRect; int max = gdk_screen_get_n_monitors (pScreen); for (int i = 0; i < max; ++i) { gdk_screen_get_monitor_workarea(pScreen, i, &aRect); tools::Rectangle aMonitorRect(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height); aRetRect.Union(aMonitorRect); } rRect = aRetRect; #else GdkDisplay* pDisplay = gtk_widget_get_display(m_pWindow); GdkSurface* gdkWindow = widget_get_surface(m_pWindow); GdkMonitor* pMonitor = gdk_display_get_monitor_at_surface(pDisplay, gdkWindow); gdk_monitor_get_geometry(pMonitor, &aRect); rRect = tools::Rectangle(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height); #endif } SalFrame* GtkSalFrame::GetParent() const { return m_pParent; } void GtkSalFrame::SetWindowState( const SalFrameState* pState ) { if( ! m_pWindow || ! pState || isChild( true, false ) ) return; const WindowStateMask nMaxGeometryMask = WindowStateMask::X | WindowStateMask::Y | WindowStateMask::Width | WindowStateMask::Height | WindowStateMask::MaximizedX | WindowStateMask::MaximizedY | WindowStateMask::MaximizedWidth | WindowStateMask::MaximizedHeight; if( (pState->mnMask & WindowStateMask::State) && ! ( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED ) && (pState->mnState & WindowStateState::Maximized) && (pState->mnMask & nMaxGeometryMask) == nMaxGeometryMask ) { resizeWindow( pState->mnWidth, pState->mnHeight ); moveWindow( pState->mnX, pState->mnY ); m_bDefaultPos = m_bDefaultSize = false; updateScreenNumber(); m_nState = GdkToplevelState(m_nState | GDK_TOPLEVEL_STATE_MAXIMIZED); m_aRestorePosSize = tools::Rectangle( Point( pState->mnX, pState->mnY ), Size( pState->mnWidth, pState->mnHeight ) ); } else if( pState->mnMask & (WindowStateMask::X | WindowStateMask::Y | WindowStateMask::Width | WindowStateMask::Height ) ) { sal_uInt16 nPosSizeFlags = 0; tools::Long nX = pState->mnX - (m_pParent ? m_pParent->maGeometry.nX : 0); tools::Long nY = pState->mnY - (m_pParent ? m_pParent->maGeometry.nY : 0); if( pState->mnMask & WindowStateMask::X ) nPosSizeFlags |= SAL_FRAME_POSSIZE_X; else nX = maGeometry.nX - (m_pParent ? m_pParent->maGeometry.nX : 0); if( pState->mnMask & WindowStateMask::Y ) nPosSizeFlags |= SAL_FRAME_POSSIZE_Y; else nY = maGeometry.nY - (m_pParent ? m_pParent->maGeometry.nY : 0); if( pState->mnMask & WindowStateMask::Width ) nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH; if( pState->mnMask & WindowStateMask::Height ) nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT; SetPosSize( nX, nY, pState->mnWidth, pState->mnHeight, nPosSizeFlags ); } if( pState->mnMask & WindowStateMask::State && ! isChild() ) { if( pState->mnState & WindowStateState::Maximized ) gtk_window_maximize( GTK_WINDOW(m_pWindow) ); else gtk_window_unmaximize( GTK_WINDOW(m_pWindow) ); /* #i42379# there is no rollup state in GDK; and rolled up windows are * (probably depending on the WM) reported as iconified. If we iconify a * window here that was e.g. a dialog, then it will be unmapped but still * not be displayed in the task list, so it's an iconified window that * the user cannot get out of this state. So do not set the iconified state * on windows with a parent (that is transient frames) since these tend * to not be represented in an icon task list. */ bool bMinimize = pState->mnState & WindowStateState::Minimized && !m_pParent; #if GTK_CHECK_VERSION(4, 0, 0) if (bMinimize) gtk_window_minimize(GTK_WINDOW(m_pWindow)); else gtk_window_unminimize(GTK_WINDOW(m_pWindow)); #else if (bMinimize) gtk_window_iconify(GTK_WINDOW(m_pWindow)); else gtk_window_deiconify(GTK_WINDOW(m_pWindow)); #endif } TriggerPaintEvent(); } namespace { void GetPosAndSize(GtkWindow *pWindow, tools::Long& rX, tools::Long &rY, tools::Long &rWidth, tools::Long &rHeight) { gint width, height; #if !GTK_CHECK_VERSION(4, 0, 0) gint root_x, root_y; gtk_window_get_position(GTK_WINDOW(pWindow), &root_x, &root_y); rX = root_x; rY = root_y; gtk_window_get_size(GTK_WINDOW(pWindow), &width, &height); #else rX = 0; rY = 0; gtk_window_get_default_size(GTK_WINDOW(pWindow), &width, &height); #endif rWidth = width; rHeight = height; } tools::Rectangle GetPosAndSize(GtkWindow *pWindow) { tools::Long nX, nY, nWidth, nHeight; GetPosAndSize(pWindow, nX, nY, nWidth, nHeight); return tools::Rectangle(nX, nY, nX + nWidth, nY + nHeight); } } bool GtkSalFrame::GetWindowState( SalFrameState* pState ) { pState->mnState = WindowStateState::Normal; pState->mnMask = WindowStateMask::State; // rollup ? gtk 2.2 does not seem to support the shaded state if( m_nState & GDK_TOPLEVEL_STATE_MINIMIZED ) pState->mnState |= WindowStateState::Minimized; if( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED ) { pState->mnState |= WindowStateState::Maximized; pState->mnX = m_aRestorePosSize.Left(); pState->mnY = m_aRestorePosSize.Top(); pState->mnWidth = m_aRestorePosSize.GetWidth(); pState->mnHeight = m_aRestorePosSize.GetHeight(); GetPosAndSize(GTK_WINDOW(m_pWindow), pState->mnMaximizedX, pState->mnMaximizedY, pState->mnMaximizedWidth, pState->mnMaximizedHeight); pState->mnMask |= WindowStateMask::MaximizedX | WindowStateMask::MaximizedY | WindowStateMask::MaximizedWidth | WindowStateMask::MaximizedHeight; } else { GetPosAndSize(GTK_WINDOW(m_pWindow), pState->mnX, pState->mnY, pState->mnWidth, pState->mnHeight); } pState->mnMask |= WindowStateMask::X | WindowStateMask::Y | WindowStateMask::Width | WindowStateMask::Height; return true; } void GtkSalFrame::SetScreen( unsigned int nNewScreen, SetType eType, tools::Rectangle const *pSize ) { if( !m_pWindow ) return; if (maGeometry.nDisplayScreenNumber == nNewScreen && eType == SetType::RetainSize) return; #if !GTK_CHECK_VERSION(4, 0, 0) int nX = maGeometry.nX, nY = maGeometry.nY, nWidth = maGeometry.nWidth, nHeight = maGeometry.nHeight; GdkScreen *pScreen = nullptr; GdkRectangle aNewMonitor; bool bSpanAllScreens = nNewScreen == static_cast(-1); bool bSpanMonitorsWhenFullscreen = bSpanAllScreens && getDisplay()->getSystem()->GetDisplayScreenCount() > 1; gint nMonitor = -1; if (bSpanMonitorsWhenFullscreen) //span all screens { pScreen = gtk_widget_get_screen( m_pWindow ); aNewMonitor.x = 0; aNewMonitor.y = 0; aNewMonitor.width = gdk_screen_get_width(pScreen); aNewMonitor.height = gdk_screen_get_height(pScreen); } else { bool bSameMonitor = false; if (!bSpanAllScreens) { pScreen = getDisplay()->getSystem()->getScreenMonitorFromIdx( nNewScreen, nMonitor ); if (!pScreen) { g_warning ("Attempt to move GtkSalFrame to invalid screen %d => " "fallback to current\n", nNewScreen); } } if (!pScreen) { pScreen = gtk_widget_get_screen( m_pWindow ); bSameMonitor = true; } // Heavy lifting, need to move screen ... if( pScreen != gtk_widget_get_screen( m_pWindow )) gtk_window_set_screen( GTK_WINDOW( m_pWindow ), pScreen ); gint nOldMonitor = gdk_screen_get_monitor_at_window( pScreen, widget_get_surface( m_pWindow ) ); if (bSameMonitor) nMonitor = nOldMonitor; #if OSL_DEBUG_LEVEL > 1 if( nMonitor == nOldMonitor ) g_warning( "An apparently pointless SetScreen - should we elide it ?" ); #endif GdkRectangle aOldMonitor; gdk_screen_get_monitor_geometry( pScreen, nOldMonitor, &aOldMonitor ); gdk_screen_get_monitor_geometry( pScreen, nMonitor, &aNewMonitor ); nX = aNewMonitor.x + nX - aOldMonitor.x; nY = aNewMonitor.y + nY - aOldMonitor.y; } bool bResize = false; bool bVisible = gtk_widget_get_mapped( m_pWindow ); if( bVisible ) Show( false ); if( eType == SetType::Fullscreen ) { nX = aNewMonitor.x; nY = aNewMonitor.y; nWidth = aNewMonitor.width; nHeight = aNewMonitor.height; m_nStyle |= SalFrameStyleFlags::PARTIAL_FULLSCREEN; bResize = true; // #i110881# for the benefit of compiz set a max size here // else setting to fullscreen fails for unknown reasons m_aMaxSize.setWidth( aNewMonitor.width ); m_aMaxSize.setHeight( aNewMonitor.height ); } if( pSize && eType == SetType::UnFullscreen ) { nX = pSize->Left(); nY = pSize->Top(); nWidth = pSize->GetWidth(); nHeight = pSize->GetHeight(); m_nStyle &= ~SalFrameStyleFlags::PARTIAL_FULLSCREEN; bResize = true; } if (bResize) { // temporarily re-sizeable if( !(m_nStyle & SalFrameStyleFlags::SIZEABLE) ) gtk_window_set_resizable( GTK_WINDOW(m_pWindow), true ); window_resize(nWidth, nHeight); } gtk_window_move(GTK_WINDOW(m_pWindow), nX, nY); GdkFullscreenMode eMode = bSpanMonitorsWhenFullscreen ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR; gdk_window_set_fullscreen_mode(widget_get_surface(m_pWindow), eMode); GtkWidget* pMenuBarContainerWidget = m_pSalMenu ? m_pSalMenu->GetMenuBarContainerWidget() : nullptr; if( eType == SetType::Fullscreen ) { if (pMenuBarContainerWidget) gtk_widget_hide(pMenuBarContainerWidget); if (bSpanMonitorsWhenFullscreen) gtk_window_fullscreen(GTK_WINDOW(m_pWindow)); else gtk_window_fullscreen_on_monitor(GTK_WINDOW(m_pWindow), pScreen, nMonitor); } else if( eType == SetType::UnFullscreen ) { if (pMenuBarContainerWidget) gtk_widget_show(pMenuBarContainerWidget); gtk_window_unfullscreen( GTK_WINDOW( m_pWindow ) ); } if( eType == SetType::UnFullscreen && !(m_nStyle & SalFrameStyleFlags::SIZEABLE) ) gtk_window_set_resizable( GTK_WINDOW( m_pWindow ), FALSE ); // FIXME: we should really let gtk+ handle our widget hierarchy ... if( m_pParent && gtk_widget_get_screen( m_pParent->m_pWindow ) != pScreen ) SetParent( nullptr ); std::list< GtkSalFrame* > aChildren = m_aChildren; for (auto const& child : aChildren) child->SetScreen( nNewScreen, SetType::RetainSize ); m_bDefaultPos = m_bDefaultSize = false; updateScreenNumber(); if( bVisible ) Show( true ); #else (void)pSize; // assume restore will restore the original size without our help bool bSpanMonitorsWhenFullscreen = nNewScreen == static_cast(-1); GdkFullscreenMode eMode = bSpanMonitorsWhenFullscreen ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR; g_object_set(widget_get_surface(m_pWindow), "fullscreen-mode", eMode, nullptr); GtkWidget* pMenuBarContainerWidget = m_pSalMenu ? m_pSalMenu->GetMenuBarContainerWidget() : nullptr; if (eType == SetType::Fullscreen) { if (!(m_nStyle & SalFrameStyleFlags::SIZEABLE)) { // temp make it resizable, restore when unfullscreened gtk_window_set_resizable(GTK_WINDOW(m_pWindow), true); } m_nStyle |= SalFrameStyleFlags::PARTIAL_FULLSCREEN; if (pMenuBarContainerWidget) gtk_widget_hide(pMenuBarContainerWidget); if (bSpanMonitorsWhenFullscreen) gtk_window_fullscreen(GTK_WINDOW(m_pWindow)); else { GdkDisplay* pDisplay = gtk_widget_get_display(m_pWindow); GListModel* pList = gdk_display_get_monitors(pDisplay); GdkMonitor* pMonitor = static_cast(g_list_model_get_item(pList, nNewScreen)); if (!pMonitor) pMonitor = gdk_display_get_monitor_at_surface(pDisplay, widget_get_surface(m_pWindow)); gtk_window_fullscreen_on_monitor(GTK_WINDOW(m_pWindow), pMonitor); } } else if (eType == SetType::UnFullscreen) { m_nStyle &= ~SalFrameStyleFlags::PARTIAL_FULLSCREEN; if (pMenuBarContainerWidget) gtk_widget_show(pMenuBarContainerWidget); gtk_window_unfullscreen(GTK_WINDOW(m_pWindow)); if (!(m_nStyle & SalFrameStyleFlags::SIZEABLE)) { // restore temp resizability gtk_window_set_resizable(GTK_WINDOW(m_pWindow), false); } } for (auto const& child : m_aChildren) child->SetScreen(nNewScreen, SetType::RetainSize); m_bDefaultPos = m_bDefaultSize = false; updateScreenNumber(); #endif } void GtkSalFrame::SetScreenNumber( unsigned int nNewScreen ) { SetScreen( nNewScreen, SetType::RetainSize ); } void GtkSalFrame::updateWMClass() { if (!DLSYM_GDK_IS_X11_DISPLAY(getGdkDisplay())) return; if (!gtk_widget_get_realized(m_pWindow)) return; OString aResClass = OUStringToOString(m_sWMClass, RTL_TEXTENCODING_ASCII_US); const char *pResClass = !aResClass.isEmpty() ? aResClass.getStr() : SalGenericSystem::getFrameClassName(); XClassHint* pClass = XAllocClassHint(); OString aResName = SalGenericSystem::getFrameResName(); pClass->res_name = const_cast(aResName.getStr()); pClass->res_class = const_cast(pResClass); Display *display = gdk_x11_display_get_xdisplay(getGdkDisplay()); XSetClassHint( display, GtkSalFrame::GetNativeWindowHandle(m_pWindow), pClass ); XFree( pClass ); } void GtkSalFrame::SetApplicationID( const OUString &rWMClass ) { if( rWMClass != m_sWMClass && ! isChild() ) { m_sWMClass = rWMClass; updateWMClass(); for (auto const& child : m_aChildren) child->SetApplicationID(rWMClass); } } void GtkSalFrame::ShowFullScreen( bool bFullScreen, sal_Int32 nScreen ) { m_bFullscreen = bFullScreen; if( !m_pWindow || isChild() ) return; if( bFullScreen ) { m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(m_pWindow)); SetScreen( nScreen, SetType::Fullscreen ); } else { SetScreen( nScreen, SetType::UnFullscreen, !m_aRestorePosSize.IsEmpty() ? &m_aRestorePosSize : nullptr ); m_aRestorePosSize = tools::Rectangle(); } } void GtkSalFrame::StartPresentation( bool bStart ) { std::optional aWindow; std::optional aDisplay; bool bX11 = DLSYM_GDK_IS_X11_DISPLAY(getGdkDisplay()); if (bX11) { aWindow = GtkSalFrame::GetNativeWindowHandle(m_pWindow); aDisplay = gdk_x11_display_get_xdisplay(getGdkDisplay()); } m_ScreenSaverInhibitor.inhibit( bStart, u"presentation", bX11, aWindow, aDisplay ); } void GtkSalFrame::SetAlwaysOnTop( bool bOnTop ) { #if !GTK_CHECK_VERSION(4, 0, 0) if( m_pWindow ) gtk_window_set_keep_above( GTK_WINDOW( m_pWindow ), bOnTop ); #else (void)bOnTop; #endif } static guint32 nLastUserInputTime = GDK_CURRENT_TIME; guint32 GtkSalFrame::GetLastInputEventTime() { return nLastUserInputTime; } void GtkSalFrame::UpdateLastInputEventTime(guint32 nUserInputTime) { //gtk3 can generate a synthetic crossing event with a useless 0 //(GDK_CURRENT_TIME) timestamp on showing a menu from the main //menubar, which is unhelpful, so ignore the 0 timestamps if (nUserInputTime == GDK_CURRENT_TIME) return; nLastUserInputTime = nUserInputTime; } void GtkSalFrame::ToTop( SalFrameToTop nFlags ) { if( !m_pWindow ) return; if( isChild( false ) ) GrabFocus(); else if( gtk_widget_get_mapped( m_pWindow ) ) { auto nTimestamp = GetLastInputEventTime(); #ifdef GDK_WINDOWING_X11 GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay(); if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay)) nTimestamp = gdk_x11_display_get_user_time(pDisplay); #endif if (!(nFlags & SalFrameToTop::GrabFocusOnly)) gtk_window_present_with_time(GTK_WINDOW(m_pWindow), nTimestamp); #if !GTK_CHECK_VERSION(4, 0, 0) else gdk_window_focus(widget_get_surface(m_pWindow), nTimestamp); #endif GrabFocus(); } else { if( nFlags & SalFrameToTop::RestoreWhenMin ) gtk_window_present( GTK_WINDOW(m_pWindow) ); } } void GtkSalFrame::SetPointer( PointerStyle ePointerStyle ) { if( !m_pWindow || ePointerStyle == m_ePointerStyle ) return; m_ePointerStyle = ePointerStyle; GdkCursor *pCursor = getDisplay()->getCursor( ePointerStyle ); widget_set_cursor(GTK_WIDGET(m_pWindow), pCursor); } void GtkSalFrame::grabPointer( bool bGrab, bool bKeyboardAlso, bool bOwnerEvents ) { if (bGrab) { // tdf#135779 move focus back inside usual input window out of any // other gtk widgets before grabbing the pointer GrabFocus(); } static const char* pEnv = getenv( "SAL_NO_MOUSEGRABS" ); if (pEnv && *pEnv) return; if (!m_pWindow) return; #if !GTK_CHECK_VERSION(4, 0, 0) GdkSeat* pSeat = gdk_display_get_default_seat(getGdkDisplay()); if (bGrab) { GdkSeatCapabilities eCapability = bKeyboardAlso ? GDK_SEAT_CAPABILITY_ALL : GDK_SEAT_CAPABILITY_ALL_POINTING; gdk_seat_grab(pSeat, widget_get_surface(getMouseEventWidget()), eCapability, bOwnerEvents, nullptr, nullptr, nullptr, nullptr); } else { gdk_seat_ungrab(pSeat); } #else (void)bKeyboardAlso; (void)bOwnerEvents; #endif } void GtkSalFrame::CaptureMouse( bool bCapture ) { getDisplay()->CaptureMouse( bCapture ? this : nullptr ); } void GtkSalFrame::SetPointerPos( tools::Long nX, tools::Long nY ) { #if !GTK_CHECK_VERSION(4, 0, 0) GtkSalFrame* pFrame = this; while( pFrame && pFrame->isChild( false ) ) pFrame = pFrame->m_pParent; if( ! pFrame ) return; GdkScreen *pScreen = gtk_widget_get_screen(pFrame->m_pWindow); GdkDisplay *pDisplay = gdk_screen_get_display( pScreen ); /* when the application tries to center the mouse in the dialog the * window isn't mapped already. So use coordinates relative to the root window. */ unsigned int nWindowLeft = maGeometry.nX + nX; unsigned int nWindowTop = maGeometry.nY + nY; GdkDeviceManager* pManager = gdk_display_get_device_manager(pDisplay); gdk_device_warp(gdk_device_manager_get_client_pointer(pManager), pScreen, nWindowLeft, nWindowTop); // #i38648# ask for the next motion hint gint x, y; GdkModifierType mask; gdk_window_get_pointer( widget_get_surface(pFrame->m_pWindow) , &x, &y, &mask ); #else (void)nX; (void)nY; #endif } void GtkSalFrame::Flush() { gdk_display_flush( getGdkDisplay() ); } void GtkSalFrame::KeyCodeToGdkKey(const vcl::KeyCode& rKeyCode, guint* pGdkKeyCode, GdkModifierType *pGdkModifiers) { if ( pGdkKeyCode == nullptr || pGdkModifiers == nullptr ) return; // Get GDK key modifiers GdkModifierType nModifiers = GdkModifierType(0); if ( rKeyCode.IsShift() ) nModifiers = static_cast( nModifiers | GDK_SHIFT_MASK ); if ( rKeyCode.IsMod1() ) nModifiers = static_cast( nModifiers | GDK_CONTROL_MASK ); if ( rKeyCode.IsMod2() ) nModifiers = static_cast( nModifiers | GDK_ALT_MASK ); *pGdkModifiers = nModifiers; // Get GDK keycode. guint nKeyCode = 0; guint nCode = rKeyCode.GetCode(); if ( nCode >= KEY_0 && nCode <= KEY_9 ) nKeyCode = ( nCode - KEY_0 ) + GDK_KEY_0; else if ( nCode >= KEY_A && nCode <= KEY_Z ) nKeyCode = ( nCode - KEY_A ) + GDK_KEY_A; else if ( nCode >= KEY_F1 && nCode <= KEY_F26 ) nKeyCode = ( nCode - KEY_F1 ) + GDK_KEY_F1; else { switch (nCode) { case KEY_DOWN: nKeyCode = GDK_KEY_Down; break; case KEY_UP: nKeyCode = GDK_KEY_Up; break; case KEY_LEFT: nKeyCode = GDK_KEY_Left; break; case KEY_RIGHT: nKeyCode = GDK_KEY_Right; break; case KEY_HOME: nKeyCode = GDK_KEY_Home; break; case KEY_END: nKeyCode = GDK_KEY_End; break; case KEY_PAGEUP: nKeyCode = GDK_KEY_Page_Up; break; case KEY_PAGEDOWN: nKeyCode = GDK_KEY_Page_Down; break; case KEY_RETURN: nKeyCode = GDK_KEY_Return; break; case KEY_ESCAPE: nKeyCode = GDK_KEY_Escape; break; case KEY_TAB: nKeyCode = GDK_KEY_Tab; break; case KEY_BACKSPACE: nKeyCode = GDK_KEY_BackSpace; break; case KEY_SPACE: nKeyCode = GDK_KEY_space; break; case KEY_INSERT: nKeyCode = GDK_KEY_Insert; break; case KEY_DELETE: nKeyCode = GDK_KEY_Delete; break; case KEY_ADD: nKeyCode = GDK_KEY_plus; break; case KEY_SUBTRACT: nKeyCode = GDK_KEY_minus; break; case KEY_MULTIPLY: nKeyCode = GDK_KEY_asterisk; break; case KEY_DIVIDE: nKeyCode = GDK_KEY_slash; break; case KEY_POINT: nKeyCode = GDK_KEY_period; break; case KEY_COMMA: nKeyCode = GDK_KEY_comma; break; case KEY_LESS: nKeyCode = GDK_KEY_less; break; case KEY_GREATER: nKeyCode = GDK_KEY_greater; break; case KEY_EQUAL: nKeyCode = GDK_KEY_equal; break; case KEY_FIND: nKeyCode = GDK_KEY_Find; break; case KEY_CONTEXTMENU: nKeyCode = GDK_KEY_Menu; break; case KEY_HELP: nKeyCode = GDK_KEY_Help; break; case KEY_UNDO: nKeyCode = GDK_KEY_Undo; break; case KEY_REPEAT: nKeyCode = GDK_KEY_Redo; break; case KEY_DECIMAL: nKeyCode = GDK_KEY_KP_Decimal; break; case KEY_TILDE: nKeyCode = GDK_KEY_asciitilde; break; case KEY_QUOTELEFT: nKeyCode = GDK_KEY_quoteleft; break; case KEY_BRACKETLEFT: nKeyCode = GDK_KEY_bracketleft; break; case KEY_BRACKETRIGHT: nKeyCode = GDK_KEY_bracketright; break; case KEY_SEMICOLON: nKeyCode = GDK_KEY_semicolon; break; case KEY_QUOTERIGHT: nKeyCode = GDK_KEY_quoteright; break; // Special cases case KEY_COPY: nKeyCode = GDK_KEY_Copy; break; case KEY_CUT: nKeyCode = GDK_KEY_Cut; break; case KEY_PASTE: nKeyCode = GDK_KEY_Paste; break; case KEY_OPEN: nKeyCode = GDK_KEY_Open; break; } } *pGdkKeyCode = nKeyCode; } OUString GtkSalFrame::GetKeyName( sal_uInt16 nKeyCode ) { guint nGtkKeyCode; GdkModifierType nGtkModifiers; KeyCodeToGdkKey(nKeyCode, &nGtkKeyCode, &nGtkModifiers ); gchar* pName = gtk_accelerator_get_label(nGtkKeyCode, nGtkModifiers); OUString aRet(pName, rtl_str_getLength(pName), RTL_TEXTENCODING_UTF8); g_free(pName); return aRet; } GdkDisplay *GtkSalFrame::getGdkDisplay() { return GetGtkSalData()->GetGdkDisplay(); } GtkSalDisplay *GtkSalFrame::getDisplay() { return GetGtkSalData()->GetGtkDisplay(); } SalFrame::SalPointerState GtkSalFrame::GetPointerState() { SalPointerState aState; #if !GTK_CHECK_VERSION(4, 0, 0) GdkScreen* pScreen; gint x, y; GdkModifierType aMask; gdk_display_get_pointer( getGdkDisplay(), &pScreen, &x, &y, &aMask ); aState.maPos = Point( x - maGeometry.nX, y - maGeometry.nY ); aState.mnState = GetMouseModCode( aMask ); #endif return aState; } KeyIndicatorState GtkSalFrame::GetIndicatorState() { KeyIndicatorState nState = KeyIndicatorState::NONE; #if !GTK_CHECK_VERSION(4, 0, 0) GdkKeymap *pKeyMap = gdk_keymap_get_for_display(getGdkDisplay()); if (gdk_keymap_get_caps_lock_state(pKeyMap)) nState |= KeyIndicatorState::CAPSLOCK; if (gdk_keymap_get_num_lock_state(pKeyMap)) nState |= KeyIndicatorState::NUMLOCK; if (gdk_keymap_get_scroll_lock_state(pKeyMap)) nState |= KeyIndicatorState::SCROLLLOCK; #endif return nState; } void GtkSalFrame::SimulateKeyPress( sal_uInt16 nKeyCode ) { g_warning ("missing simulate keypress %d", nKeyCode); } void GtkSalFrame::SetInputContext( SalInputContext* pContext ) { if( ! pContext ) return; if( ! (pContext->mnOptions & InputContextFlags::Text) ) return; // create a new im context if( ! m_pIMHandler ) m_pIMHandler.reset( new IMHandler( this ) ); } void GtkSalFrame::EndExtTextInput( EndExtTextInputFlags nFlags ) { if( m_pIMHandler ) m_pIMHandler->endExtTextInput( nFlags ); } bool GtkSalFrame::MapUnicodeToKeyCode( sal_Unicode , LanguageType , vcl::KeyCode& ) { // not supported yet return false; } LanguageType GtkSalFrame::GetInputLanguage() { return LANGUAGE_DONTKNOW; } void GtkSalFrame::UpdateSettings( AllSettings& rSettings ) { if( ! m_pWindow ) return; GtkSalGraphics* pGraphics = m_pGraphics.get(); bool bFreeGraphics = false; if( ! pGraphics ) { pGraphics = static_cast(AcquireGraphics()); if ( !pGraphics ) { SAL_WARN("vcl.gtk3", "Could not get graphics - unable to update settings"); return; } bFreeGraphics = true; } pGraphics->UpdateSettings( rSettings ); if( bFreeGraphics ) ReleaseGraphics( pGraphics ); } void GtkSalFrame::Beep() { gdk_display_beep( getGdkDisplay() ); } const SystemEnvData* GtkSalFrame::GetSystemData() const { return &m_aSystemData; } void GtkSalFrame::ResolveWindowHandle(SystemEnvData& rData) const { if (!rData.pWidget) return; SAL_WARN("vcl.gtk3", "its undesirable to need the NativeWindowHandle, see tdf#139609"); rData.SetWindowHandle(GetNativeWindowHandle(static_cast(rData.pWidget))); } void GtkSalFrame::SetParent( SalFrame* pNewParent ) { GtkWindow* pWindow = GTK_IS_WINDOW(m_pWindow) ? GTK_WINDOW(m_pWindow) : nullptr; if (m_pParent) { if (pWindow && GTK_IS_WINDOW(m_pParent->m_pWindow)) gtk_window_group_remove_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), pWindow); m_pParent->m_aChildren.remove(this); } m_pParent = static_cast(pNewParent); if (m_pParent) { m_pParent->m_aChildren.push_back(this); if (pWindow && GTK_IS_WINDOW(m_pParent->m_pWindow)) gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), pWindow); } if (!isChild() && pWindow) gtk_window_set_transient_for( pWindow, (m_pParent && ! m_pParent->isChild(true,false)) ? GTK_WINDOW(m_pParent->m_pWindow) : nullptr ); } void GtkSalFrame::SetPluginParent( SystemParentData* ) { //FIXME: no SetPluginParent impl. for gtk3 } void GtkSalFrame::ResetClipRegion() { #if !GTK_CHECK_VERSION(4, 0, 0) if( m_pWindow ) gdk_window_shape_combine_region( widget_get_surface( m_pWindow ), nullptr, 0, 0 ); #endif } void GtkSalFrame::BeginSetClipRegion( sal_uInt32 ) { if( m_pRegion ) cairo_region_destroy( m_pRegion ); m_pRegion = cairo_region_create(); } void GtkSalFrame::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) { if( m_pRegion ) { GdkRectangle aRect; aRect.x = nX; aRect.y = nY; aRect.width = nWidth; aRect.height = nHeight; cairo_region_union_rectangle( m_pRegion, &aRect ); } } void GtkSalFrame::EndSetClipRegion() { #if !GTK_CHECK_VERSION(4, 0, 0) if( m_pWindow && m_pRegion ) gdk_window_shape_combine_region( widget_get_surface(m_pWindow), m_pRegion, 0, 0 ); #endif } void GtkSalFrame::PositionByToolkit(const tools::Rectangle& rRect, FloatWinPopupFlags nFlags) { if (ImplGetSVData()->maNWFData.mbCanDetermineWindowPosition) return; m_aFloatRect = rRect; m_nFloatFlags = nFlags; m_bFloatPositioned = true; } void GtkSalFrame::SetModal(bool bModal) { if (!m_pWindow) return; gtk_window_set_modal(GTK_WINDOW(m_pWindow), bModal); } bool GtkSalFrame::GetModal() const { if (!m_pWindow) return false; return gtk_window_get_modal(GTK_WINDOW(m_pWindow)); } gboolean GtkSalFrame::signalTooltipQuery(GtkWidget*, gint /*x*/, gint /*y*/, gboolean /*keyboard_mode*/, GtkTooltip *tooltip, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); if (pThis->m_aTooltip.isEmpty() || pThis->m_bTooltipBlocked) return false; gtk_tooltip_set_text(tooltip, OUStringToOString(pThis->m_aTooltip, RTL_TEXTENCODING_UTF8).getStr()); GdkRectangle aHelpArea; aHelpArea.x = pThis->m_aHelpArea.Left(); aHelpArea.y = pThis->m_aHelpArea.Top(); aHelpArea.width = pThis->m_aHelpArea.GetWidth(); aHelpArea.height = pThis->m_aHelpArea.GetHeight(); if (AllSettings::GetLayoutRTL()) aHelpArea.x = pThis->maGeometry.nWidth-aHelpArea.width-1-aHelpArea.x; gtk_tooltip_set_tip_area(tooltip, &aHelpArea); return true; } bool GtkSalFrame::ShowTooltip(const OUString& rHelpText, const tools::Rectangle& rHelpArea) { m_aTooltip = rHelpText; m_aHelpArea = rHelpArea; gtk_widget_trigger_tooltip_query(getMouseEventWidget()); return true; } void GtkSalFrame::BlockTooltip() { m_bTooltipBlocked = true; } void GtkSalFrame::UnblockTooltip() { m_bTooltipBlocked = false; } void GtkSalFrame::HideTooltip() { m_aTooltip.clear(); GtkWidget* pEventWidget = getMouseEventWidget(); gtk_widget_trigger_tooltip_query(pEventWidget); } namespace { void set_pointing_to(GtkPopover *pPopOver, vcl::Window* pParent, const tools::Rectangle& rHelpArea, const SalFrameGeometry& rGeometry) { GdkRectangle aRect; aRect.x = FloatingWindow::ImplConvertToAbsPos(pParent, rHelpArea).Left() - rGeometry.nX; aRect.y = rHelpArea.Top(); aRect.width = 1; aRect.height = 1; GtkPositionType ePos = gtk_popover_get_position(pPopOver); switch (ePos) { case GTK_POS_BOTTOM: case GTK_POS_TOP: aRect.width = rHelpArea.GetWidth(); break; case GTK_POS_RIGHT: case GTK_POS_LEFT: aRect.height = rHelpArea.GetHeight(); break; } gtk_popover_set_pointing_to(pPopOver, &aRect); } } void* GtkSalFrame::ShowPopover(const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea, QuickHelpFlags nFlags) { #if !GTK_CHECK_VERSION(4, 0, 0) GtkWidget *pWidget = gtk_popover_new(getMouseEventWidget()); #else GtkWidget *pWidget = gtk_popover_new(); gtk_widget_set_parent(pWidget, getMouseEventWidget()); #endif OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8); GtkWidget *pLabel = gtk_label_new(sUTF.getStr()); #if !GTK_CHECK_VERSION(4, 0, 0) gtk_container_add(GTK_CONTAINER(pWidget), pLabel); #else gtk_popover_set_child(GTK_POPOVER(pWidget), pLabel); #endif if (nFlags & QuickHelpFlags::Top) gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_BOTTOM); else if (nFlags & QuickHelpFlags::Bottom) gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_TOP); else if (nFlags & QuickHelpFlags::Left) gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_RIGHT); else if (nFlags & QuickHelpFlags::Right) gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_LEFT); set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry); #if !GTK_CHECK_VERSION(4, 0, 0) gtk_popover_set_modal(GTK_POPOVER(pWidget), false); #else gtk_popover_set_autohide(GTK_POPOVER(pWidget), false); #endif gtk_widget_show(pLabel); gtk_widget_show(pWidget); return pWidget; } bool GtkSalFrame::UpdatePopover(void* nId, const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea) { GtkWidget *pWidget = static_cast(nId); set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry); #if !GTK_CHECK_VERSION(4, 0, 0) GtkWidget *pLabel = gtk_bin_get_child(GTK_BIN(pWidget)); #else GtkWidget *pLabel = gtk_popover_get_child(GTK_POPOVER(pWidget)); #endif OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8); gtk_label_set_text(GTK_LABEL(pLabel), sUTF.getStr()); return true; } bool GtkSalFrame::HidePopover(void* nId) { GtkWidget *pWidget = static_cast(nId); #if !GTK_CHECK_VERSION(4, 0, 0) gtk_widget_destroy(pWidget); #else g_clear_pointer(&pWidget, gtk_widget_unparent); #endif return true; } void GtkSalFrame::addGrabLevel() { #if !GTK_CHECK_VERSION(4, 0, 0) if (m_nGrabLevel == 0) gtk_grab_add(getMouseEventWidget()); #endif ++m_nGrabLevel; } void GtkSalFrame::removeGrabLevel() { if (m_nGrabLevel > 0) { --m_nGrabLevel; #if !GTK_CHECK_VERSION(4, 0, 0) if (m_nGrabLevel == 0) gtk_grab_remove(getMouseEventWidget()); #endif } } void GtkSalFrame::closePopup() { if (!m_nFloats) return; ImplSVData* pSVData = ImplGetSVData(); if (!pSVData->mpWinData->mpFirstFloat) return; if (pSVData->mpWinData->mpFirstFloat->ImplGetFrame() != this) return; pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll); } #if !GTK_CHECK_VERSION(4, 0, 0) namespace { //tdf#117981 translate embedded video window mouse events to parent coordinates void translate_coords(GdkWindow* pSourceWindow, GtkWidget* pTargetWidget, int& rEventX, int& rEventY) { gpointer user_data=nullptr; gdk_window_get_user_data(pSourceWindow, &user_data); GtkWidget* pRealEventWidget = static_cast(user_data); if (pRealEventWidget) { gtk_coord fX(0.0), fY(0.0); gtk_widget_translate_coordinates(pRealEventWidget, pTargetWidget, rEventX, rEventY, &fX, &fY); rEventX = fX; rEventY = fY; } } } #endif void GtkSalFrame::GrabFocus() { GtkWidget* pGrabWidget; #if !GTK_CHECK_VERSION(4, 0, 0) if (GTK_IS_EVENT_BOX(m_pWindow)) pGrabWidget = GTK_WIDGET(m_pWindow); else pGrabWidget = GTK_WIDGET(m_pFixedContainer); // m_nSetFocusSignalId is 0 for the DisallowCycleFocusOut case where // we don't allow focus to enter the toplevel, but expect it to // stay in some embedded native gtk widget if (!gtk_widget_get_can_focus(pGrabWidget) && m_nSetFocusSignalId) gtk_widget_set_can_focus(pGrabWidget, true); #else pGrabWidget = GTK_WIDGET(m_pFixedContainer); #endif if (!gtk_widget_has_focus(pGrabWidget)) { gtk_widget_grab_focus(pGrabWidget); if (m_pIMHandler) m_pIMHandler->focusChanged(true); } } bool GtkSalFrame::DrawingAreaButton(SalEvent nEventType, int nEventX, int nEventY, int nButton, guint32 nTime, guint nState) { UpdateLastInputEventTime(nTime); SalMouseEvent aEvent; switch(nButton) { case 1: aEvent.mnButton = MOUSE_LEFT; break; case 2: aEvent.mnButton = MOUSE_MIDDLE; break; case 3: aEvent.mnButton = MOUSE_RIGHT; break; default: return false; } aEvent.mnTime = nTime; aEvent.mnX = nEventX; aEvent.mnY = nEventY; aEvent.mnCode = GetMouseModCode(nState); if( AllSettings::GetLayoutRTL() ) aEvent.mnX = maGeometry.nWidth-1-aEvent.mnX; CallCallbackExc(nEventType, &aEvent); return true; } #if !GTK_CHECK_VERSION(4, 0, 0) gboolean GtkSalFrame::signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); GtkWidget* pEventWidget = pThis->getMouseEventWidget(); bool bDifferentEventWindow = pEvent->window != widget_get_surface(pEventWidget); if (pEvent->type == GDK_BUTTON_PRESS) { // tdf#120764 It isn't allowed under wayland to have two visible popups that share // the same top level parent. The problem is that since gtk 3.24 tooltips are also // implemented as popups, which means that we cannot show any popup if there is a // visible tooltip. In fact, gtk will hide the tooltip by itself after this handler, // in case of a button press event. But if we intend to show a popup on button press // it will be too late, so just do it here: pThis->HideTooltip(); // focus on click if (!bDifferentEventWindow) pThis->GrabFocus(); } SalEvent nEventType = SalEvent::NONE; switch( pEvent->type ) { case GDK_BUTTON_PRESS: nEventType = SalEvent::MouseButtonDown; break; case GDK_BUTTON_RELEASE: nEventType = SalEvent::MouseButtonUp; break; default: return false; } vcl::DeletionListener aDel( pThis ); if (pThis->isFloatGrabWindow()) { //rhbz#1505379 if the window that got the event isn't our one, or there's none //of our windows under the mouse then close this popup window if (bDifferentEventWindow || gdk_device_get_window_at_position(pEvent->device, nullptr, nullptr) == nullptr) { if (pEvent->type == GDK_BUTTON_PRESS) pThis->closePopup(); else if (pEvent->type == GDK_BUTTON_RELEASE) return true; } } int nEventX = pEvent->x; int nEventY = pEvent->y; if (bDifferentEventWindow) translate_coords(pEvent->window, pEventWidget, nEventX, nEventY); if (!aDel.isDeleted()) { int frame_x = static_cast(pEvent->x_root - nEventX); int frame_y = static_cast(pEvent->y_root - nEventY); if (pThis->m_bGeometryIsProvisional || frame_x != pThis->maGeometry.nX || frame_y != pThis->maGeometry.nY) { pThis->m_bGeometryIsProvisional = false; pThis->maGeometry.nX = frame_x; pThis->maGeometry.nY = frame_y; ImplSVData* pSVData = ImplGetSVData(); if (pSVData->maNWFData.mbCanDetermineWindowPosition) pThis->CallCallbackExc(SalEvent::Move, nullptr); } } bool bRet = false; if (!aDel.isDeleted()) { bRet = pThis->DrawingAreaButton(nEventType, pEvent->x_root - pThis->maGeometry.nX, pEvent->y_root - pThis->maGeometry.nY, pEvent->button, pEvent->time, pEvent->state); } return bRet; } #else void GtkSalFrame::gesturePressed(GtkGestureClick* pGesture, int /*n_press*/, gdouble x, gdouble y, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); pThis->gestureButton(pGesture, SalEvent::MouseButtonDown, x, y); } void GtkSalFrame::gestureReleased(GtkGestureClick* pGesture, int /*n_press*/, gdouble x, gdouble y, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); pThis->gestureButton(pGesture, SalEvent::MouseButtonUp, x, y); } void GtkSalFrame::gestureButton(GtkGestureClick* pGesture, SalEvent nEventType, gdouble x, gdouble y) { GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pGesture)); GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pGesture)); int nButton = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(pGesture)); DrawingAreaButton(nEventType, x, y, nButton, gdk_event_get_time(pEvent), eType); } #endif #if !GTK_CHECK_VERSION(4, 0, 0) void GtkSalFrame::LaunchAsyncScroll(GdkEvent const * pEvent) { //if we don't match previous pending states, flush that queue now if (!m_aPendingScrollEvents.empty() && pEvent->scroll.state != m_aPendingScrollEvents.back()->scroll.state) { m_aSmoothScrollIdle.Stop(); m_aSmoothScrollIdle.Invoke(); assert(m_aPendingScrollEvents.empty()); } //add scroll event to queue m_aPendingScrollEvents.push_back(gdk_event_copy(pEvent)); if (!m_aSmoothScrollIdle.IsActive()) m_aSmoothScrollIdle.Start(); } #endif void GtkSalFrame::DrawingAreaScroll(double delta_x, double delta_y, int nEventX, int nEventY, guint32 nTime, guint nState) { SalWheelMouseEvent aEvent; aEvent.mnTime = nTime; aEvent.mnX = nEventX; // --- RTL --- (mirror mouse pos) if (AllSettings::GetLayoutRTL()) aEvent.mnX = maGeometry.nWidth - 1 - aEvent.mnX; aEvent.mnY = nEventY; aEvent.mnCode = GetMouseModCode(nState); // rhbz#1344042 "Traditionally" in gtk3 we tool a single up/down event as // equating to 3 scroll lines and a delta of 120. So scale the delta here // by 120 where a single mouse wheel click is an incoming delta_x of 1 // and divide that by 40 to get the number of scroll lines if (delta_x != 0.0) { aEvent.mnDelta = -delta_x * 120; aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1; if (aEvent.mnDelta == 0) aEvent.mnDelta = aEvent.mnNotchDelta; aEvent.mbHorz = true; aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0; CallCallbackExc(SalEvent::WheelMouse, &aEvent); } if (delta_y != 0.0) { aEvent.mnDelta = -delta_y * 120; aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1; if (aEvent.mnDelta == 0) aEvent.mnDelta = aEvent.mnNotchDelta; aEvent.mbHorz = false; aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0; CallCallbackExc(SalEvent::WheelMouse, &aEvent); } } #if !GTK_CHECK_VERSION(4, 0, 0) IMPL_LINK_NOARG(GtkSalFrame, AsyncScroll, Timer *, void) { assert(!m_aPendingScrollEvents.empty()); SalWheelMouseEvent aEvent; GdkEvent* pEvent = m_aPendingScrollEvents.back(); auto nEventX = pEvent->scroll.x; auto nEventY = pEvent->scroll.y; auto nTime = pEvent->scroll.time; auto nState = pEvent->scroll.state; double delta_x(0.0), delta_y(0.0); for (auto pSubEvent : m_aPendingScrollEvents) { delta_x += pSubEvent->scroll.delta_x; delta_y += pSubEvent->scroll.delta_y; gdk_event_free(pSubEvent); } m_aPendingScrollEvents.clear(); DrawingAreaScroll(delta_x, delta_y, nEventX, nEventY, nTime, nState); } #endif #if !GTK_CHECK_VERSION(4, 0, 0) SalWheelMouseEvent GtkSalFrame::GetWheelEvent(const GdkEventScroll& rEvent) { SalWheelMouseEvent aEvent; aEvent.mnTime = rEvent.time; aEvent.mnX = static_cast(rEvent.x); aEvent.mnY = static_cast(rEvent.y); aEvent.mnCode = GetMouseModCode(rEvent.state); switch (rEvent.direction) { case GDK_SCROLL_UP: aEvent.mnDelta = 120; aEvent.mnNotchDelta = 1; aEvent.mnScrollLines = 3; aEvent.mbHorz = false; break; case GDK_SCROLL_DOWN: aEvent.mnDelta = -120; aEvent.mnNotchDelta = -1; aEvent.mnScrollLines = 3; aEvent.mbHorz = false; break; case GDK_SCROLL_LEFT: aEvent.mnDelta = 120; aEvent.mnNotchDelta = 1; aEvent.mnScrollLines = 3; aEvent.mbHorz = true; break; case GDK_SCROLL_RIGHT: aEvent.mnDelta = -120; aEvent.mnNotchDelta = -1; aEvent.mnScrollLines = 3; aEvent.mbHorz = true; break; default: break; } return aEvent; } gboolean GtkSalFrame::signalScroll(GtkWidget*, GdkEvent* pInEvent, gpointer frame) { GdkEventScroll& rEvent = pInEvent->scroll; UpdateLastInputEventTime(rEvent.time); GtkSalFrame* pThis = static_cast(frame); if (rEvent.direction == GDK_SCROLL_SMOOTH) { pThis->LaunchAsyncScroll(pInEvent); return true; } //if we have smooth scrolling previous pending states, flush that queue now if (!pThis->m_aPendingScrollEvents.empty()) { pThis->m_aSmoothScrollIdle.Stop(); pThis->m_aSmoothScrollIdle.Invoke(); assert(pThis->m_aPendingScrollEvents.empty()); } SalWheelMouseEvent aEvent(GetWheelEvent(rEvent)); // --- RTL --- (mirror mouse pos) if (AllSettings::GetLayoutRTL()) aEvent.mnX = pThis->maGeometry.nWidth - 1 - aEvent.mnX; pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent); return true; } #else gboolean GtkSalFrame::signalScroll(GtkEventControllerScroll* pController, double delta_x, double delta_y, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController)); GdkModifierType eState = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController)); auto nTime = gdk_event_get_time(pEvent); UpdateLastInputEventTime(nTime); double nEventX(0.0), nEventY(0.0); gdk_event_get_position(pEvent, &nEventX, &nEventY); pThis->DrawingAreaScroll(delta_x, delta_y, nEventX, nEventY, nTime, eState); return true; } #endif void GtkSalFrame::gestureSwipe(GtkGestureSwipe* gesture, gdouble velocity_x, gdouble velocity_y, gpointer frame) { gdouble x, y; GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture)); //I feel I want the first point of the sequence, not the last point which //the docs say this gives, but for the moment assume we start and end //within the same vcl window if (gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y)) { SalSwipeEvent aEvent; aEvent.mnVelocityX = velocity_x; aEvent.mnVelocityY = velocity_y; aEvent.mnX = x; aEvent.mnY = y; GtkSalFrame* pThis = static_cast(frame); pThis->CallCallbackExc(SalEvent::Swipe, &aEvent); } } void GtkSalFrame::gestureLongPress(GtkGestureLongPress* gesture, gdouble x, gdouble y, gpointer frame) { GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture)); if (gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y)) { SalLongPressEvent aEvent; aEvent.mnX = x; aEvent.mnY = y; GtkSalFrame* pThis = static_cast(frame); pThis->CallCallbackExc(SalEvent::LongPress, &aEvent); } } void GtkSalFrame::DrawingAreaMotion(int nEventX, int nEventY, guint32 nTime, guint nState) { UpdateLastInputEventTime(nTime); SalMouseEvent aEvent; aEvent.mnTime = nTime; aEvent.mnX = nEventX; aEvent.mnY = nEventY; aEvent.mnCode = GetMouseModCode(nState); aEvent.mnButton = 0; if( AllSettings::GetLayoutRTL() ) aEvent.mnX = maGeometry.nWidth-1-aEvent.mnX; CallCallbackExc(SalEvent::MouseMove, &aEvent); } #if GTK_CHECK_VERSION(4, 0, 0) void GtkSalFrame::signalMotion(GtkEventControllerMotion *pController, double x, double y, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController)); GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController)); pThis->DrawingAreaMotion(x, y, gdk_event_get_time(pEvent), eType); } #else gboolean GtkSalFrame::signalMotion( GtkWidget*, GdkEventMotion* pEvent, gpointer frame ) { GtkSalFrame* pThis = static_cast(frame); GtkWidget* pEventWidget = pThis->getMouseEventWidget(); bool bDifferentEventWindow = pEvent->window != widget_get_surface(pEventWidget); //If a menu, e.g. font name dropdown, is open, then under wayland moving the //mouse in the top left corner of the toplevel window in a //0,0,float-width,float-height area generates motion events which are //delivered to the dropdown if (pThis->isFloatGrabWindow() && bDifferentEventWindow) return true; vcl::DeletionListener aDel( pThis ); int nEventX = pEvent->x; int nEventY = pEvent->y; if (bDifferentEventWindow) translate_coords(pEvent->window, pEventWidget, nEventX, nEventY); int frame_x = static_cast(pEvent->x_root - nEventX); int frame_y = static_cast(pEvent->y_root - nEventY); if (pThis->m_bGeometryIsProvisional || frame_x != pThis->maGeometry.nX || frame_y != pThis->maGeometry.nY) { pThis->m_bGeometryIsProvisional = false; pThis->maGeometry.nX = frame_x; pThis->maGeometry.nY = frame_y; ImplSVData* pSVData = ImplGetSVData(); if (pSVData->maNWFData.mbCanDetermineWindowPosition) pThis->CallCallbackExc(SalEvent::Move, nullptr); } if (!aDel.isDeleted()) { pThis->DrawingAreaMotion(pEvent->x_root - pThis->maGeometry.nX, pEvent->y_root - pThis->maGeometry.nY, pEvent->time, pEvent->state); } if (!aDel.isDeleted()) { // ask for the next hint gint x, y; GdkModifierType mask; gdk_window_get_pointer( widget_get_surface(GTK_WIDGET(pThis->m_pWindow)), &x, &y, &mask ); } return true; } #endif void GtkSalFrame::DrawingAreaCrossing(SalEvent nEventType, int nEventX, int nEventY, guint32 nTime, guint nState) { UpdateLastInputEventTime(nTime); SalMouseEvent aEvent; aEvent.mnTime = nTime; aEvent.mnX = nEventX; aEvent.mnY = nEventY; aEvent.mnCode = GetMouseModCode(nState); aEvent.mnButton = 0; if (AllSettings::GetLayoutRTL()) aEvent.mnX = maGeometry.nWidth-1-aEvent.mnX; CallCallbackExc(nEventType, &aEvent); } #if GTK_CHECK_VERSION(4, 0, 0) void GtkSalFrame::signalEnter(GtkEventControllerMotion *pController, double x, double y, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController)); GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController)); pThis->DrawingAreaCrossing(SalEvent::MouseMove, x, y, pEvent ? gdk_event_get_time(pEvent) : GDK_CURRENT_TIME, eType); } void GtkSalFrame::signalLeave(GtkEventControllerMotion *pController, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController)); GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController)); pThis->DrawingAreaCrossing(SalEvent::MouseLeave, -1, -1, pEvent ? gdk_event_get_time(pEvent) : GDK_CURRENT_TIME, eType); } #else gboolean GtkSalFrame::signalCrossing( GtkWidget*, GdkEventCrossing* pEvent, gpointer frame ) { GtkSalFrame* pThis = static_cast(frame); pThis->DrawingAreaCrossing((pEvent->type == GDK_ENTER_NOTIFY) ? SalEvent::MouseMove : SalEvent::MouseLeave, pEvent->x_root - pThis->maGeometry.nX, pEvent->y_root - pThis->maGeometry.nY, pEvent->time, pEvent->state); return true; } #endif cairo_t* GtkSalFrame::getCairoContext() const { cairo_t* cr = cairo_create(m_pSurface); assert(cr); return cr; } void GtkSalFrame::damaged(sal_Int32 nExtentsX, sal_Int32 nExtentsY, sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight) const { #if OSL_DEBUG_LEVEL > 0 if (dumpframes) { static int frame; OString tmp("/tmp/frame" + OString::number(frame++) + ".png"); cairo_t* cr = getCairoContext(); cairo_surface_write_to_png(cairo_get_target(cr), tmp.getStr()); cairo_destroy(cr); } #endif // quite a bit of noise in RTL mode with negative widths if (nExtentsWidth <= 0 || nExtentsHeight <= 0) return; #if !GTK_CHECK_VERSION(4, 0, 0) gtk_widget_queue_draw_area(GTK_WIDGET(m_pDrawingArea), nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight); #else gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea)); (void)nExtentsX; (void)nExtentsY; #endif } // blit our backing cairo surface to the target cairo context void GtkSalFrame::DrawingAreaDraw(cairo_t *cr) { cairo_set_source_surface(cr, m_pSurface, 0, 0); cairo_paint(cr); } #if !GTK_CHECK_VERSION(4, 0, 0) gboolean GtkSalFrame::signalDraw(GtkWidget*, cairo_t *cr, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); pThis->DrawingAreaDraw(cr); return false; } #else void GtkSalFrame::signalDraw(GtkDrawingArea*, cairo_t *cr, int /*width*/, int /*height*/, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); pThis->DrawingAreaDraw(cr); } #endif void GtkSalFrame::DrawingAreaResized(GtkWidget* pWidget, int nWidth, int nHeight) { // ignore size-allocations that occur during configuring an embedded SalObject if (m_bSalObjectSetPosSize) return; maGeometry.nWidth = nWidth; maGeometry.nHeight = nHeight; bool bRealized = gtk_widget_get_realized(pWidget); if (bRealized) AllocateFrame(); CallCallbackExc( SalEvent::Resize, nullptr ); if (bRealized) TriggerPaintEvent(); } #if !GTK_CHECK_VERSION(4, 0, 0) void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, GdkRectangle *pAllocation, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); pThis->DrawingAreaResized(pWidget, pAllocation->width, pAllocation->height); } #else void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, int nWidth, int nHeight, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); pThis->DrawingAreaResized(pWidget, nWidth, nHeight); } #endif #if !GTK_CHECK_VERSION(4, 0, 0) namespace { void swapDirection(GdkGravity& gravity) { if (gravity == GDK_GRAVITY_NORTH_WEST) gravity = GDK_GRAVITY_NORTH_EAST; else if (gravity == GDK_GRAVITY_NORTH_EAST) gravity = GDK_GRAVITY_NORTH_WEST; else if (gravity == GDK_GRAVITY_SOUTH_WEST) gravity = GDK_GRAVITY_SOUTH_EAST; else if (gravity == GDK_GRAVITY_SOUTH_EAST) gravity = GDK_GRAVITY_SOUTH_WEST; } } #endif void GtkSalFrame::signalRealize(GtkWidget*, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); pThis->AllocateFrame(); if (pThis->m_bSalObjectSetPosSize) return; pThis->TriggerPaintEvent(); #if !GTK_CHECK_VERSION(4, 0, 0) if (!pThis->m_bFloatPositioned) return; static auto window_move_to_rect = reinterpret_cast( dlsym(nullptr, "gdk_window_move_to_rect")); if (!window_move_to_rect) return; GdkGravity rect_anchor = GDK_GRAVITY_SOUTH_WEST, menu_anchor = GDK_GRAVITY_NORTH_WEST; if (pThis->m_nFloatFlags & FloatWinPopupFlags::Left) { rect_anchor = GDK_GRAVITY_NORTH_WEST; menu_anchor = GDK_GRAVITY_NORTH_EAST; } else if (pThis->m_nFloatFlags & FloatWinPopupFlags::Up) { rect_anchor = GDK_GRAVITY_NORTH_WEST; menu_anchor = GDK_GRAVITY_SOUTH_WEST; } else if (pThis->m_nFloatFlags & FloatWinPopupFlags::Right) { rect_anchor = GDK_GRAVITY_NORTH_EAST; } VclPtr pVclParent = pThis->GetWindow()->GetParent(); if (pVclParent->GetOutDev()->HasMirroredGraphics() && pVclParent->IsRTLEnabled()) { swapDirection(rect_anchor); swapDirection(menu_anchor); } tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pVclParent, pThis->m_aFloatRect); if (gdk_window_get_window_type(widget_get_surface(pThis->m_pParent->m_pWindow)) != GDK_WINDOW_TOPLEVEL) aFloatRect.Move(-pThis->m_pParent->maGeometry.nX, -pThis->m_pParent->maGeometry.nY); GdkRectangle rect {static_cast(aFloatRect.Left()), static_cast(aFloatRect.Top()), static_cast(aFloatRect.GetWidth()), static_cast(aFloatRect.GetHeight())}; GdkSurface* gdkWindow = widget_get_surface(pThis->m_pWindow); window_move_to_rect(gdkWindow, &rect, rect_anchor, menu_anchor, static_cast(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE), 0, 0); #endif } #if !GTK_CHECK_VERSION(4, 0, 0) gboolean GtkSalFrame::signalConfigure(GtkWidget*, GdkEventConfigure* pEvent, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); bool bMoved = false; int x = pEvent->x, y = pEvent->y; /* #i31785# claims we cannot trust the x,y members of the event; * they are e.g. not set correctly on maximize/demaximize; * yet the gdkdisplay-x11.c code handling configure_events has * done this XTranslateCoordinates work since the day ~zero. */ if (pThis->m_bGeometryIsProvisional || x != pThis->maGeometry.nX || y != pThis->maGeometry.nY ) { bMoved = true; pThis->m_bGeometryIsProvisional = false; pThis->maGeometry.nX = x; pThis->maGeometry.nY = y; } // update decoration hints GdkRectangle aRect; gdk_window_get_frame_extents( widget_get_surface(GTK_WIDGET(pThis->m_pWindow)), &aRect ); pThis->maGeometry.nTopDecoration = y - aRect.y; pThis->maGeometry.nBottomDecoration = aRect.y + aRect.height - y - pEvent->height; pThis->maGeometry.nLeftDecoration = x - aRect.x; pThis->maGeometry.nRightDecoration = aRect.x + aRect.width - x - pEvent->width; pThis->updateScreenNumber(); if (bMoved) { ImplSVData* pSVData = ImplGetSVData(); if (pSVData->maNWFData.mbCanDetermineWindowPosition) pThis->CallCallbackExc(SalEvent::Move, nullptr); } return false; } #endif void GtkSalFrame::queue_draw() { gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea)); } void GtkSalFrame::TriggerPaintEvent() { //Under gtk2 we can basically paint directly into the XWindow and on //additional "expose-event" events we can re-render the missing pieces // //Under gtk3 we have to keep our own buffer up to date and flush it into //the given cairo context on "draw". So we emit a paint event on //opportune resize trigger events to initially fill our backbuffer and then //keep it up to date with our direct paints and tell gtk those regions //have changed and then blit them into the provided cairo context when //we get the "draw" // //The other alternative was to always paint everything on "draw", but //that duplicates the amount of drawing and is hideously slow SAL_INFO("vcl.gtk3", "force painting" << 0 << "," << 0 << " " << maGeometry.nWidth << "x" << maGeometry.nHeight); SalPaintEvent aPaintEvt(0, 0, maGeometry.nWidth, maGeometry.nHeight, true); CallCallbackExc(SalEvent::Paint, &aPaintEvt); queue_draw(); } void GtkSalFrame::DrawingAreaFocusInOut(SalEvent nEventType) { SalGenericInstance* pSalInstance = static_cast(GetSalData()->m_pInstance); // check if printers have changed (analogous to salframe focus handler) pSalInstance->updatePrinterUpdate(); if (nEventType == SalEvent::LoseFocus) m_nKeyModifiers = ModKeyFlags::NONE; if (m_pIMHandler) { bool bFocusInAnotherGtkWidget = false; if (GTK_IS_WINDOW(m_pWindow)) { GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(m_pWindow)); bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(m_pFixedContainer); } if (!bFocusInAnotherGtkWidget) m_pIMHandler->focusChanged(nEventType == SalEvent::GetFocus); } // ask for changed printers like generic implementation if (nEventType == SalEvent::GetFocus && pSalInstance->isPrinterInit()) pSalInstance->updatePrinterUpdate(); CallCallbackExc(nEventType, nullptr); } #if !GTK_CHECK_VERSION(4, 0, 0) gboolean GtkSalFrame::signalFocus( GtkWidget*, GdkEventFocus* pEvent, gpointer frame ) { GtkSalFrame* pThis = static_cast(frame); SalGenericInstance *pSalInstance = static_cast< SalGenericInstance* >(GetSalData()->m_pInstance); // check if printers have changed (analogous to salframe focus handler) pSalInstance->updatePrinterUpdate(); if( !pEvent->in ) pThis->m_nKeyModifiers = ModKeyFlags::NONE; if( pThis->m_pIMHandler ) { bool bFocusInAnotherGtkWidget = false; if (GTK_IS_WINDOW(pThis->m_pWindow)) { GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow)); bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(pThis->m_pFixedContainer); } if (!bFocusInAnotherGtkWidget) pThis->m_pIMHandler->focusChanged( pEvent->in != 0 ); } // ask for changed printers like generic implementation if( pEvent->in && pSalInstance->isPrinterInit() ) pSalInstance->updatePrinterUpdate(); // FIXME: find out who the hell steals the focus from our frame // while we have the pointer grabbed, this should not come from // the window manager. Is this an event that was still queued ? // The focus does not seem to get set inside our process // in the meantime do not propagate focus get/lose if floats are open if( m_nFloats == 0 ) { GtkWidget* pGrabWidget; if (GTK_IS_EVENT_BOX(pThis->m_pWindow)) pGrabWidget = GTK_WIDGET(pThis->m_pWindow); else pGrabWidget = GTK_WIDGET(pThis->m_pFixedContainer); bool bHasFocus = gtk_widget_has_focus(pGrabWidget); pThis->CallCallbackExc(bHasFocus ? SalEvent::GetFocus : SalEvent::LoseFocus, nullptr); } return false; } #else void GtkSalFrame::signalFocusEnter(GtkEventControllerFocus*, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); pThis->DrawingAreaFocusInOut(SalEvent::GetFocus); } void GtkSalFrame::signalFocusLeave(GtkEventControllerFocus*, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); pThis->DrawingAreaFocusInOut(SalEvent::LoseFocus); } #endif // change of focus between native widgets within the toplevel #if !GTK_CHECK_VERSION(4, 0, 0) void GtkSalFrame::signalSetFocus(GtkWindow*, GtkWidget* pWidget, gpointer frame) #else void GtkSalFrame::signalSetFocus(GtkWindow*, GParamSpec*, gpointer frame) #endif { GtkSalFrame* pThis = static_cast(frame); GtkWidget* pGrabWidget = GTK_WIDGET(pThis->m_pFixedContainer); GtkWidget* pTopLevel = widget_get_toplevel(pGrabWidget); // see commentary in GtkSalObjectWidgetClip::Show if (pTopLevel && g_object_get_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange")) return; #if GTK_CHECK_VERSION(4, 0, 0) GtkWidget* pWidget = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow)); #endif // tdf#129634 interpret losing focus as focus passing explicitly to another widget bool bLoseFocus = pWidget && pWidget != pGrabWidget; // do not propagate focus get/lose if floats are open pThis->CallCallbackExc(bLoseFocus ? SalEvent::LoseFocus : SalEvent::GetFocus, nullptr); #if !GTK_CHECK_VERSION(4, 0, 0) gtk_widget_set_can_focus(GTK_WIDGET(pThis->m_pFixedContainer), !bLoseFocus); #endif } void GtkSalFrame::WindowMap() { if (m_bIconSetWhileUnmapped) SetIcon(gtk_window_get_icon_name(GTK_WINDOW(m_pWindow))); CallCallbackExc( SalEvent::Resize, nullptr ); TriggerPaintEvent(); } void GtkSalFrame::WindowUnmap() { CallCallbackExc( SalEvent::Resize, nullptr ); if (m_bFloatPositioned) { // Unrealize is needed for cases where we reuse the same popup // (e.g. the font name control), making the realize signal fire // again on next show. gtk_widget_unrealize(m_pWindow); m_bFloatPositioned = false; } } #if GTK_CHECK_VERSION(4, 0, 0) void GtkSalFrame::signalMap(GtkWidget*, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); pThis->WindowMap(); } void GtkSalFrame::signalUnmap(GtkWidget*, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); pThis->WindowUnmap(); } #else gboolean GtkSalFrame::signalMap(GtkWidget*, GdkEvent*, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); pThis->WindowMap(); return false; } gboolean GtkSalFrame::signalUnmap(GtkWidget*, GdkEvent*, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); pThis->WindowUnmap(); return false; } #endif #if !GTK_CHECK_VERSION(4, 0, 0) static bool key_forward(GdkEventKey* pEvent, GtkWindow* pDest) { gpointer pClass = g_type_class_ref(GTK_TYPE_WINDOW); GtkWidgetClass* pWindowClass = GTK_WIDGET_CLASS(pClass); bool bHandled = pEvent->type == GDK_KEY_PRESS ? pWindowClass->key_press_event(GTK_WIDGET(pDest), pEvent) : pWindowClass->key_release_event(GTK_WIDGET(pDest), pEvent); g_type_class_unref(pClass); return bHandled; } static bool activate_menubar_mnemonic(GtkWidget* pWidget, guint nKeyval) { const char* pLabel = gtk_menu_item_get_label(GTK_MENU_ITEM(pWidget)); gunichar cAccelChar = 0; if (!pango_parse_markup(pLabel, -1, '_', nullptr, nullptr, &cAccelChar, nullptr)) return false; if (!cAccelChar) return false; auto nMnemonicKeyval = gdk_keyval_to_lower(gdk_unicode_to_keyval(cAccelChar)); if (nKeyval == nMnemonicKeyval) return gtk_widget_mnemonic_activate(pWidget, false); return false; } bool GtkSalFrame::HandleMenubarMnemonic(guint eState, guint nKeyval) { bool bUsedInMenuBar = false; if (eState & GDK_ALT_MASK) { if (GtkWidget* pMenuBar = m_pSalMenu ? m_pSalMenu->GetMenuBarWidget() : nullptr) { GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pMenuBar)); for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild)) { bUsedInMenuBar = activate_menubar_mnemonic(static_cast(pChild->data), nKeyval); if (bUsedInMenuBar) break; } g_list_free(pChildren); } } return bUsedInMenuBar; } gboolean GtkSalFrame::signalKey(GtkWidget* pWidget, GdkEventKey* pEvent, gpointer frame) { UpdateLastInputEventTime(pEvent->time); GtkSalFrame* pThis = static_cast(frame); bool bFocusInAnotherGtkWidget = false; VclPtr xTopLevelInterimWindow; if (GTK_IS_WINDOW(pThis->m_pWindow)) { GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow)); bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(pThis->m_pFixedContainer); if (bFocusInAnotherGtkWidget) { if (!gtk_widget_get_realized(pFocusWindow)) return true; // if the focus is not in our main widget, see if there is a handler // for this key stroke in GtkWindow first if (key_forward(pEvent, GTK_WINDOW(pThis->m_pWindow))) return true; // Is focus inside an InterimItemWindow? In which case find that // InterimItemWindow and send unconsumed keystrokes to it to // support ctrl-q etc shortcuts. Only bother to search for the // InterimItemWindow if it is a toplevel that fills its frame, or // the keystroke is sufficiently special its worth passing on, // e.g. F6 to switch between task-panels or F5 to close a navigator if (pThis->IsCycleFocusOutDisallowed() || IsFunctionKeyVal(pEvent->keyval)) { GtkWidget* pSearch = pFocusWindow; while (pSearch) { void* pData = g_object_get_data(G_OBJECT(pSearch), "InterimWindowGlue"); if (pData) { xTopLevelInterimWindow = static_cast(pData); break; } pSearch = gtk_widget_get_parent(pSearch); } } } } if (pThis->isFloatGrabWindow()) return signalKey(pWidget, pEvent, pThis->m_pParent); vcl::DeletionListener aDel( pThis ); if (!bFocusInAnotherGtkWidget && pThis->m_pIMHandler && pThis->m_pIMHandler->handleKeyEvent(pEvent)) return true; bool bStopProcessingKey = false; // handle modifiers if( pEvent->keyval == GDK_KEY_Shift_L || pEvent->keyval == GDK_KEY_Shift_R || pEvent->keyval == GDK_KEY_Control_L || pEvent->keyval == GDK_KEY_Control_R || pEvent->keyval == GDK_KEY_Alt_L || pEvent->keyval == GDK_KEY_Alt_R || pEvent->keyval == GDK_KEY_Meta_L || pEvent->keyval == GDK_KEY_Meta_R || pEvent->keyval == GDK_KEY_Super_L || pEvent->keyval == GDK_KEY_Super_R ) { sal_uInt16 nModCode = GetKeyModCode( pEvent->state ); ModKeyFlags nExtModMask = ModKeyFlags::NONE; sal_uInt16 nModMask = 0; // pressing just the ctrl key leads to a keysym of XK_Control but // the event state does not contain ControlMask. In the release // event it's the other way round: it does contain the Control mask. // The modifier mode therefore has to be adapted manually. switch( pEvent->keyval ) { case GDK_KEY_Control_L: nExtModMask = ModKeyFlags::LeftMod1; nModMask = KEY_MOD1; break; case GDK_KEY_Control_R: nExtModMask = ModKeyFlags::RightMod1; nModMask = KEY_MOD1; break; case GDK_KEY_Alt_L: nExtModMask = ModKeyFlags::LeftMod2; nModMask = KEY_MOD2; break; case GDK_KEY_Alt_R: nExtModMask = ModKeyFlags::RightMod2; nModMask = KEY_MOD2; break; case GDK_KEY_Shift_L: nExtModMask = ModKeyFlags::LeftShift; nModMask = KEY_SHIFT; break; case GDK_KEY_Shift_R: nExtModMask = ModKeyFlags::RightShift; nModMask = KEY_SHIFT; break; // Map Meta/Super to MOD3 modifier on all Unix systems // except macOS case GDK_KEY_Meta_L: case GDK_KEY_Super_L: nExtModMask = ModKeyFlags::LeftMod3; nModMask = KEY_MOD3; break; case GDK_KEY_Meta_R: case GDK_KEY_Super_R: nExtModMask = ModKeyFlags::RightMod3; nModMask = KEY_MOD3; break; } SalKeyModEvent aModEvt; aModEvt.mbDown = pEvent->type == GDK_KEY_PRESS; if( pEvent->type == GDK_KEY_RELEASE ) { aModEvt.mnModKeyCode = pThis->m_nKeyModifiers; aModEvt.mnCode = nModCode & ~nModMask; pThis->m_nKeyModifiers &= ~nExtModMask; } else { aModEvt.mnCode = nModCode | nModMask; pThis->m_nKeyModifiers |= nExtModMask; aModEvt.mnModKeyCode = pThis->m_nKeyModifiers; } pThis->CallCallbackExc( SalEvent::KeyModChange, &aModEvt ); } else { bool bRestoreDisallowCycleFocusOut = false; VclPtr xOrigFrameFocusWin; VclPtr xOrigFocusWin; if (xTopLevelInterimWindow) { // Focus is inside an InterimItemWindow so send unconsumed // keystrokes to by setting it as the mpFocusWin VclPtr xVclWindow = pThis->GetWindow(); ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData; xOrigFrameFocusWin = pFrameData->mpFocusWin; pFrameData->mpFocusWin = xTopLevelInterimWindow; ImplSVData* pSVData = ImplGetSVData(); xOrigFocusWin = pSVData->mpWinData->mpFocusWin; pSVData->mpWinData->mpFocusWin = xTopLevelInterimWindow; if (pEvent->keyval == GDK_KEY_F6 && pThis->IsCycleFocusOutDisallowed()) { // For F6, allow the focus to leave the InterimItemWindow pThis->AllowCycleFocusOut(); bRestoreDisallowCycleFocusOut = true; } } bStopProcessingKey = pThis->doKeyCallback(pEvent->state, pEvent->keyval, pEvent->hardware_keycode, pEvent->group, sal_Unicode(gdk_keyval_to_unicode( pEvent->keyval )), (pEvent->type == GDK_KEY_PRESS), false); // tdf#144846 If this is registered as a menubar mnemonic then ensure // that any other widget won't be considered as a candidate by taking // over the task of launch the menubar menu outself // The code was moved here from its original position at beginning // of this function in order to resolve tdf#146174. if (!bStopProcessingKey && // module key handler did not process key pEvent->type == GDK_KEY_PRESS && // module key handler handles only GDK_KEY_PRESS GTK_IS_WINDOW(pThis->m_pWindow) && pThis->HandleMenubarMnemonic(pEvent->state, pEvent->keyval)) { return true; } if (!aDel.isDeleted()) { pThis->m_nKeyModifiers = ModKeyFlags::NONE; if (xTopLevelInterimWindow) { // Focus was inside an InterimItemWindow, restore the original // focus win, unless the focus was changed away from the // InterimItemWindow which should only be possible with F6 VclPtr xVclWindow = pThis->GetWindow(); ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData; if (pFrameData->mpFocusWin == xTopLevelInterimWindow) pFrameData->mpFocusWin = xOrigFrameFocusWin; ImplSVData* pSVData = ImplGetSVData(); if (pSVData->mpWinData->mpFocusWin == xTopLevelInterimWindow) pSVData->mpWinData->mpFocusWin = xOrigFocusWin; if (bRestoreDisallowCycleFocusOut) { // undo the above AllowCycleFocusOut for F6 pThis->DisallowCycleFocusOut(); } } } } if (!bFocusInAnotherGtkWidget && !aDel.isDeleted() && pThis->m_pIMHandler) pThis->m_pIMHandler->updateIMSpotLocation(); return bStopProcessingKey; } #else bool GtkSalFrame::DrawingAreaKey(GtkEventControllerKey* pController, SalEvent nEventType, guint keyval, guint keycode, guint state) { guint32 nTime = gdk_event_get_time(gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController))); UpdateLastInputEventTime(nTime); bool bFocusInAnotherGtkWidget = false; VclPtr xTopLevelInterimWindow; if (GTK_IS_WINDOW(m_pWindow)) { GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(m_pWindow)); bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(m_pFixedContainer); if (bFocusInAnotherGtkWidget) { if (!gtk_widget_get_realized(pFocusWindow)) return true; // if the focus is not in our main widget, see if there is a handler // for this key stroke in GtkWindow first bool bHandled = gtk_event_controller_key_forward(pController, m_pWindow); if (bHandled) return true; // Is focus inside an InterimItemWindow? In which case find that // InterimItemWindow and send unconsumed keystrokes to it to // support ctrl-q etc shortcuts. Only bother to search for the // InterimItemWindow if it is a toplevel that fills its frame, or // the keystroke is sufficiently special its worth passing on, // e.g. F6 to switch between task-panels or F5 to close a navigator if (IsCycleFocusOutDisallowed() || IsFunctionKeyVal(keyval)) { GtkWidget* pSearch = pFocusWindow; while (pSearch) { void* pData = g_object_get_data(G_OBJECT(pSearch), "InterimWindowGlue"); if (pData) { xTopLevelInterimWindow = static_cast(pData); break; } pSearch = gtk_widget_get_parent(pSearch); } } } } vcl::DeletionListener aDel(this); bool bStopProcessingKey = false; // handle modifiers if( keyval == GDK_KEY_Shift_L || keyval == GDK_KEY_Shift_R || keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R || keyval == GDK_KEY_Alt_L || keyval == GDK_KEY_Alt_R || keyval == GDK_KEY_Meta_L || keyval == GDK_KEY_Meta_R || keyval == GDK_KEY_Super_L || keyval == GDK_KEY_Super_R ) { sal_uInt16 nModCode = GetKeyModCode(state); ModKeyFlags nExtModMask = ModKeyFlags::NONE; sal_uInt16 nModMask = 0; // pressing just the ctrl key leads to a keysym of XK_Control but // the event state does not contain ControlMask. In the release // event it's the other way round: it does contain the Control mask. // The modifier mode therefore has to be adapted manually. switch (keyval) { case GDK_KEY_Control_L: nExtModMask = ModKeyFlags::LeftMod1; nModMask = KEY_MOD1; break; case GDK_KEY_Control_R: nExtModMask = ModKeyFlags::RightMod1; nModMask = KEY_MOD1; break; case GDK_KEY_Alt_L: nExtModMask = ModKeyFlags::LeftMod2; nModMask = KEY_MOD2; break; case GDK_KEY_Alt_R: nExtModMask = ModKeyFlags::RightMod2; nModMask = KEY_MOD2; break; case GDK_KEY_Shift_L: nExtModMask = ModKeyFlags::LeftShift; nModMask = KEY_SHIFT; break; case GDK_KEY_Shift_R: nExtModMask = ModKeyFlags::RightShift; nModMask = KEY_SHIFT; break; // Map Meta/Super to MOD3 modifier on all Unix systems // except macOS case GDK_KEY_Meta_L: case GDK_KEY_Super_L: nExtModMask = ModKeyFlags::LeftMod3; nModMask = KEY_MOD3; break; case GDK_KEY_Meta_R: case GDK_KEY_Super_R: nExtModMask = ModKeyFlags::RightMod3; nModMask = KEY_MOD3; break; } SalKeyModEvent aModEvt; aModEvt.mbDown = nEventType == SalEvent::KeyInput; if (!aModEvt.mbDown) { aModEvt.mnModKeyCode = m_nKeyModifiers; aModEvt.mnCode = nModCode & ~nModMask; m_nKeyModifiers &= ~nExtModMask; } else { aModEvt.mnCode = nModCode | nModMask; m_nKeyModifiers |= nExtModMask; aModEvt.mnModKeyCode = m_nKeyModifiers; } CallCallbackExc(SalEvent::KeyModChange, &aModEvt); } else { bool bRestoreDisallowCycleFocusOut = false; VclPtr xOrigFrameFocusWin; VclPtr xOrigFocusWin; if (xTopLevelInterimWindow) { // Focus is inside an InterimItemWindow so send unconsumed // keystrokes to by setting it as the mpFocusWin VclPtr xVclWindow = GetWindow(); ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData; xOrigFrameFocusWin = pFrameData->mpFocusWin; pFrameData->mpFocusWin = xTopLevelInterimWindow; ImplSVData* pSVData = ImplGetSVData(); xOrigFocusWin = pSVData->mpWinData->mpFocusWin; pSVData->mpWinData->mpFocusWin = xTopLevelInterimWindow; if (keyval == GDK_KEY_F6 && IsCycleFocusOutDisallowed()) { // For F6, allow the focus to leave the InterimItemWindow AllowCycleFocusOut(); bRestoreDisallowCycleFocusOut = true; } } bStopProcessingKey = doKeyCallback(state, keyval, keycode, 0, // group sal_Unicode(gdk_keyval_to_unicode(keyval)), nEventType == SalEvent::KeyInput, false); if (!aDel.isDeleted()) { m_nKeyModifiers = ModKeyFlags::NONE; if (xTopLevelInterimWindow) { // Focus was inside an InterimItemWindow, restore the original // focus win, unless the focus was changed away from the // InterimItemWindow which should only be possible with F6 VclPtr xVclWindow = GetWindow(); ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData; if (pFrameData->mpFocusWin == xTopLevelInterimWindow) pFrameData->mpFocusWin = xOrigFrameFocusWin; ImplSVData* pSVData = ImplGetSVData(); if (pSVData->mpWinData->mpFocusWin == xTopLevelInterimWindow) pSVData->mpWinData->mpFocusWin = xOrigFocusWin; if (bRestoreDisallowCycleFocusOut) { // undo the above AllowCycleFocusOut for F6 DisallowCycleFocusOut(); } } } } if (m_pIMHandler) m_pIMHandler->updateIMSpotLocation(); return bStopProcessingKey; } gboolean GtkSalFrame::signalKeyPressed(GtkEventControllerKey* pController, guint keyval, guint keycode, GdkModifierType state, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); return pThis->DrawingAreaKey(pController, SalEvent::KeyInput, keyval, keycode, state); } gboolean GtkSalFrame::signalKeyReleased(GtkEventControllerKey* pController, guint keyval, guint keycode, GdkModifierType state, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); return pThis->DrawingAreaKey(pController, SalEvent::KeyUp, keyval, keycode, state); } #endif bool GtkSalFrame::WindowCloseRequest() { CallCallbackExc(SalEvent::Close, nullptr); return true; } #if GTK_CHECK_VERSION(4, 0, 0) gboolean GtkSalFrame::signalDelete(GtkWidget*, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); return pThis->WindowCloseRequest(); } #else gboolean GtkSalFrame::signalDelete(GtkWidget*, GdkEvent*, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); return pThis->WindowCloseRequest(); } #endif const cairo_font_options_t* GtkSalFrame::get_font_options() { GtkWidget* pWidget = getMouseEventWidget(); #if GTK_CHECK_VERSION(4, 0, 0) PangoContext* pContext = gtk_widget_get_pango_context(pWidget); assert(pContext); return pango_cairo_context_get_font_options(pContext); #else return gdk_screen_get_font_options(gtk_widget_get_screen(pWidget)); #endif } #if GTK_CHECK_VERSION(4, 0, 0) void GtkSalFrame::signalStyleUpdated(GtkWidget*, const gchar* /*pSetting*/, gpointer frame) #else void GtkSalFrame::signalStyleUpdated(GtkWidget*, gpointer frame) #endif { GtkSalFrame* pThis = static_cast(frame); // note: settings changed for multiple frames is avoided in winproc.cxx ImplHandleSettings GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::SettingsChanged ); // a plausible alternative might be to send SalEvent::FontChanged if pSetting starts with "gtk-xft" // fire off font-changed when the system cairo font hints change GtkInstance *pInstance = static_cast(GetSalData()->m_pInstance); const cairo_font_options_t* pLastCairoFontOptions = pInstance->GetLastSeenCairoFontOptions(); const cairo_font_options_t* pCurrentCairoFontOptions = pThis->get_font_options(); bool bFontSettingsChanged = true; if (pLastCairoFontOptions && pCurrentCairoFontOptions) bFontSettingsChanged = !cairo_font_options_equal(pLastCairoFontOptions, pCurrentCairoFontOptions); else if (!pLastCairoFontOptions && !pCurrentCairoFontOptions) bFontSettingsChanged = false; if (bFontSettingsChanged) { pInstance->ResetLastSeenCairoFontOptions(pCurrentCairoFontOptions); GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::FontChanged ); } } #if !GTK_CHECK_VERSION(4, 0, 0) gboolean GtkSalFrame::signalWindowState( GtkWidget*, GdkEvent* pEvent, gpointer frame ) { GtkSalFrame* pThis = static_cast(frame); if( (pThis->m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) != (pEvent->window_state.new_window_state & GDK_TOPLEVEL_STATE_MINIMIZED) ) { GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize ); pThis->TriggerPaintEvent(); } if ((pEvent->window_state.new_window_state & GDK_TOPLEVEL_STATE_MAXIMIZED) && !(pThis->m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED)) { pThis->m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(pThis->m_pWindow)); } if ((pEvent->window_state.new_window_state & GDK_WINDOW_STATE_WITHDRAWN) && !(pThis->m_nState & GDK_WINDOW_STATE_WITHDRAWN)) { if (pThis->isFloatGrabWindow()) pThis->closePopup(); } pThis->m_nState = pEvent->window_state.new_window_state; return false; } #else void GtkSalFrame::signalWindowState(GdkToplevel* pSurface, GParamSpec*, gpointer frame) { GdkToplevelState eNewWindowState = gdk_toplevel_get_state(pSurface); GtkSalFrame* pThis = static_cast(frame); if( (pThis->m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) != (eNewWindowState & GDK_TOPLEVEL_STATE_MINIMIZED) ) { GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize ); pThis->TriggerPaintEvent(); } if ((eNewWindowState & GDK_TOPLEVEL_STATE_MAXIMIZED) && !(pThis->m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED)) { pThis->m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(pThis->m_pWindow)); } pThis->m_nState = eNewWindowState; } #endif namespace { GdkDragAction VclToGdk(sal_Int8 dragOperation) { GdkDragAction eRet(static_cast(0)); if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY) eRet = static_cast(eRet | GDK_ACTION_COPY); if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE) eRet = static_cast(eRet | GDK_ACTION_MOVE); if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK) eRet = static_cast(eRet | GDK_ACTION_LINK); return eRet; } sal_Int8 GdkToVcl(GdkDragAction dragOperation) { sal_Int8 nRet(0); if (dragOperation & GDK_ACTION_COPY) nRet |= css::datatransfer::dnd::DNDConstants::ACTION_COPY; if (dragOperation & GDK_ACTION_MOVE) nRet |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE; if (dragOperation & GDK_ACTION_LINK) nRet |= css::datatransfer::dnd::DNDConstants::ACTION_LINK; return nRet; } } namespace { GdkDragAction getPreferredDragAction(sal_Int8 dragOperation) { GdkDragAction eAct(static_cast(0)); if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE) eAct = GDK_ACTION_MOVE; else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY) eAct = GDK_ACTION_COPY; else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK) eAct = GDK_ACTION_LINK; return eAct; } } static bool g_DropSuccessSet = false; static bool g_DropSuccess = false; namespace { #if GTK_CHECK_VERSION(4, 0, 0) void read_drop_async_completed(GObject* source, GAsyncResult* res, gpointer user_data) { GdkDrop* drop = GDK_DROP(source); read_transfer_result* pRes = static_cast(user_data); GInputStream* pResult = gdk_drop_read_finish(drop, res, nullptr, nullptr); if (!pResult) { pRes->bDone = true; g_main_context_wakeup(nullptr); return; } pRes->aVector.resize(read_transfer_result::BlockSize); g_input_stream_read_async(pResult, pRes->aVector.data(), pRes->aVector.size(), G_PRIORITY_DEFAULT, nullptr, read_transfer_result::read_block_async_completed, user_data); } #endif class GtkDropTargetDropContext : public cppu::WeakImplHelper { #if !GTK_CHECK_VERSION(4, 0, 0) GdkDragContext *m_pContext; guint m_nTime; #else GdkDrop* m_pDrop; #endif public: #if !GTK_CHECK_VERSION(4, 0, 0) GtkDropTargetDropContext(GdkDragContext* pContext, guint nTime) : m_pContext(pContext) , m_nTime(nTime) #else GtkDropTargetDropContext(GdkDrop* pDrop) : m_pDrop(pDrop) #endif { } // XDropTargetDropContext virtual void SAL_CALL acceptDrop(sal_Int8 dragOperation) override { #if !GTK_CHECK_VERSION(4, 0, 0) gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime); #else GdkDragAction eDragAction = getPreferredDragAction(dragOperation); gdk_drop_status(m_pDrop, static_cast(eDragAction | gdk_drop_get_actions(m_pDrop)), eDragAction); #endif } virtual void SAL_CALL rejectDrop() override { #if !GTK_CHECK_VERSION(4, 0, 0) gdk_drag_status(m_pContext, static_cast(0), m_nTime); #else gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), static_cast(0)); #endif } virtual void SAL_CALL dropComplete(sal_Bool bSuccess) override { #if !GTK_CHECK_VERSION(4, 0, 0) gtk_drag_finish(m_pContext, bSuccess, false, m_nTime); #else // should we do something better here gdk_drop_finish(m_pDrop, bSuccess ? gdk_drop_get_actions(m_pDrop) : static_cast(0)); #endif if (GtkInstDragSource::g_ActiveDragSource) { g_DropSuccessSet = true; g_DropSuccess = bSuccess; } } }; } class GtkDnDTransferable : public GtkTransferable { #if !GTK_CHECK_VERSION(4, 0, 0) GdkDragContext *m_pContext; guint m_nTime; GtkWidget *m_pWidget; GtkInstDropTarget* m_pDropTarget; #else GdkDrop* m_pDrop; #endif #if !GTK_CHECK_VERSION(4, 0, 0) GMainLoop *m_pLoop; GtkSelectionData *m_pData; #endif public: #if !GTK_CHECK_VERSION(4, 0, 0) GtkDnDTransferable(GdkDragContext *pContext, guint nTime, GtkWidget *pWidget, GtkInstDropTarget *pDropTarget) : m_pContext(pContext) , m_nTime(nTime) , m_pWidget(pWidget) , m_pDropTarget(pDropTarget) #else GtkDnDTransferable(GdkDrop *pDrop) : m_pDrop(pDrop) #endif #if !GTK_CHECK_VERSION(4, 0, 0) , m_pLoop(nullptr) , m_pData(nullptr) #endif { } virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override { css::datatransfer::DataFlavor aFlavor(rFlavor); if (aFlavor.MimeType == "text/plain;charset=utf-16") aFlavor.MimeType = "text/plain;charset=utf-8"; auto it = m_aMimeTypeToGtkType.find(aFlavor.MimeType); if (it == m_aMimeTypeToGtkType.end()) return css::uno::Any(); css::uno::Any aRet; #if !GTK_CHECK_VERSION(4, 0, 0) /* like gtk_clipboard_wait_for_contents run a sub loop * waiting for drag-data-received triggered from * gtk_drag_get_data */ { m_pLoop = g_main_loop_new(nullptr, true); m_pDropTarget->SetFormatConversionRequest(this); gtk_drag_get_data(m_pWidget, m_pContext, it->second, m_nTime); if (g_main_loop_is_running(m_pLoop)) main_loop_run(m_pLoop); g_main_loop_unref(m_pLoop); m_pLoop = nullptr; m_pDropTarget->SetFormatConversionRequest(nullptr); } if (aFlavor.MimeType == "text/plain;charset=utf-8") { OUString aStr; gchar *pText = reinterpret_cast(gtk_selection_data_get_text(m_pData)); if (pText) aStr = OUString(pText, rtl_str_getLength(pText), RTL_TEXTENCODING_UTF8); g_free(pText); aRet <<= aStr.replaceAll("\r\n", "\n"); } else { gint length(0); const guchar *rawdata = gtk_selection_data_get_data_with_length(m_pData, &length); // seen here was rawhide == nullptr and length set to -1 if (rawdata) { css::uno::Sequence aSeq(reinterpret_cast(rawdata), length); aRet <<= aSeq; } } gtk_selection_data_free(m_pData); #else SalInstance* pInstance = GetSalData()->m_pInstance; read_transfer_result aRes; const char *mime_types[] = { it->second.getStr(), nullptr }; gdk_drop_read_async(m_pDrop, mime_types, G_PRIORITY_DEFAULT, nullptr, read_drop_async_completed, &aRes); while (!aRes.bDone) pInstance->DoYield(true, false); if (aFlavor.MimeType == "text/plain;charset=utf-8") aRet <<= aRes.get_as_string(); else aRet <<= aRes.get_as_sequence(); #endif return aRet; } virtual std::vector getTransferDataFlavorsAsVector() override { #if !GTK_CHECK_VERSION(4, 0, 0) std::vector targets; for (GList* l = gdk_drag_context_list_targets(m_pContext); l; l = l->next) targets.push_back(static_cast(l->data)); return GtkTransferable::getTransferDataFlavorsAsVector(targets.data(), targets.size()); #else GdkContentFormats* pFormats = gdk_drop_get_formats(m_pDrop); gsize n_targets; const char * const *targets = gdk_content_formats_get_mime_types(pFormats, &n_targets); return GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets); #endif } #if !GTK_CHECK_VERSION(4, 0, 0) void LoopEnd(GtkSelectionData *pData) { m_pData = pData; g_main_loop_quit(m_pLoop); } #endif }; // For LibreOffice internal D&D we provide the Transferable without Gtk // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this GtkInstDragSource* GtkInstDragSource::g_ActiveDragSource; #if GTK_CHECK_VERSION(4, 0, 0) gboolean GtkSalFrame::signalDragDrop(GtkDropTargetAsync* context, GdkDrop* drop, double x, double y, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); if (!pThis->m_pDropTarget) return false; return pThis->m_pDropTarget->signalDragDrop(context, drop, x, y); } #else gboolean GtkSalFrame::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); if (!pThis->m_pDropTarget) return false; return pThis->m_pDropTarget->signalDragDrop(pWidget, context, x, y, time); } #endif #if !GTK_CHECK_VERSION(4, 0, 0) gboolean GtkInstDropTarget::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time) #else gboolean GtkInstDropTarget::signalDragDrop(GtkDropTargetAsync* context, GdkDrop* drop, double x, double y) #endif { // remove the deferred dragExit, as we'll do a drop #ifndef NDEBUG bool res = #endif g_idle_remove_by_data(this); #ifndef NDEBUG #if !GTK_CHECK_VERSION(4, 0, 0) assert(res); #else (void)res; #endif #endif css::datatransfer::dnd::DropTargetDropEvent aEvent; aEvent.Source = static_cast(this); #if !GTK_CHECK_VERSION(4, 0, 0) aEvent.Context = new GtkDropTargetDropContext(context, time); #else aEvent.Context = new GtkDropTargetDropContext(drop); #endif aEvent.LocationX = x; aEvent.LocationY = y; #if !GTK_CHECK_VERSION(4, 0, 0) aEvent.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context)); #else aEvent.DropAction = GdkToVcl(getPreferredDragAction(GdkToVcl(gdk_drop_get_actions(drop)))); #endif // ACTION_DEFAULT is documented as... // 'This means the user did not press any key during the Drag and Drop operation // and the action that was combined with ACTION_DEFAULT is the system default action' // in tdf#107031 writer won't insert a link when a heading is dragged from the // navigator unless this is set. Its unclear really what ACTION_DEFAULT means, // there is a deprecated 'GDK_ACTION_DEFAULT Means nothing, and should not be used' // possible equivalent in gtk. // So (tdf#109227) set ACTION_DEFAULT if no modifier key is held down #if !GTK_CHECK_VERSION(4,0,0) aEvent.SourceActions = GdkToVcl(gdk_drag_context_get_actions(context)); GdkModifierType mask; gdk_window_get_pointer(widget_get_surface(pWidget), nullptr, nullptr, &mask); #else aEvent.SourceActions = GdkToVcl(gdk_drop_get_actions(drop)); GdkModifierType mask = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(context)); #endif if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) aEvent.DropAction |= css::datatransfer::dnd::DNDConstants::ACTION_DEFAULT; css::uno::Reference xTransferable; // For LibreOffice internal D&D we provide the Transferable without Gtk // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this if (GtkInstDragSource::g_ActiveDragSource) xTransferable = GtkInstDragSource::g_ActiveDragSource->GetTransferrable(); else { #if GTK_CHECK_VERSION(4,0,0) xTransferable = new GtkDnDTransferable(drop); #else xTransferable = new GtkDnDTransferable(context, time, pWidget, this); #endif } aEvent.Transferable = xTransferable; fire_drop(aEvent); return true; } namespace { class GtkDropTargetDragContext : public cppu::WeakImplHelper { #if !GTK_CHECK_VERSION(4, 0, 0) GdkDragContext *m_pContext; guint m_nTime; #else GdkDrop* m_pDrop; #endif public: #if !GTK_CHECK_VERSION(4, 0, 0) GtkDropTargetDragContext(GdkDragContext *pContext, guint nTime) : m_pContext(pContext) , m_nTime(nTime) #else GtkDropTargetDragContext(GdkDrop* pDrop) : m_pDrop(pDrop) #endif { } virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) override { #if !GTK_CHECK_VERSION(4, 0, 0) gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime); #else gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), getPreferredDragAction(dragOperation)); #endif } virtual void SAL_CALL rejectDrag() override { #if !GTK_CHECK_VERSION(4, 0, 0) gdk_drag_status(m_pContext, static_cast(0), m_nTime); #else gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), static_cast(0)); #endif } }; } #if !GTK_CHECK_VERSION(4, 0, 0) void GtkSalFrame::signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); if (!pThis->m_pDropTarget) return; pThis->m_pDropTarget->signalDragDropReceived(pWidget, context, x, y, data, ttype, time); } void GtkInstDropTarget::signalDragDropReceived(GtkWidget* /*pWidget*/, GdkDragContext * /*context*/, gint /*x*/, gint /*y*/, GtkSelectionData* data, guint /*ttype*/, guint /*time*/) { /* * If we get a drop, then we will call like gtk_clipboard_wait_for_contents * with a loop inside a loop to get the right format, so if this is the * case return to the outer loop here with a copy of the desired data * * don't look at me like that. */ if (!m_pFormatConversionRequest) return; m_pFormatConversionRequest->LoopEnd(gtk_selection_data_copy(data)); } #endif #if GTK_CHECK_VERSION(4,0,0) GdkDragAction GtkSalFrame::signalDragMotion(GtkDropTargetAsync *dest, GdkDrop *drop, double x, double y, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); if (!pThis->m_pDropTarget) return GdkDragAction(0); return pThis->m_pDropTarget->signalDragMotion(dest, drop, x, y); } #else gboolean GtkSalFrame::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); if (!pThis->m_pDropTarget) return false; return pThis->m_pDropTarget->signalDragMotion(pWidget, context, x, y, time); } #endif #if !GTK_CHECK_VERSION(4,0,0) gboolean GtkInstDropTarget::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time) #else GdkDragAction GtkInstDropTarget::signalDragMotion(GtkDropTargetAsync *context, GdkDrop *pDrop, double x, double y) #endif { if (!m_bInDrag) { #if !GTK_CHECK_VERSION(4,0,0) GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) : pWidget; gtk_drag_highlight(pHighlightWidget); #else GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) : gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(context)); gtk_widget_set_state_flags(pHighlightWidget, GTK_STATE_FLAG_DROP_ACTIVE, false); #endif } css::datatransfer::dnd::DropTargetDragEnterEvent aEvent; aEvent.Source = static_cast(this); #if !GTK_CHECK_VERSION(4,0,0) rtl::Reference pContext = new GtkDropTargetDragContext(context, time); #else rtl::Reference pContext = new GtkDropTargetDragContext(pDrop); #endif //preliminary accept the Drag and select the preferred action, the fire_* will //inform the original caller of our choice and the callsite can decide //to overrule this choice. i.e. typically here we default to ACTION_MOVE #if !GTK_CHECK_VERSION(4,0,0) sal_Int8 nSourceActions = GdkToVcl(gdk_drag_context_get_actions(context)); GdkModifierType mask; gdk_window_get_pointer(widget_get_surface(pWidget), nullptr, nullptr, &mask); #else sal_Int8 nSourceActions = GdkToVcl(gdk_drop_get_actions(pDrop)); GdkModifierType mask = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(context)); #endif // tdf#124411 default to move if drag originates within LO itself, default // to copy if it comes from outside, this is similar to srcAndDestEqual // in macosx DropTarget::determineDropAction equivalent sal_Int8 nNewDropAction = GtkInstDragSource::g_ActiveDragSource ? css::datatransfer::dnd::DNDConstants::ACTION_MOVE : css::datatransfer::dnd::DNDConstants::ACTION_COPY; // tdf#109227 if a modifier is held down, default to the matching // action for that modifier combo, otherwise pick the preferred // default from the possible source actions if ((mask & GDK_SHIFT_MASK) && !(mask & GDK_CONTROL_MASK)) nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_MOVE; else if ((mask & GDK_CONTROL_MASK) && !(mask & GDK_SHIFT_MASK)) nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_COPY; else if ((mask & GDK_SHIFT_MASK) && (mask & GDK_CONTROL_MASK) ) nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_LINK; nNewDropAction &= nSourceActions; GdkDragAction eAction; if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) && !nNewDropAction) eAction = getPreferredDragAction(nSourceActions); else eAction = getPreferredDragAction(nNewDropAction); #if !GTK_CHECK_VERSION(4,0,0) gdk_drag_status(context, eAction, time); #else gdk_drop_status(pDrop, static_cast(eAction | gdk_drop_get_actions(pDrop)), eAction); #endif aEvent.Context = pContext; aEvent.LocationX = x; aEvent.LocationY = y; //under wayland at least, the action selected by gdk_drag_status on the //context is not immediately available via gdk_drag_context_get_selected_action //so here we set the DropAction from what we selected on the context, not //what the context says is selected aEvent.DropAction = GdkToVcl(eAction); aEvent.SourceActions = nSourceActions; if (!m_bInDrag) { css::uno::Reference xTransferable; // For LibreOffice internal D&D we provide the Transferable without Gtk // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this if (GtkInstDragSource::g_ActiveDragSource) xTransferable = GtkInstDragSource::g_ActiveDragSource->GetTransferrable(); else { #if !GTK_CHECK_VERSION(4,0,0) xTransferable = new GtkDnDTransferable(context, time, pWidget, this); #else xTransferable = new GtkDnDTransferable(pDrop); #endif } css::uno::Sequence aFormats = xTransferable->getTransferDataFlavors(); aEvent.SupportedDataFlavors = aFormats; fire_dragEnter(aEvent); m_bInDrag = true; } else { fire_dragOver(aEvent); } #if !GTK_CHECK_VERSION(4,0,0) return true; #else return eAction; #endif } #if GTK_CHECK_VERSION(4,0,0) void GtkSalFrame::signalDragLeave(GtkDropTargetAsync* pDest, GdkDrop* /*drop*/, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); if (!pThis->m_pDropTarget) return; pThis->m_pDropTarget->signalDragLeave(gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(pDest))); } #else void GtkSalFrame::signalDragLeave(GtkWidget* pWidget, GdkDragContext* /*context*/, guint /*time*/, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); if (!pThis->m_pDropTarget) return; pThis->m_pDropTarget->signalDragLeave(pWidget); } #endif static gboolean lcl_deferred_dragExit(gpointer user_data) { GtkInstDropTarget* pThis = static_cast(user_data); css::datatransfer::dnd::DropTargetEvent aEvent; aEvent.Source = static_cast(pThis); pThis->fire_dragExit(aEvent); return false; } void GtkInstDropTarget::signalDragLeave(GtkWidget *pWidget) { m_bInDrag = false; GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) : pWidget; #if !GTK_CHECK_VERSION(4,0,0) gtk_drag_unhighlight(pHighlightWidget); #else gtk_widget_unset_state_flags(pHighlightWidget, GTK_STATE_FLAG_DROP_ACTIVE); #endif // defer fire_dragExit, since gtk also sends a drag-leave before the drop, while // LO expect to either handle the drop or the exit... at least in Writer. // but since we don't know there will be a drop following the leave, defer the // exit handling to an idle. g_idle_add(lcl_deferred_dragExit, this); } void GtkSalFrame::signalDestroy( GtkWidget* pObj, gpointer frame ) { GtkSalFrame* pThis = static_cast(frame); if( pObj != pThis->m_pWindow ) return; pThis->m_aDamageHandler.damaged = nullptr; pThis->m_aDamageHandler.handle = nullptr; if (pThis->m_pSurface) cairo_surface_set_user_data(pThis->m_pSurface, SvpSalGraphics::getDamageKey(), nullptr, nullptr); pThis->m_pFixedContainer = nullptr; pThis->m_pDrawingArea = nullptr; #if !GTK_CHECK_VERSION(4, 0, 0) pThis->m_pEventBox = nullptr; #endif pThis->m_pTopLevelGrid = nullptr; pThis->m_pWindow = nullptr; pThis->m_xFrameWeld.reset(); pThis->InvalidateGraphics(); } // GtkSalFrame::IMHandler GtkSalFrame::IMHandler::IMHandler( GtkSalFrame* pFrame ) : m_pFrame(pFrame), m_nPrevKeyPresses( 0 ), m_pIMContext( nullptr ), m_bFocused( true ), m_bPreeditJustChanged( false ) { m_aInputEvent.mpTextAttr = nullptr; createIMContext(); } GtkSalFrame::IMHandler::~IMHandler() { // cancel an eventual event posted to begin preedit again GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput ); deleteIMContext(); } void GtkSalFrame::IMHandler::createIMContext() { if( m_pIMContext ) return; m_pIMContext = gtk_im_multicontext_new (); g_signal_connect( m_pIMContext, "commit", G_CALLBACK (signalIMCommit), this ); g_signal_connect( m_pIMContext, "preedit_changed", G_CALLBACK (signalIMPreeditChanged), this ); g_signal_connect( m_pIMContext, "retrieve_surrounding", G_CALLBACK (signalIMRetrieveSurrounding), this ); g_signal_connect( m_pIMContext, "delete_surrounding", G_CALLBACK (signalIMDeleteSurrounding), this ); g_signal_connect( m_pIMContext, "preedit_start", G_CALLBACK (signalIMPreeditStart), this ); g_signal_connect( m_pIMContext, "preedit_end", G_CALLBACK (signalIMPreeditEnd), this ); GetGenericUnixSalData()->ErrorTrapPush(); im_context_set_client_widget(m_pIMContext, m_pFrame->getMouseEventWidget()); #if GTK_CHECK_VERSION(4, 0, 0) gtk_event_controller_key_set_im_context(m_pFrame->m_pKeyController, m_pIMContext); #endif gtk_im_context_focus_in( m_pIMContext ); GetGenericUnixSalData()->ErrorTrapPop(); m_bFocused = true; } void GtkSalFrame::IMHandler::deleteIMContext() { if( !m_pIMContext ) return; // first give IC a chance to deinitialize GetGenericUnixSalData()->ErrorTrapPush(); #if GTK_CHECK_VERSION(4, 0, 0) gtk_event_controller_key_set_im_context(m_pFrame->m_pKeyController, nullptr); #endif im_context_set_client_widget(m_pIMContext, nullptr); GetGenericUnixSalData()->ErrorTrapPop(); // destroy old IC g_object_unref( m_pIMContext ); m_pIMContext = nullptr; } void GtkSalFrame::IMHandler::doCallEndExtTextInput() { m_aInputEvent.mpTextAttr = nullptr; m_pFrame->CallCallbackExc( SalEvent::EndExtTextInput, nullptr ); } void GtkSalFrame::IMHandler::updateIMSpotLocation() { SalExtTextInputPosEvent aPosEvent; m_pFrame->CallCallbackExc( SalEvent::ExtTextInputPos, static_cast(&aPosEvent) ); GdkRectangle aArea; aArea.x = aPosEvent.mnX; aArea.y = aPosEvent.mnY; aArea.width = aPosEvent.mnWidth; aArea.height = aPosEvent.mnHeight; GetGenericUnixSalData()->ErrorTrapPush(); gtk_im_context_set_cursor_location( m_pIMContext, &aArea ); GetGenericUnixSalData()->ErrorTrapPop(); } void GtkSalFrame::IMHandler::sendEmptyCommit() { vcl::DeletionListener aDel( m_pFrame ); SalExtTextInputEvent aEmptyEv; aEmptyEv.mpTextAttr = nullptr; aEmptyEv.maText.clear(); aEmptyEv.mnCursorPos = 0; aEmptyEv.mnCursorFlags = 0; m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast(&aEmptyEv) ); if( ! aDel.isDeleted() ) m_pFrame->CallCallbackExc( SalEvent::EndExtTextInput, nullptr ); } void GtkSalFrame::IMHandler::endExtTextInput( EndExtTextInputFlags /*nFlags*/ ) { gtk_im_context_reset ( m_pIMContext ); if( !m_aInputEvent.mpTextAttr ) return; vcl::DeletionListener aDel( m_pFrame ); // delete preedit in sal (commit an empty string) sendEmptyCommit(); if( ! aDel.isDeleted() ) { // mark previous preedit state again (will e.g. be sent at focus gain) m_aInputEvent.mpTextAttr = m_aInputFlags.data(); if( m_bFocused ) { // begin preedit again GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput ); } } } void GtkSalFrame::IMHandler::focusChanged( bool bFocusIn ) { m_bFocused = bFocusIn; if( bFocusIn ) { GetGenericUnixSalData()->ErrorTrapPush(); gtk_im_context_focus_in( m_pIMContext ); GetGenericUnixSalData()->ErrorTrapPop(); if( m_aInputEvent.mpTextAttr ) { sendEmptyCommit(); // begin preedit again GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput ); } } else { GetGenericUnixSalData()->ErrorTrapPush(); gtk_im_context_focus_out( m_pIMContext ); GetGenericUnixSalData()->ErrorTrapPop(); // cancel an eventual event posted to begin preedit again GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput ); } } #if !GTK_CHECK_VERSION(4, 0, 0) bool GtkSalFrame::IMHandler::handleKeyEvent( GdkEventKey* pEvent ) { vcl::DeletionListener aDel( m_pFrame ); if( pEvent->type == GDK_KEY_PRESS ) { // Add this key press event to the list of previous key presses // to which we compare key release events. If a later key release // event has a matching key press event in this list, we swallow // the key release because some GTK Input Methods don't swallow it // for us. m_aPrevKeyPresses.emplace_back(pEvent ); m_nPrevKeyPresses++; // Also pop off the earliest key press event if there are more than 10 // already. while (m_nPrevKeyPresses > 10) { m_aPrevKeyPresses.pop_front(); m_nPrevKeyPresses--; } GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) ); // #i51353# update spot location on every key input since we cannot // know which key may activate a preedit choice window updateIMSpotLocation(); if( aDel.isDeleted() ) return true; bool bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent ); g_object_unref( pRef ); if( aDel.isDeleted() ) return true; m_bPreeditJustChanged = false; if( bResult ) return true; else { SAL_WARN_IF( m_nPrevKeyPresses <= 0, "vcl.gtk3", "key press has vanished !" ); if( ! m_aPrevKeyPresses.empty() ) // sanity check { // event was not swallowed, do not filter a following // key release event // note: this relies on gtk_im_context_filter_keypress // returning without calling a handler (in the "not swallowed" // case ) which might change the previous key press list so // we would pop the wrong event here m_aPrevKeyPresses.pop_back(); m_nPrevKeyPresses--; } } } // Determine if we got an earlier key press event corresponding to this key release if (pEvent->type == GDK_KEY_RELEASE) { GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) ); bool bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent ); g_object_unref( pRef ); if( aDel.isDeleted() ) return true; m_bPreeditJustChanged = false; auto iter = std::find(m_aPrevKeyPresses.begin(), m_aPrevKeyPresses.end(), pEvent); // If we found a corresponding previous key press event, swallow the release // and remove the earlier key press from our list if (iter != m_aPrevKeyPresses.end()) { m_aPrevKeyPresses.erase(iter); m_nPrevKeyPresses--; return true; } if( bResult ) return true; } return false; } /* FIXME: * #122282# still more hacking: some IMEs never start a preedit but simply commit * in this case we cannot commit a single character. Workaround: do not do the * single key hack for enter or space if the unicode committed does not match */ static bool checkSingleKeyCommitHack( guint keyval, sal_Unicode cCode ) { bool bRet = true; switch( keyval ) { case GDK_KEY_KP_Enter: case GDK_KEY_Return: if( cCode != '\n' && cCode != '\r' ) bRet = false; break; case GDK_KEY_space: case GDK_KEY_KP_Space: if( cCode != ' ' ) bRet = false; break; default: break; } return bRet; } void GtkSalFrame::IMHandler::signalIMCommit( GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler ) { GtkSalFrame::IMHandler* pThis = static_cast(im_handler); SolarMutexGuard aGuard; vcl::DeletionListener aDel( pThis->m_pFrame ); { const bool bWasPreedit = (pThis->m_aInputEvent.mpTextAttr != nullptr) || pThis->m_bPreeditJustChanged; pThis->m_aInputEvent.mpTextAttr = nullptr; pThis->m_aInputEvent.maText = OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 ); pThis->m_aInputEvent.mnCursorPos = pThis->m_aInputEvent.maText.getLength(); pThis->m_aInputEvent.mnCursorFlags = 0; pThis->m_aInputFlags.clear(); /* necessary HACK: all keyboard input comes in here as soon as an IMContext is set * which is logical and consequent. But since even simple input like * comes through the commit signal instead of signalKey * and all kinds of windows only implement KeyInput (e.g. PushButtons, * RadioButtons and a lot of other Controls), will send a single * KeyInput/KeyUp sequence instead of an ExtText event if there * never was a preedit and the text is only one character. * * In this case there the last ExtText event must have been * SalEvent::EndExtTextInput, either because of a regular commit * or because there never was a preedit. */ bool bSingleCommit = false; if( ! bWasPreedit && pThis->m_aInputEvent.maText.getLength() == 1 && ! pThis->m_aPrevKeyPresses.empty() ) { const PreviousKeyPress& rKP = pThis->m_aPrevKeyPresses.back(); sal_Unicode aOrigCode = pThis->m_aInputEvent.maText[0]; if( checkSingleKeyCommitHack( rKP.keyval, aOrigCode ) ) { pThis->m_pFrame->doKeyCallback( rKP.state, rKP.keyval, rKP.hardware_keycode, rKP.group, aOrigCode, true, true ); bSingleCommit = true; } } if( ! bSingleCommit ) { pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast(&pThis->m_aInputEvent)); if( ! aDel.isDeleted() ) pThis->doCallEndExtTextInput(); } if( ! aDel.isDeleted() ) { // reset input event pThis->m_aInputEvent.maText.clear(); pThis->m_aInputEvent.mnCursorPos = 0; pThis->updateIMSpotLocation(); } } } #else void GtkSalFrame::IMHandler::signalIMCommit( GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler ) { GtkSalFrame::IMHandler* pThis = static_cast(im_handler); SolarMutexGuard aGuard; vcl::DeletionListener aDel( pThis->m_pFrame ); { #if 0 const bool bWasPreedit = (pThis->m_aInputEvent.mpTextAttr != nullptr) || pThis->m_bPreeditJustChanged; #endif pThis->m_aInputEvent.mpTextAttr = nullptr; pThis->m_aInputEvent.maText = OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 ); pThis->m_aInputEvent.mnCursorPos = pThis->m_aInputEvent.maText.getLength(); pThis->m_aInputEvent.mnCursorFlags = 0; pThis->m_aInputFlags.clear(); /* necessary HACK: all keyboard input comes in here as soon as an IMContext is set * which is logical and consequent. But since even simple input like * comes through the commit signal instead of signalKey * and all kinds of windows only implement KeyInput (e.g. PushButtons, * RadioButtons and a lot of other Controls), will send a single * KeyInput/KeyUp sequence instead of an ExtText event if there * never was a preedit and the text is only one character. * * In this case there the last ExtText event must have been * SalEvent::EndExtTextInput, either because of a regular commit * or because there never was a preedit. */ bool bSingleCommit = false; #if 0 // TODO this needs a rethink to work again if necessary if( ! bWasPreedit && pThis->m_aInputEvent.maText.getLength() == 1 && ! pThis->m_aPrevKeyPresses.empty() ) { const PreviousKeyPress& rKP = pThis->m_aPrevKeyPresses.back(); sal_Unicode aOrigCode = pThis->m_aInputEvent.maText[0]; if( checkSingleKeyCommitHack( rKP.keyval, aOrigCode ) ) { pThis->m_pFrame->doKeyCallback( rKP.state, rKP.keyval, rKP.hardware_keycode, rKP.group, aOrigCode, true, true ); bSingleCommit = true; } } #endif if( ! bSingleCommit ) { pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast(&pThis->m_aInputEvent)); if( ! aDel.isDeleted() ) pThis->doCallEndExtTextInput(); } if( ! aDel.isDeleted() ) { // reset input event pThis->m_aInputEvent.maText.clear(); pThis->m_aInputEvent.mnCursorPos = 0; pThis->updateIMSpotLocation(); } } } #endif OUString GtkSalFrame::GetPreeditDetails(GtkIMContext* pIMContext, std::vector& rInputFlags, sal_Int32& rCursorPos, sal_uInt8& rCursorFlags) { char* pText = nullptr; PangoAttrList* pAttrs = nullptr; gint nCursorPos = 0; gtk_im_context_get_preedit_string( pIMContext, &pText, &pAttrs, &nCursorPos ); gint nUtf8Len = pText ? strlen(pText) : 0; OUString sText = pText ? OUString(pText, nUtf8Len, RTL_TEXTENCODING_UTF8) : OUString(); std::vector aUtf16Offsets; for (sal_Int32 nUtf16Offset = 0; nUtf16Offset < sText.getLength(); sText.iterateCodePoints(&nUtf16Offset)) aUtf16Offsets.push_back(nUtf16Offset); sal_Int32 nUtf32Len = aUtf16Offsets.size(); // from the above loop filling aUtf16Offsets, we know that its size() fits into sal_Int32 aUtf16Offsets.push_back(sText.getLength()); // sanitize the CurPos which is in utf-32 if (nCursorPos < 0) nCursorPos = 0; else if (nCursorPos > nUtf32Len) nCursorPos = nUtf32Len; rCursorPos = aUtf16Offsets[nCursorPos]; rCursorFlags = 0; rInputFlags.resize(std::max(1, static_cast(sText.getLength())), ExtTextInputAttr::NONE); PangoAttrIterator *iter = pango_attr_list_get_iterator(pAttrs); do { GSList *attr_list = nullptr; GSList *tmp_list = nullptr; gint nUtf8Start, nUtf8End; ExtTextInputAttr sal_attr = ExtTextInputAttr::NONE; // docs say... "Get the range of the current segment ... the stored // return values are signed, not unsigned like the values in // PangoAttribute", which implies that the units are otherwise the same // as that of PangoAttribute whose docs state these units are "in // bytes" // so this is the utf8 range pango_attr_iterator_range(iter, &nUtf8Start, &nUtf8End); // sanitize the utf8 range nUtf8Start = std::min(nUtf8Start, nUtf8Len); nUtf8End = std::min(nUtf8End, nUtf8Len); if (nUtf8Start >= nUtf8End) continue; // get the utf32 range sal_Int32 nUtf32Start = g_utf8_pointer_to_offset(pText, pText + nUtf8Start); sal_Int32 nUtf32End = g_utf8_pointer_to_offset(pText, pText + nUtf8End); // sanitize the utf32 range nUtf32Start = std::min(nUtf32Start, nUtf32Len); nUtf32End = std::min(nUtf32End, nUtf32Len); if (nUtf32Start >= nUtf32End) continue; tmp_list = attr_list = pango_attr_iterator_get_attrs (iter); while (tmp_list) { PangoAttribute *pango_attr = static_cast(tmp_list->data); switch (pango_attr->klass->type) { case PANGO_ATTR_BACKGROUND: sal_attr |= ExtTextInputAttr::Highlight; rCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE; break; case PANGO_ATTR_UNDERLINE: sal_attr |= ExtTextInputAttr::Underline; break; case PANGO_ATTR_STRIKETHROUGH: sal_attr |= ExtTextInputAttr::RedText; break; default: break; } pango_attribute_destroy (pango_attr); tmp_list = tmp_list->next; } if (sal_attr == ExtTextInputAttr::NONE) sal_attr |= ExtTextInputAttr::Underline; g_slist_free (attr_list); // Set the sal attributes on our text // rhbz#1648281 apply over our utf-16 range derived from the input utf-32 range for (sal_Int32 i = aUtf16Offsets[nUtf32Start]; i < aUtf16Offsets[nUtf32End]; ++i) { SAL_WARN_IF(i >= static_cast(rInputFlags.size()), "vcl.gtk3", "pango attrib out of range. Broken range: " << aUtf16Offsets[nUtf32Start] << "," << aUtf16Offsets[nUtf32End] << " Legal range: 0," << rInputFlags.size()); if (i >= static_cast(rInputFlags.size())) continue; rInputFlags[i] |= sal_attr; } } while (pango_attr_iterator_next (iter)); pango_attr_iterator_destroy(iter); g_free( pText ); pango_attr_list_unref( pAttrs ); return sText; } void GtkSalFrame::IMHandler::signalIMPreeditChanged( GtkIMContext* pIMContext, gpointer im_handler ) { GtkSalFrame::IMHandler* pThis = static_cast(im_handler); sal_Int32 nCursorPos(0); sal_uInt8 nCursorFlags(0); std::vector aInputFlags; OUString sText = GtkSalFrame::GetPreeditDetails(pIMContext, aInputFlags, nCursorPos, nCursorFlags); if (sText.isEmpty() && pThis->m_aInputEvent.maText.isEmpty()) { // change from nothing to nothing -> do not start preedit // e.g. this will activate input into a calc cell without // user input return; } pThis->m_bPreeditJustChanged = true; bool bEndPreedit = sText.isEmpty() && pThis->m_aInputEvent.mpTextAttr != nullptr; pThis->m_aInputEvent.maText = sText; pThis->m_aInputEvent.mnCursorPos = nCursorPos; pThis->m_aInputEvent.mnCursorFlags = nCursorFlags; pThis->m_aInputFlags = aInputFlags; pThis->m_aInputEvent.mpTextAttr = pThis->m_aInputFlags.data(); SolarMutexGuard aGuard; vcl::DeletionListener aDel( pThis->m_pFrame ); pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast(&pThis->m_aInputEvent)); if( bEndPreedit && ! aDel.isDeleted() ) pThis->doCallEndExtTextInput(); if( ! aDel.isDeleted() ) pThis->updateIMSpotLocation(); } void GtkSalFrame::IMHandler::signalIMPreeditStart( GtkIMContext*, gpointer /*im_handler*/ ) { } void GtkSalFrame::IMHandler::signalIMPreeditEnd( GtkIMContext*, gpointer im_handler ) { GtkSalFrame::IMHandler* pThis = static_cast(im_handler); pThis->m_bPreeditJustChanged = true; SolarMutexGuard aGuard; vcl::DeletionListener aDel( pThis->m_pFrame ); pThis->doCallEndExtTextInput(); if( ! aDel.isDeleted() ) pThis->updateIMSpotLocation(); } gboolean GtkSalFrame::IMHandler::signalIMRetrieveSurrounding( GtkIMContext* pContext, gpointer im_handler ) { GtkSalFrame::IMHandler* pThis = static_cast(im_handler); SalSurroundingTextRequestEvent aEvt; aEvt.maText.clear(); aEvt.mnStart = aEvt.mnEnd = 0; SolarMutexGuard aGuard; pThis->m_pFrame->CallCallback(SalEvent::SurroundingTextRequest, &aEvt); OString sUTF = OUStringToOString(aEvt.maText, RTL_TEXTENCODING_UTF8); OUString sCursorText(aEvt.maText.copy(0, aEvt.mnStart)); gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(), OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength()); return true; } Selection GtkSalFrame::CalcDeleteSurroundingSelection(const OUString& rSurroundingText, sal_Int32 nCursorIndex, int nOffset, int nChars) { Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32); if (nCursorIndex == -1) return aInvalid; // Note that offset and n_chars are in characters not in bytes // which differs from the usage other places in GtkIMContext if (nOffset > 0) { while (nOffset && nCursorIndex < rSurroundingText.getLength()) { rSurroundingText.iterateCodePoints(&nCursorIndex, 1); --nOffset; } } else if (nOffset < 0) { while (nOffset && nCursorIndex > 0) { rSurroundingText.iterateCodePoints(&nCursorIndex, -1); ++nOffset; } } if (nOffset) { SAL_WARN("vcl.gtk", "IM delete-surrounding, unable to move to offset: " << nOffset); return aInvalid; } sal_Int32 nCursorEndIndex(nCursorIndex); sal_Int32 nCount(0); while (nCount < nChars && nCursorEndIndex < rSurroundingText.getLength()) { rSurroundingText.iterateCodePoints(&nCursorEndIndex, 1); ++nCount; } if (nCount != nChars) { SAL_WARN("vcl.gtk", "IM delete-surrounding, unable to select: " << nChars << " characters"); return aInvalid; } return Selection(nCursorIndex, nCursorEndIndex); } gboolean GtkSalFrame::IMHandler::signalIMDeleteSurrounding( GtkIMContext*, gint offset, gint nchars, gpointer im_handler ) { GtkSalFrame::IMHandler* pThis = static_cast(im_handler); // First get the surrounding text SalSurroundingTextRequestEvent aSurroundingTextEvt; aSurroundingTextEvt.maText.clear(); aSurroundingTextEvt.mnStart = aSurroundingTextEvt.mnEnd = 0; SolarMutexGuard aGuard; pThis->m_pFrame->CallCallback(SalEvent::SurroundingTextRequest, &aSurroundingTextEvt); // Turn offset, nchars into a utf-16 selection Selection aSelection = GtkSalFrame::CalcDeleteSurroundingSelection(aSurroundingTextEvt.maText, aSurroundingTextEvt.mnStart, offset, nchars); Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32); if (aSelection == aInvalid) return false; SalSurroundingTextSelectionChangeEvent aEvt; aEvt.mnStart = aSelection.Min(); aEvt.mnEnd = aSelection.Max(); pThis->m_pFrame->CallCallback(SalEvent::DeleteSurroundingTextRequest, &aEvt); aSelection = Selection(aEvt.mnStart, aEvt.mnEnd); if (aSelection == aInvalid) return false; return true; } Size GtkSalDisplay::GetScreenSize( int nDisplayScreen ) { tools::Rectangle aRect = m_pSys->GetDisplayScreenPosSizePixel( nDisplayScreen ); return Size( aRect.GetWidth(), aRect.GetHeight() ); } sal_uIntPtr GtkSalFrame::GetNativeWindowHandle(GtkWidget *pWidget) { GdkSurface* pSurface = widget_get_surface(pWidget); GdkDisplay *pDisplay = getGdkDisplay(); #if defined(GDK_WINDOWING_X11) if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay)) { return gdk_x11_surface_get_xid(pSurface); } #endif #if defined(GDK_WINDOWING_WAYLAND) if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay)) { return reinterpret_cast(gdk_wayland_surface_get_wl_surface(pSurface)); } #endif return 0; } void GtkInstDragSource::set_datatransfer(const css::uno::Reference& rTrans, const css::uno::Reference& rListener) { m_xListener = rListener; m_xTrans = rTrans; } void GtkInstDragSource::setActiveDragSource() { // For LibreOffice internal D&D we provide the Transferable without Gtk // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this g_ActiveDragSource = this; g_DropSuccessSet = false; g_DropSuccess = false; } #if !GTK_CHECK_VERSION(4, 0, 0) std::vector GtkInstDragSource::FormatsToGtk(const css::uno::Sequence &rFormats) { return m_aConversionHelper.FormatsToGtk(rFormats); } #endif void GtkInstDragSource::startDrag(const datatransfer::dnd::DragGestureEvent& rEvent, sal_Int8 sourceActions, sal_Int32 /*cursor*/, sal_Int32 /*image*/, const css::uno::Reference& rTrans, const css::uno::Reference& rListener) { set_datatransfer(rTrans, rListener); if (m_pFrame) { setActiveDragSource(); m_pFrame->startDrag(rEvent, rTrans, m_aConversionHelper ,VclToGdk(sourceActions)); } else dragFailed(); } void GtkSalFrame::startDrag(const css::datatransfer::dnd::DragGestureEvent& rEvent, const css::uno::Reference& rTrans, VclToGtkHelper& rConversionHelper, GdkDragAction sourceActions) { SolarMutexGuard aGuard; assert(m_pDragSource); #if GTK_CHECK_VERSION(4, 0, 0) GdkSeat *pSeat = gdk_display_get_default_seat(getGdkDisplay()); GdkDrag* pDrag = gdk_drag_begin(widget_get_surface(getMouseEventWidget()), gdk_seat_get_pointer(pSeat), transerable_content_new(&rConversionHelper, rTrans.get()), sourceActions, rEvent.DragOriginX, rEvent.DragOriginY); g_signal_connect(G_OBJECT(pDrag), "drop-performed", G_CALLBACK(signalDragEnd), this); g_signal_connect(G_OBJECT(pDrag), "cancel", G_CALLBACK(signalDragFailed), this); g_signal_connect(G_OBJECT(pDrag), "dnd-finished", G_CALLBACK(signalDragDelete), this); #else auto aFormats = rTrans->getTransferDataFlavors(); auto aGtkTargets = rConversionHelper.FormatsToGtk(aFormats); GtkTargetList *pTargetList = gtk_target_list_new(aGtkTargets.data(), aGtkTargets.size()); gint nDragButton = 1; // default to left button css::awt::MouseEvent aEvent; if (rEvent.Event >>= aEvent) { if (aEvent.Buttons & css::awt::MouseButton::LEFT ) nDragButton = 1; else if (aEvent.Buttons & css::awt::MouseButton::RIGHT) nDragButton = 3; else if (aEvent.Buttons & css::awt::MouseButton::MIDDLE) nDragButton = 2; } GdkEvent aFakeEvent; memset(&aFakeEvent, 0, sizeof(GdkEvent)); aFakeEvent.type = GDK_BUTTON_PRESS; aFakeEvent.button.window = widget_get_surface(getMouseEventWidget()); aFakeEvent.button.time = GDK_CURRENT_TIME; aFakeEvent.button.device = gtk_get_current_event_device(); // if no current event to determine device, or (tdf#140272) the device will be unsuitable then find an // appropiate device to use. if (!aFakeEvent.button.device || !gdk_device_get_window_at_position(aFakeEvent.button.device, nullptr, nullptr)) { GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(getGdkDisplay()); GList* pDevices = gdk_device_manager_list_devices(pDeviceManager, GDK_DEVICE_TYPE_MASTER); for (GList* pEntry = pDevices; pEntry; pEntry = pEntry->next) { GdkDevice* pDevice = static_cast(pEntry->data); if (gdk_device_get_source(pDevice) == GDK_SOURCE_KEYBOARD) continue; if (gdk_device_get_window_at_position(pDevice, nullptr, nullptr)) { aFakeEvent.button.device = pDevice; break; } } g_list_free(pDevices); } GdkDragContext *pDrag; if (!aFakeEvent.button.device || !gdk_device_get_window_at_position(aFakeEvent.button.device, nullptr, nullptr)) pDrag = nullptr; else pDrag = gtk_drag_begin_with_coordinates(getMouseEventWidget(), pTargetList, sourceActions, nDragButton, &aFakeEvent, rEvent.DragOriginX, rEvent.DragOriginY); gtk_target_list_unref(pTargetList); for (auto &a : aGtkTargets) g_free(a.target); #endif if (!pDrag) m_pDragSource->dragFailed(); } void GtkInstDragSource::dragFailed() { if (m_xListener.is()) { datatransfer::dnd::DragSourceDropEvent aEv; aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_NONE; aEv.DropSuccess = false; auto xListener = m_xListener; m_xListener.clear(); xListener->dragDropEnd(aEv); } } #if GTK_CHECK_VERSION(4, 0, 0) void GtkSalFrame::signalDragFailed(GdkDrag* /*drag*/, GdkDragCancelReason /*reason*/, gpointer frame) #else gboolean GtkSalFrame::signalDragFailed(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkDragResult /*result*/, gpointer frame) #endif { GtkSalFrame* pThis = static_cast(frame); if (pThis->m_pDragSource) pThis->m_pDragSource->dragFailed(); #if !GTK_CHECK_VERSION(4, 0, 0) return false; #endif } void GtkInstDragSource::dragDelete() { if (m_xListener.is()) { datatransfer::dnd::DragSourceDropEvent aEv; aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_MOVE; aEv.DropSuccess = true; auto xListener = m_xListener; m_xListener.clear(); xListener->dragDropEnd(aEv); } } #if GTK_CHECK_VERSION(4, 0, 0) void GtkSalFrame::signalDragDelete(GdkDrag* /*context*/, gpointer frame) #else void GtkSalFrame::signalDragDelete(GtkWidget* /*widget*/, GdkDragContext* /*context*/, gpointer frame) #endif { GtkSalFrame* pThis = static_cast(frame); if (!pThis->m_pDragSource) return; pThis->m_pDragSource->dragDelete(); } #if GTK_CHECK_VERSION(4, 0, 0) void GtkInstDragSource::dragEnd(GdkDrag* context) #else void GtkInstDragSource::dragEnd(GdkDragContext* context) #endif { if (m_xListener.is()) { datatransfer::dnd::DragSourceDropEvent aEv; #if GTK_CHECK_VERSION(4, 0, 0) aEv.DropAction = GdkToVcl(gdk_drag_get_selected_action(context)); #else aEv.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context)); #endif // an internal drop can accept the drop but fail with dropComplete( false ) // this is different than the GTK API if (g_DropSuccessSet) aEv.DropSuccess = g_DropSuccess; else aEv.DropSuccess = true; auto xListener = m_xListener; m_xListener.clear(); xListener->dragDropEnd(aEv); } g_ActiveDragSource = nullptr; } #if GTK_CHECK_VERSION(4, 0, 0) void GtkSalFrame::signalDragEnd(GdkDrag* context, gpointer frame) #else void GtkSalFrame::signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer frame) #endif { GtkSalFrame* pThis = static_cast(frame); if (!pThis->m_pDragSource) return; pThis->m_pDragSource->dragEnd(context); } #if !GTK_CHECK_VERSION(4, 0, 0) void GtkInstDragSource::dragDataGet(GtkSelectionData *data, guint info) { m_aConversionHelper.setSelectionData(m_xTrans, data, info); } void GtkSalFrame::signalDragDataGet(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkSelectionData *data, guint info, guint /*time*/, gpointer frame) { GtkSalFrame* pThis = static_cast(frame); if (!pThis->m_pDragSource) return; pThis->m_pDragSource->dragDataGet(data, info); } #endif bool GtkSalFrame::CallCallbackExc(SalEvent nEvent, const void* pEvent) const { SolarMutexGuard aGuard; bool nRet = false; try { nRet = CallCallback(nEvent, pEvent); } catch (...) { GtkSalData *pSalData = static_cast(GetSalData()); pSalData->setException(std::current_exception()); } return nRet; } #if !GTK_CHECK_VERSION(4, 0, 0) void GtkSalFrame::nopaint_container_resize_children(GtkContainer *pContainer) { bool bOrigSalObjectSetPosSize = m_bSalObjectSetPosSize; m_bSalObjectSetPosSize = true; gtk_container_resize_children(pContainer); m_bSalObjectSetPosSize = bOrigSalObjectSetPosSize; } #endif GdkEvent* GtkSalFrame::makeFakeKeyPress(GtkWidget* pWidget) { #if !GTK_CHECK_VERSION(4, 0, 0) GdkEvent *event = gdk_event_new(GDK_KEY_PRESS); event->key.window = GDK_WINDOW(g_object_ref(widget_get_surface(pWidget))); GdkSeat *seat = gdk_display_get_default_seat(gtk_widget_get_display(pWidget)); gdk_event_set_device(event, gdk_seat_get_keyboard(seat)); event->key.send_event = 1 /* TRUE */; event->key.time = gtk_get_current_event_time(); event->key.state = 0; event->key.keyval = 0; event->key.length = 0; event->key.string = nullptr; event->key.hardware_keycode = 0; event->key.group = 0; event->key.is_modifier = false; return event; #else (void)pWidget; return nullptr; #endif } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */