libreoffice/vcl/unx/gtk3/gtkframe.cxx
Sahil Gautam ddeeca35d9 tdf#163620 [API CHANGE] Add UI for libreoffice themes
Instead of Color, we have Light and  Dark in the registry. So each theme extension
will specify  dark and  light color  values for  each "customizable  element" like
DocColor etc. Under appearance we have three radio buttons - light/dark/system. If
system  is selected  then light/dark  colors are  switched based  on the  system's
theme.  if  explicitly light/dark  is  selected  - that  color  is  used from  the
registry.

ColorConfigValue now has three entries  nColor, nLightColor, nDarkColor. nColor is
used as a cache for the color being used at the moment. This is to avoid otherwise
expensive  function calls  + hundreds  of modifications  in the  codebase just  to
change nColor. nColor  is cached either when  the theme is loaded  or when changes
are committed in `ColorConfig_Impl::ImplCommit()`.

Now, if  Automatic theme is selected  then themes is disabled  and the application
uses the system  colors. If some other scheme is  selected like "CustomTheme" etc,
then LibreOffice themes/UI color customization is enabled and the theme colors are
used.

Instead of a scroll window, now we have  a combobox for the registry entries and a
single  color dropdown  to select  the  color value.  This color  dropdown is  for
convinience in case the user wants  to change some specific color that's bothering
him. For themeing, theme extensions should be used.

API CHANGE
+ remove Color in favour of Light and Dark
+ AppBackground has additional two - BackgroundType and Bitmap
+ remove officecfg::Office::Common::Misc::Appearnce in favor of
  officecfg::Office::Common::Appearance::ApplicationAppearance
+ move LibreofficeTheme under officecfg::Office::Common::Appearance

UI
+ it looks like https://i.imgur.com/UMxjfuC.png which is a little
  different from how the [mockup] on the ticket describes it, and that's
  because of lack of time due to upcomming feature freeze.

+ system/light/dark allow the user to switch between light/dark modes
  based on either the system theme (system) or by manually specifying
  light/dark.

+ ui themeing and color customization is disabled when automatic theme
  is selected, and enabled otherwise.

[mockup]: https://bug-attachments.documentfoundation.org/attachment.cgi?id=197469

Change-Id: I1a7f70dfe44b81f863814f87e8d46e146c0e3d5a
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/174835
Reviewed-by: Heiko Tietze <heiko.tietze@documentfoundation.org>
Tested-by: Jenkins
Reviewed-by: Sahil Gautam <sahil.gautam.extern@allotropia.de>
2024-12-18 16:24:08 +01:00

6437 lines
224 KiB
C++

/* -*- 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 <config_version.h>
#include <unx/gtk/gtkframe.hxx>
#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <unx/gtk/gtkgdi.hxx>
#include <unx/gtk/gtksalmenu.hxx>
#include <unx/gtk/hudawareness.h>
#include <vcl/event.hxx>
#include <vcl/i18nhelp.hxx>
#include <vcl/keycodes.hxx>
#include <unx/geninst.h>
#include <headless/svpgdi.hxx>
#include <sal/log.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <vcl/toolkit/floatwin.hxx>
#include <vcl/toolkit/unowrap.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <vcl/window.hxx>
#include <vcl/settings.hxx>
#include <gtk/gtk.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <unx/gtk/gtkbackend.hxx>
#include <strings.hrc>
#include <window.h>
#include <basegfx/vector/b2ivector.hxx>
#include <officecfg/Office/Common.hxx>
#include <dlfcn.h>
#include <algorithm>
#if OSL_DEBUG_LEVEL > 1
# include <cstdio>
#endif
#include <i18nlangtag/mslangid.hxx>
#include <cstdlib>
#include <cmath>
#include <com/sun/star/awt/MouseButton.hpp>
#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/util/XModifiable.hpp>
#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;
static void EnsureSessionBus()
{
if (!pSessionBus)
pSessionBus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, 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;
case GDK_KEY_braceright: nCode = KEY_RIGHTCURLYBRACKET; break;
case GDK_KEY_numbersign: nCode = KEY_NUMBERSIGN; break;
case GDK_KEY_Forward: nCode = KEY_XF86FORWARD; break;
case GDK_KEY_Back: nCode = KEY_XF86BACK; break;
case GDK_KEY_colon: nCode = KEY_COLON; 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)
{
// tdf#152404 Commit uncommitted text before dispatching key shortcuts. In
// certain cases such as pressing Control-Alt-C in a Writer document while
// there is uncommitted text will call GtkSalFrame::EndExtTextInput() which
// will dispatch a SalEvent::EndExtTextInput event. Writer's handler for that
// event will delete the uncommitted text and then insert the committed text
// but LibreOffice will crash when deleting the uncommitted text because
// deletion of the text also removes and deletes the newly inserted comment.
if (m_pIMHandler && !m_pIMHandler->m_aInputEvent.maText.isEmpty() && (aEvent.mnCode & (KEY_MOD1 | KEY_MOD2)))
m_pIMHandler->doCallEndExtTextInput();
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<GActionGroup*>(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.
EnsureSessionBus();
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<GtkSalMenu*>(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");
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.
EnsureSessionBus();
if (!pSessionBus)
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);
if (m_nPortalSettingChangedSignalId)
g_signal_handler_disconnect(m_pSettingsPortal, m_nPortalSettingChangedSignalId);
if (m_pSettingsPortal)
g_object_unref(m_pSettingsPortal);
if (m_nSessionClientSignalId)
g_signal_handler_disconnect(m_pSessionClient, m_nSessionClientSignalId);
if (m_pSessionClient)
g_object_unref(m_pSessionClient);
if (m_pSessionManager)
g_object_unref(m_pSessionManager);
}
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.x(), nY - m_pParent->maGeometry.y() );
}
return;
}
#if GTK_CHECK_VERSION(4,0,0)
if (GTK_IS_POPOVER(m_pWindow))
{
GdkRectangle aRect;
aRect.x = nX;
aRect.y = nY;
aRect.width = 1;
aRect.height = 1;
gtk_popover_set_pointing_to(GTK_POPOVER(m_pWindow), &aRect);
return;
}
#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 );
#if GTK_CHECK_VERSION(4,0,0)
gtk_widget_set_size_request(GTK_WIDGET(m_pDrawingArea), nWidth, nHeight );
#endif
}
void GtkSalFrame::window_resize(tools::Long nWidth, tools::Long nHeight)
{
m_nWidthRequest = nWidth;
m_nHeightRequest = nHeight;
if (!GTK_IS_WINDOW(m_pWindow))
{
#if GTK_CHECK_VERSION(4,0,0)
gtk_widget_set_size_request(GTK_WIDGET(m_pDrawingArea), nWidth, nHeight);
#endif
return;
}
gtk_window_set_default_size(GTK_WINDOW(m_pWindow), nWidth, nHeight);
#if !GTK_CHECK_VERSION(4,0,0)
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;
}
static void
ooo_fixed_class_init(gpointer klass_, gpointer)
{
auto const klass = static_cast<GtkFixedClass *>(klass_);
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;
}
/*
* 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 */
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;
}
#endif
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.x(), maGeometry.y() );
maGeometry.setScreen(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<GtkSalFrame*>(handle);
pThis->damaged(nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight);
}
#if !GTK_CHECK_VERSION(4,0,0)
static void notifyUnref(gpointer data, GObject *) { g_object_unref(data); }
#endif
void GtkSalFrame::InitCommon()
{
m_pSurface = nullptr;
m_nGrabLevel = 0;
m_bSalObjectSetPosSize = false;
m_nPortalSettingChangedSignalId = 0;
m_nSessionClientSignalId = 0;
m_pSettingsPortal = nullptr;
m_pSessionManager = nullptr;
m_pSessionClient = nullptr;
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(g_object_new( ooo_fixed_get_type(), nullptr ));
m_pDrawingArea = GTK_DRAWING_AREA(gtk_drawing_area_new());
#endif
if (GTK_IS_WINDOW(m_pWindow))
{
Size aDefWindowSize = calcDefaultSize();
gtk_window_set_default_size(GTK_WINDOW(m_pWindow), aDefWindowSize.Width(), aDefWindowSize.Height());
}
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_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
#if GTK_CHECK_VERSION(4,0,0)
GtkGesture* pZoomGesture = gtk_gesture_zoom_new();
gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pZoomGesture));
#else
GtkGesture* pZoomGesture = gtk_gesture_zoom_new(GTK_WIDGET(pEventWidget));
g_object_weak_ref(G_OBJECT(pEventWidget), notifyUnref, pZoomGesture);
#endif
gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(pZoomGesture),
GTK_PHASE_TARGET);
// Note that the default zoom gesture signal handler needs to run first to setup correct
// scale delta. Otherwise the first "begin" event will always contain scale delta of infinity.
g_signal_connect_after(pZoomGesture, "begin", G_CALLBACK(signalZoomBegin), this);
g_signal_connect_after(pZoomGesture, "update", G_CALLBACK(signalZoomUpdate), this);
g_signal_connect_after(pZoomGesture, "end", G_CALLBACK(signalZoomEnd), this);
#if GTK_CHECK_VERSION(4,0,0)
GtkGesture* pRotateGesture = gtk_gesture_rotate_new();
gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pRotateGesture));
#else
GtkGesture* pRotateGesture = gtk_gesture_rotate_new(GTK_WIDGET(pEventWidget));
g_object_weak_ref(G_OBJECT(pEventWidget), notifyUnref, pRotateGesture);
#endif
gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(pRotateGesture),
GTK_PHASE_TARGET);
g_signal_connect(pRotateGesture, "begin", G_CALLBACK(signalRotateBegin), this);
g_signal_connect(pRotateGesture, "update", G_CALLBACK(signalRotateUpdate), this);
g_signal_connect(pRotateGesture, "end", G_CALLBACK(signalRotateEnd), this);
//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), notifyUnref, 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), notifyUnref, 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<GdkToplevelState>(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<PointerStyle>(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 | GDK_TOUCHPAD_GESTURE_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<sal_IntPtr>(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.setPosSize({ -1, -1 }, aDefSize);
maGeometry.setDecorations(0, 0, 0, 0);
}
updateScreenNumber();
SetIcon(SV_ICON_ID_OFFICE);
}
GtkSalFrame *GtkSalFrame::getFromWindow( GtkWidget *pWindow )
{
return static_cast<GtkSalFrame *>(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
}
namespace
{
enum ColorScheme
{
DEFAULT,
PREFER_DARK,
PREFER_LIGHT
};
void ReadColorScheme(GDBusProxy* proxy, GVariant** out)
{
g_autoptr (GVariant) ret =
g_dbus_proxy_call_sync(proxy, "Read",
g_variant_new ("(ss)", "org.freedesktop.appearance", "color-scheme"),
G_DBUS_CALL_FLAGS_NONE, G_MAXINT, nullptr, nullptr);
if (!ret)
return;
g_autoptr (GVariant) child = nullptr;
g_variant_get(ret, "(v)", &child);
g_variant_get(child, "v", out);
return;
}
}
void GtkSalFrame::SetColorScheme(GVariant* variant)
{
if (!m_pWindow)
return;
guint32 color_scheme;
switch (MiscSettings::GetAppColorMode())
{
default:
case 0: // Auto
{
if (variant)
{
color_scheme = g_variant_get_uint32(variant);
if (color_scheme > PREFER_LIGHT)
color_scheme = DEFAULT;
}
else
color_scheme = DEFAULT;
break;
}
case 1: // Light
color_scheme = PREFER_LIGHT;
break;
case 2: // Dark
color_scheme = PREFER_DARK;
break;
}
bool bDarkIconTheme(color_scheme == PREFER_DARK);
GtkSettings* pSettings = gtk_widget_get_settings(m_pWindow);
g_object_set(pSettings, "gtk-application-prefer-dark-theme", bDarkIconTheme, nullptr);
}
bool GtkSalFrame::GetUseDarkMode() const
{
if (!m_pWindow)
return false;
GtkSettings* pSettings = gtk_widget_get_settings(m_pWindow);
gboolean bDarkIconTheme = false;
g_object_get(pSettings, "gtk-application-prefer-dark-theme", &bDarkIconTheme, nullptr);
return bDarkIconTheme;
}
bool GtkSalFrame::GetUseReducedAnimation() const
{
if (!m_pWindow)
return false;
GtkSettings* pSettings = gtk_widget_get_settings(m_pWindow);
gboolean bAnimations;
g_object_get(pSettings, "gtk-enable-animations", &bAnimations, nullptr);
return !bAnimations;
}
static void settings_portal_changed_cb(GDBusProxy*, const char*, const char* signal_name,
GVariant* parameters, gpointer frame)
{
if (g_strcmp0(signal_name, "SettingChanged"))
return;
g_autoptr (GVariant) value = nullptr;
const char *name_space;
const char *name;
g_variant_get(parameters, "(&s&sv)", &name_space, &name, &value);
if (g_strcmp0(name_space, "org.freedesktop.appearance") ||
g_strcmp0(name, "color-scheme"))
return;
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
pThis->SetColorScheme(value);
}
void GtkSalFrame::ListenPortalSettings()
{
EnsureSessionBus();
if (!pSessionBus)
return;
m_pSettingsPortal = g_dbus_proxy_new_sync(pSessionBus,
G_DBUS_PROXY_FLAGS_NONE,
nullptr,
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Settings",
nullptr,
nullptr);
UpdateDarkMode();
if (!m_pSettingsPortal)
return;
m_nPortalSettingChangedSignalId = g_signal_connect(m_pSettingsPortal, "g-signal", G_CALLBACK(settings_portal_changed_cb), this);
}
static void session_client_response(GDBusProxy* client_proxy)
{
g_dbus_proxy_call(client_proxy,
"EndSessionResponse",
g_variant_new ("(bs)", true, ""),
G_DBUS_CALL_FLAGS_NONE,
G_MAXINT,
nullptr, nullptr, nullptr);
}
// unset documents "modify" flag so they won't veto closing
static void clear_modify_and_terminate()
{
const css::uno::Reference<css::uno::XComponentContext>& xContext = ::comphelper::getProcessComponentContext();
uno::Reference<frame::XDesktop> xDesktop(frame::Desktop::create(xContext));
uno::Reference<css::container::XEnumeration> xComponents = xDesktop->getComponents()->createEnumeration();
while (xComponents->hasMoreElements())
{
css::uno::Reference<css::util::XModifiable> xModifiable(xComponents->nextElement(), css::uno::UNO_QUERY);
if (xModifiable)
xModifiable->setModified(false);
}
xDesktop->terminate();
}
static void session_client_signal(GDBusProxy* client_proxy, const char*, const char* signal_name,
GVariant* /*parameters*/, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
if (g_str_equal (signal_name, "QueryEndSession"))
{
const css::uno::Reference<css::uno::XComponentContext>& xContext = ::comphelper::getProcessComponentContext();
uno::Reference<frame::XDesktop2> xDesktop(frame::Desktop::create(xContext));
bool bModified = false;
// find the XModifiable for this GtkSalFrame
if (UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper(false))
{
VclPtr<vcl::Window> xThisWindow = pThis->GetWindow();
css::uno::Reference<css::container::XIndexAccess> xList = xDesktop->getFrames();
sal_Int32 nFrameCount = xList->getCount();
for (sal_Int32 i = 0; i < nFrameCount; ++i)
{
css::uno::Reference<css::frame::XFrame> xFrame;
xList->getByIndex(i) >>= xFrame;
if (!xFrame)
continue;
VclPtr<vcl::Window> xWin = pWrapper->GetWindow(xFrame->getContainerWindow());
if (!xWin)
continue;
if (xWin->GetFrameWindow() != xThisWindow)
continue;
css::uno::Reference<css::frame::XController> xController = xFrame->getController();
if (!xController)
break;
css::uno::Reference<css::util::XModifiable> xModifiable(xController->getModel(), css::uno::UNO_QUERY);
if (!xModifiable)
break;
bModified = xModifiable->isModified();
break;
}
}
pThis->SessionManagerInhibit(bModified, APPLICATION_INHIBIT_LOGOUT, VclResId(STR_UNSAVED_DOCUMENTS),
gtk_window_get_icon_name(GTK_WINDOW(pThis->getWindow())));
session_client_response(client_proxy);
}
else if (g_str_equal (signal_name, "CancelEndSession"))
{
// restore back to uninhibited (to set again if queried), so frames
// that go away before the next logout don't affect that logout
pThis->SessionManagerInhibit(false, APPLICATION_INHIBIT_LOGOUT, VclResId(STR_UNSAVED_DOCUMENTS),
gtk_window_get_icon_name(GTK_WINDOW(pThis->getWindow())));
}
else if (g_str_equal (signal_name, "EndSession"))
{
session_client_response(client_proxy);
clear_modify_and_terminate();
}
else if (g_str_equal (signal_name, "Stop"))
{
clear_modify_and_terminate();
}
}
void GtkSalFrame::ListenSessionManager()
{
EnsureSessionBus();
if (!pSessionBus)
return;
m_pSessionManager = g_dbus_proxy_new_sync(pSessionBus,
G_DBUS_PROXY_FLAGS_NONE,
nullptr,
"org.gnome.SessionManager",
"/org/gnome/SessionManager",
"org.gnome.SessionManager",
nullptr,
nullptr);
if (!m_pSessionManager)
return;
GVariant* res = g_dbus_proxy_call_sync(m_pSessionManager,
"RegisterClient",
g_variant_new ("(ss)", "org.libreoffice", ""),
G_DBUS_CALL_FLAGS_NONE,
G_MAXINT,
nullptr,
nullptr);
if (!res)
return;
gchar* client_path;
g_variant_get(res, "(o)", &client_path);
g_variant_unref(res);
m_pSessionClient = g_dbus_proxy_new_sync(pSessionBus,
G_DBUS_PROXY_FLAGS_NONE,
nullptr,
"org.gnome.SessionManager",
client_path,
"org.gnome.SessionManager.ClientPrivate",
nullptr,
nullptr);
g_free(client_path);
if (!m_pSessionClient)
return;
m_nSessionClientSignalId = g_signal_connect(m_pSessionClient, "g-signal", G_CALLBACK(session_client_signal), this);
}
void GtkSalFrame::UpdateDarkMode()
{
g_autoptr (GVariant) value = nullptr;
if (m_pSettingsPortal)
ReadColorScheme(m_pSettingsPortal, &value);
SetColorScheme(value);
}
#if GTK_CHECK_VERSION(4,0,0)
static void PopoverClosed(GtkPopover*, GtkSalFrame* pThis)
{
SolarMutexGuard aGuard;
pThis->closePopup();
}
#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<GtkSalFrame*>(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
if (!bPopup)
m_pWindow = gtk_window_new();
else
{
m_pWindow = gtk_popover_new();
gtk_popover_set_has_arrow(GTK_POPOVER(m_pWindow), false);
g_signal_connect(m_pWindow, "closed", G_CALLBACK(PopoverClosed), this);
}
#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<GdkModifierType>(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<char *>(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)));
}
}
else if (GTK_IS_POPOVER(m_pWindow))
{
assert(m_pParent);
gtk_widget_set_parent(m_pWindow, m_pParent->getMouseEventWidget());
}
// 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);
// Listen to portal settings for e.g. prefer dark theme
ListenPortalSettings();
// Listen to session manager for e.g. query-end
ListenSessionManager();
}
}
#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<ImplSVEvent> pData)
{
getDisplay()->SendInternalEvent( this, pData.release() );
return true;
}
void GtkSalFrame::SetTitle( const OUString& rTitle )
{
if (m_pWindow && GTK_IS_WINDOW(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<void (*) (GdkWindow*, const char*)>(
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<GtkSalMenu*>(pSalMenu);
}
SalMenu* GtkSalFrame::GetMenu()
{
return m_pSalMenu;
}
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) && !GTK_CHECK_VERSION(4,0,0)
/*
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.width(), maGeometry.height() );
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.width() << " x " << maGeometry.height());
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;
maGeometry.setSize({ nWidth, nHeight });
if (isChild(false) || GTK_IS_POPOVER(m_pWindow))
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.width()-m_nWidthRequest-1-nX;
nX += m_pParent->maGeometry.x();
nY += m_pParent->maGeometry.y();
}
if (nFlags & SAL_FRAME_POSSIZE_X)
maGeometry.setX(nX);
if (nFlags & SAL_FRAME_POSSIZE_Y)
maGeometry.setY(nY);
m_bGeometryIsProvisional = true;
m_bDefaultPos = false;
moveWindow(maGeometry.x(), maGeometry.y());
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.width();
rHeight = maGeometry.height();
}
else
rWidth = rHeight = 0;
}
void GtkSalFrame::GetWorkArea( AbsoluteScreenPixelRectangle& rRect )
{
GdkRectangle aRect;
#if !GTK_CHECK_VERSION(4, 0, 0)
GdkScreen *pScreen = gtk_widget_get_screen(m_pWindow);
AbsoluteScreenPixelRectangle aRetRect;
int max = gdk_screen_get_n_monitors (pScreen);
for (int i = 0; i < max; ++i)
{
gdk_screen_get_monitor_workarea(pScreen, i, &aRect);
AbsoluteScreenPixelRectangle 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 = AbsoluteScreenPixelRectangle(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height);
#endif
}
SalFrame* GtkSalFrame::GetParent() const
{
return m_pParent;
}
void GtkSalFrame::SetWindowState(const vcl::WindowData* pState)
{
if( ! m_pWindow || ! pState || isChild( true, false ) )
return;
const vcl::WindowDataMask nMaxGeometryMask = vcl::WindowDataMask::PosSize |
vcl::WindowDataMask::MaximizedX | vcl::WindowDataMask::MaximizedY |
vcl::WindowDataMask::MaximizedWidth | vcl::WindowDataMask::MaximizedHeight;
if( (pState->mask() & vcl::WindowDataMask::State) &&
! ( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED ) &&
(pState->state() & vcl::WindowState::Maximized) &&
(pState->mask() & nMaxGeometryMask) == nMaxGeometryMask )
{
resizeWindow(pState->width(), pState->height());
moveWindow(pState->x(), pState->y());
m_bDefaultPos = m_bDefaultSize = false;
updateScreenNumber();
m_nState = GdkToplevelState(m_nState | GDK_TOPLEVEL_STATE_MAXIMIZED);
m_aRestorePosSize = pState->posSize();
}
else if (pState->mask() & vcl::WindowDataMask::PosSize)
{
sal_uInt16 nPosSizeFlags = 0;
tools::Long nX = pState->x() - (m_pParent ? m_pParent->maGeometry.x() : 0);
tools::Long nY = pState->y() - (m_pParent ? m_pParent->maGeometry.y() : 0);
if (pState->mask() & vcl::WindowDataMask::X)
nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
else
nX = maGeometry.x() - (m_pParent ? m_pParent->maGeometry.x() : 0);
if (pState->mask() & vcl::WindowDataMask::Y)
nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
else
nY = maGeometry.y() - (m_pParent ? m_pParent->maGeometry.y() : 0);
if (pState->mask() & vcl::WindowDataMask::Width)
nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
if (pState->mask() & vcl::WindowDataMask::Height)
nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
SetPosSize(nX, nY, pState->width(), pState->height(), nPosSizeFlags);
}
if (pState->mask() & vcl::WindowDataMask::State && !isChild())
{
if (pState->state() & vcl::WindowState::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->state() & vcl::WindowState::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(vcl::WindowData* pState)
{
pState->setState(vcl::WindowState::Normal);
pState->setMask(vcl::WindowDataMask::PosSizeState);
// rollup ? gtk 2.2 does not seem to support the shaded state
if( m_nState & GDK_TOPLEVEL_STATE_MINIMIZED )
pState->rState() |= vcl::WindowState::Minimized;
if( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED )
{
pState->rState() |= vcl::WindowState::Maximized;
pState->setPosSize(m_aRestorePosSize);
tools::Rectangle aPosSize = GetPosAndSize(GTK_WINDOW(m_pWindow));
pState->SetMaximizedX(aPosSize.Left());
pState->SetMaximizedY(aPosSize.Top());
pState->SetMaximizedWidth(aPosSize.GetWidth());
pState->SetMaximizedHeight(aPosSize.GetHeight());
pState->rMask() |= vcl::WindowDataMask::MaximizedX |
vcl::WindowDataMask::MaximizedY |
vcl::WindowDataMask::MaximizedWidth |
vcl::WindowDataMask::MaximizedHeight;
}
else
pState->setPosSize(GetPosAndSize(GTK_WINDOW(m_pWindow)));
return true;
}
void GtkSalFrame::SetScreen( unsigned int nNewScreen, SetType eType, tools::Rectangle const *pSize )
{
if( !m_pWindow )
return;
if (maGeometry.screen() == nNewScreen && eType == SetType::RetainSize)
return;
#if !GTK_CHECK_VERSION(4, 0, 0)
int nX = maGeometry.x(), nY = maGeometry.y(),
nWidth = maGeometry.width(), nHeight = maGeometry.height();
GdkScreen *pScreen = nullptr;
GdkRectangle aNewMonitor;
bool bSpanAllScreens = nNewScreen == static_cast<unsigned int>(-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;
bResize = true;
// #i110881# for the benefit of compiz set a max size here
// else setting to fullscreen fails for unknown reasons
//
// tdf#161479 With fractional scaling on wayland the monitor
// sizes here are reported effectively with the fractional
// scaling factor rounded up to the next integer, so,
// 1920 x 1080 at 125% scaling which appears like,
// 1536 x 864 is reported the same as 200% scaling, i.e.
// 960 x 540 which causes a problem on trying to set
// fullscreen on fractional scaling under wayland. Drop
// this old workaround when under wayland.
#if defined(GDK_WINDOWING_WAYLAND)
const bool bWayland = DLSYM_GDK_IS_WAYLAND_DISPLAY(GtkSalFrame::getGdkDisplay());
#else
const bool bWayland = false;
#endif
if (!bWayland)
{
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();
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<unsigned int>(-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);
}
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<GdkMonitor*>(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)
{
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<char*>(aResName.getStr());
pClass->res_class = const_cast<char*>(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::SessionManagerInhibit(bool bStart, ApplicationInhibitFlags eType, std::u16string_view sReason, const char* application_id)
{
guint nWindow(0);
std::optional<Display*> aDisplay;
if (DLSYM_GDK_IS_X11_DISPLAY(getGdkDisplay()))
{
nWindow = GtkSalFrame::GetNativeWindowHandle(m_pWindow);
aDisplay = gdk_x11_display_get_xdisplay(getGdkDisplay());
}
m_SessionManagerInhibitor.inhibit(bStart, sReason, eType,
nWindow, aDisplay, application_id);
}
void GtkSalFrame::StartPresentation( bool bStart )
{
SessionManagerInhibit(bStart, APPLICATION_INHIBIT_IDLE, u"presentation", nullptr);
}
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.x() + nX;
unsigned int nWindowTop = maGeometry.y() + 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<GdkModifierType>( nModifiers | GDK_SHIFT_MASK );
if ( rKeyCode.IsMod1() )
nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_CONTROL_MASK );
if ( rKeyCode.IsMod2() )
nModifiers = static_cast<GdkModifierType>( 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;
case KEY_RIGHTCURLYBRACKET: nKeyCode = GDK_KEY_braceright; break;
case KEY_NUMBERSIGN: nKeyCode = GDK_KEY_numbersign; break;
case KEY_XF86FORWARD: nKeyCode = GDK_KEY_Forward; break;
case KEY_XF86BACK: nKeyCode = GDK_KEY_Back; break;
case KEY_COLON: nKeyCode = GDK_KEY_colon; 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 = OStringToOUString(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.x(), y - maGeometry.y() );
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<GtkSalGraphics*>(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<GtkWidget*>(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<GtkSalFrame*>(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 &&
// tdf#152155 cannot determine window positions of popup listboxes on sidebar
nFlags != LISTBOX_FLOATWINPOPUPFLAGS )
{
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);
}
gboolean GtkSalFrame::signalTooltipQuery(GtkWidget*, gint /*x*/, gint /*y*/,
gboolean /*keyboard_mode*/, GtkTooltip *tooltip,
gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(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.width()-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.x();
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<GtkWidget*>(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<GtkWidget*>(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<GtkWidget*>(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.width()-1-aEvent.mnX;
CallCallbackExc(nEventType, &aEvent);
return true;
}
#if !GTK_CHECK_VERSION(4, 0, 0)
void GtkSalFrame::UpdateGeometryFromEvent(int x_root, int y_root, int nEventX, int nEventY)
{
//tdf#151509 don't overwrite geometry for system children
if (m_nStyle & SalFrameStyleFlags::SYSTEMCHILD)
return;
int frame_x = x_root - nEventX;
int frame_y = y_root - nEventY;
if (m_bGeometryIsProvisional || frame_x != maGeometry.x() || frame_y != maGeometry.y())
{
m_bGeometryIsProvisional = false;
maGeometry.setPos({ frame_x, frame_y });
ImplSVData* pSVData = ImplGetSVData();
if (pSVData->maNWFData.mbCanDetermineWindowPosition)
CallCallbackExc(SalEvent::Move, nullptr);
}
}
gboolean GtkSalFrame::signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(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())
pThis->UpdateGeometryFromEvent(pEvent->x_root, pEvent->y_root, nEventX, nEventY);
bool bRet = false;
if (!aDel.isDeleted())
{
bRet = pThis->DrawingAreaButton(nEventType,
nEventX,
nEventY,
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<GtkSalFrame*>(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<GtkSalFrame*>(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.width() - 1 - aEvent.mnX;
aEvent.mnY = nEventY;
aEvent.mnCode = GetMouseModCode(nState);
// rhbz#1344042 "Traditionally" in gtk3 we took 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());
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<sal_uLong>(rEvent.x);
aEvent.mnY = static_cast<sal_uLong>(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<GtkSalFrame*>(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.width() - 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<GtkSalFrame*>(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;
}
gboolean GtkSalFrame::event_controller_scroll_forward(GtkEventControllerScroll* pController, double delta_x, double delta_y)
{
return GtkSalFrame::signalScroll(pController, delta_x, delta_y, this);
}
#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))
{
SalGestureSwipeEvent aEvent;
aEvent.mnVelocityX = velocity_x;
aEvent.mnVelocityY = velocity_y;
aEvent.mnX = x;
aEvent.mnY = y;
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
pThis->CallCallbackExc(SalEvent::GestureSwipe, &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))
{
SalGestureLongPressEvent aEvent;
aEvent.mnX = x;
aEvent.mnY = y;
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
pThis->CallCallbackExc(SalEvent::GestureLongPress, &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.width() - 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<GtkSalFrame*>(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<GtkSalFrame*>(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);
pThis->UpdateGeometryFromEvent(pEvent->x_root, pEvent->y_root, nEventX, nEventY);
if (!aDel.isDeleted())
pThis->DrawingAreaMotion(nEventX, nEventY, 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.width()-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<GtkSalFrame*>(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<GtkSalFrame*>(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<GtkSalFrame*>(frame);
pThis->DrawingAreaCrossing((pEvent->type == GDK_ENTER_NOTIFY) ? SalEvent::MouseMove : SalEvent::MouseLeave,
pEvent->x,
pEvent->y,
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<GtkSalFrame*>(frame);
pThis->DrawingAreaDraw(cr);
return false;
}
#else
void GtkSalFrame::signalDraw(GtkDrawingArea*, cairo_t *cr, int /*width*/, int /*height*/, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(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.setSize({ nWidth, 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<GtkSalFrame*>(frame);
pThis->DrawingAreaResized(pWidget, pAllocation->width, pAllocation->height);
}
#else
void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, int nWidth, int nHeight, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(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<GtkSalFrame*>(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<void (*) (GdkWindow*, const GdkRectangle*, GdkGravity,
GdkGravity, GdkAnchorHints, gint, gint)>(
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<vcl::Window> pVclParent = pThis->GetWindow()->GetParent();
if (pVclParent->GetOutDev()->HasMirroredGraphics() && pVclParent->IsRTLEnabled())
{
swapDirection(rect_anchor);
swapDirection(menu_anchor);
}
AbsoluteScreenPixelRectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pVclParent, pThis->m_aFloatRect);
switch (gdk_window_get_window_type(widget_get_surface(pThis->m_pParent->m_pWindow)))
{
case GDK_WINDOW_TOPLEVEL:
break;
case GDK_WINDOW_CHILD:
{
// See tdf#152155 for an example
gtk_coord nX(0), nY(0.0);
gtk_widget_translate_coordinates(pThis->m_pParent->m_pWindow, widget_get_toplevel(pThis->m_pParent->m_pWindow), 0, 0, &nX, &nY);
aFloatRect.Move(nX, nY);
break;
}
default:
{
// See tdf#154072 for an example
aFloatRect.Move(-pThis->m_pParent->maGeometry.x(), -pThis->m_pParent->maGeometry.y());
break;
}
}
GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
GdkSurface* gdkWindow = widget_get_surface(pThis->m_pWindow);
window_move_to_rect(gdkWindow, &rect, rect_anchor, menu_anchor, static_cast<GdkAnchorHints>(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<GtkSalFrame*>(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.x() || y != pThis->maGeometry.y() )
{
bMoved = true;
pThis->m_bGeometryIsProvisional = false;
pThis->maGeometry.setPos({ x, y });
}
// update decoration hints
GdkRectangle aRect;
gdk_window_get_frame_extents( widget_get_surface(GTK_WIDGET(pThis->m_pWindow)), &aRect );
pThis->maGeometry.setTopDecoration(y - aRect.y);
pThis->maGeometry.setBottomDecoration(aRect.y + aRect.height - y - pEvent->height);
pThis->maGeometry.setLeftDecoration(x - aRect.x);
pThis->maGeometry.setRightDecoration(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.width() << "x" << maGeometry.height());
SalPaintEvent aPaintEvt(0, 0, maGeometry.width(), maGeometry.height(), true);
CallCallbackExc(SalEvent::Paint, &aPaintEvt);
queue_draw();
}
void GtkSalFrame::DrawingAreaFocusInOut(SalEvent nEventType)
{
SalGenericInstance* pSalInstance = GetGenericInstance();
// 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<GtkSalFrame*>(frame);
SalGenericInstance *pSalInstance = GetGenericInstance();
// 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<GtkSalFrame*>(frame);
pThis->DrawingAreaFocusInOut(SalEvent::GetFocus);
}
void GtkSalFrame::signalFocusLeave(GtkEventControllerFocus*, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(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<GtkSalFrame*>(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<GtkSalFrame*>(frame);
pThis->WindowMap();
}
void GtkSalFrame::signalUnmap(GtkWidget*, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
pThis->WindowUnmap();
}
#else
gboolean GtkSalFrame::signalMap(GtkWidget*, GdkEvent*, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
pThis->WindowMap();
return false;
}
gboolean GtkSalFrame::signalUnmap(GtkWidget*, GdkEvent*, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(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<GtkWidget*>(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<GtkSalFrame*>(frame);
bool bFocusInAnotherGtkWidget = false;
VclPtr<vcl::Window> 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<vcl::Window*>(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<vcl::Window> xOrigFrameFocusWin;
VclPtr<vcl::Window> xOrigFocusWin;
if (xTopLevelInterimWindow)
{
// Focus is inside an InterimItemWindow so send unconsumed
// keystrokes to by setting it as the mpFocusWin
VclPtr<vcl::Window> 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<vcl::Window> xVclWindow = pThis->GetWindow();
ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
if (pFrameData->mpFocusWin == xTopLevelInterimWindow)
pFrameData->mpFocusWin = std::move(xOrigFrameFocusWin);
ImplSVData* pSVData = ImplGetSVData();
if (pSVData->mpWinData->mpFocusWin == xTopLevelInterimWindow)
pSVData->mpWinData->mpFocusWin = std::move(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<vcl::Window> 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<vcl::Window*>(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<vcl::Window> xOrigFrameFocusWin;
VclPtr<vcl::Window> xOrigFocusWin;
if (xTopLevelInterimWindow)
{
// Focus is inside an InterimItemWindow so send unconsumed
// keystrokes to by setting it as the mpFocusWin
VclPtr<vcl::Window> 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<vcl::Window> 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<GtkSalFrame*>(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<GtkSalFrame*>(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<GtkSalFrame*>(frame);
return pThis->WindowCloseRequest();
}
#else
gboolean GtkSalFrame::signalDelete(GtkWidget*, GdkEvent*, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(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<GtkSalFrame*>(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 = GetGtkInstance();
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<GtkSalFrame*>(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<GtkSalFrame*>(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
{
bool handleSignalZoom(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame,
GestureEventZoomType eEventType)
{
gdouble x = 0;
gdouble y = 0;
gtk_gesture_get_point(gesture, sequence, &x, &y);
SalGestureZoomEvent aEvent;
aEvent.meEventType = eEventType;
aEvent.mnX = x;
aEvent.mnY = y;
aEvent.mfScaleDelta = gtk_gesture_zoom_get_scale_delta(GTK_GESTURE_ZOOM(gesture));
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
pThis->CallCallbackExc(SalEvent::GestureZoom, &aEvent);
return true;
}
bool handleSignalRotate(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame,
GestureEventRotateType eEventType)
{
gdouble x = 0;
gdouble y = 0;
gtk_gesture_get_point(gesture, sequence, &x, &y);
SalGestureRotateEvent aEvent;
aEvent.meEventType = eEventType;
aEvent.mnX = x;
aEvent.mnY = y;
aEvent.mfAngleDelta = gtk_gesture_rotate_get_angle_delta(GTK_GESTURE_ROTATE(gesture));
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
pThis->CallCallbackExc(SalEvent::GestureRotate, &aEvent);
return true;
}
}
bool GtkSalFrame::signalZoomBegin(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame)
{
return handleSignalZoom(gesture, sequence, frame, GestureEventZoomType::Begin);
}
bool GtkSalFrame::signalZoomUpdate(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame)
{
return handleSignalZoom(gesture, sequence, frame, GestureEventZoomType::Update);
}
bool GtkSalFrame::signalZoomEnd(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame)
{
return handleSignalZoom(gesture, sequence, frame, GestureEventZoomType::End);
}
bool GtkSalFrame::signalRotateBegin(GtkGesture* gesture, GdkEventSequence* sequence,
gpointer frame)
{
return handleSignalRotate(gesture, sequence, frame, GestureEventRotateType::Begin);
}
bool GtkSalFrame::signalRotateUpdate(GtkGesture* gesture, GdkEventSequence* sequence,
gpointer frame)
{
return handleSignalRotate(gesture, sequence, frame, GestureEventRotateType::Update);
}
bool GtkSalFrame::signalRotateEnd(GtkGesture* gesture, GdkEventSequence* sequence,
gpointer frame)
{
return handleSignalRotate(gesture, sequence, frame, GestureEventRotateType::End);
}
namespace
{
GdkDragAction VclToGdk(sal_Int8 dragOperation)
{
GdkDragAction eRet(static_cast<GdkDragAction>(0));
if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_COPY);
if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_MOVE);
if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
eRet = static_cast<GdkDragAction>(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<GdkDragAction>(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<read_transfer_result*>(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<css::datatransfer::dnd::XDropTargetDropContext>
{
#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<GdkDragAction>(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<GdkDragAction>(0), m_nTime);
#else
gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), static_cast<GdkDragAction>(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<GdkDragAction>(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<gchar*>(gtk_selection_data_get_text(m_pData));
if (pText)
aStr = OStringToOUString(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<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
aRet <<= aSeq;
}
}
gtk_selection_data_free(m_pData);
#else
SalInstance* pInstance = GetSalInstance();
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<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector() override
{
#if !GTK_CHECK_VERSION(4, 0, 0)
std::vector<GdkAtom> targets;
for (GList* l = gdk_drag_context_list_targets(m_pContext); l; l = l->next)
targets.push_back(static_cast<GdkAtom>(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<GtkSalFrame*>(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<GtkSalFrame*>(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<css::datatransfer::dnd::XDropTarget*>(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;
// 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)
aEvent.Transferable = GtkInstDragSource::g_ActiveDragSource->GetTransferable();
else
{
#if GTK_CHECK_VERSION(4,0,0)
aEvent.Transferable = new GtkDnDTransferable(drop);
#else
aEvent.Transferable = new GtkDnDTransferable(context, time, pWidget, this);
#endif
}
fire_drop(aEvent);
return true;
}
namespace {
class GtkDropTargetDragContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDragContext>
{
#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<GdkDragAction>(0), m_nTime);
#else
gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), static_cast<GdkDragAction>(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<GtkSalFrame*>(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<GtkSalFrame*>(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<GtkSalFrame*>(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<css::datatransfer::dnd::XDropTarget*>(this);
#if !GTK_CHECK_VERSION(4,0,0)
rtl::Reference<GtkDropTargetDragContext> pContext = new GtkDropTargetDragContext(context, time);
#else
rtl::Reference<GtkDropTargetDragContext> 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<GdkDragAction>(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<css::datatransfer::XTransferable> 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->GetTransferable();
else
{
#if !GTK_CHECK_VERSION(4,0,0)
xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
#else
xTransferable = new GtkDnDTransferable(pDrop);
#endif
}
aEvent.SupportedDataFlavors = xTransferable->getTransferDataFlavors();
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<GtkSalFrame*>(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<GtkSalFrame*>(frame);
if (!pThis->m_pDropTarget)
return;
pThis->m_pDropTarget->signalDragLeave(pWidget);
}
#endif
static gboolean lcl_deferred_dragExit(gpointer user_data)
{
GtkInstDropTarget* pThis = static_cast<GtkInstDropTarget*>(user_data);
css::datatransfer::dnd::DropTargetEvent aEvent;
aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(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<GtkSalFrame*>(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<void*>(&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<void*>(&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<GtkSalFrame::IMHandler*>(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
* <space> 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<void*>(&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<GtkSalFrame::IMHandler*>(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
* <space> 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<void*>(&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<ExtTextInputAttr>& 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<sal_Int32> 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<int>(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<PangoAttribute *>(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:
{
PangoAttrInt* pango_underline = reinterpret_cast<PangoAttrInt*>(pango_attr);
switch (pango_underline->value)
{
case PANGO_UNDERLINE_NONE:
break;
case PANGO_UNDERLINE_DOUBLE:
sal_attr |= ExtTextInputAttr::DoubleUnderline;
break;
default:
sal_attr |= ExtTextInputAttr::Underline;
break;
}
break;
}
case PANGO_ATTR_STRIKETHROUGH:
sal_attr |= ExtTextInputAttr::RedText;
break;
default:
break;
}
pango_attribute_destroy (pango_attr);
tmp_list = tmp_list->next;
}
if (!attr_list)
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<int>(rInputFlags.size()),
"vcl.gtk3", "pango attrib out of range. Broken range: "
<< aUtf16Offsets[nUtf32Start] << "," << aUtf16Offsets[nUtf32End] << " Legal range: 0,"
<< rInputFlags.size());
if (i >= static_cast<int>(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<GtkSalFrame::IMHandler*>(im_handler);
sal_Int32 nCursorPos(0);
sal_uInt8 nCursorFlags(0);
std::vector<ExtTextInputAttr> 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 = std::move(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<void*>(&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<GtkSalFrame::IMHandler*>(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<GtkSalFrame::IMHandler*>(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);
std::u16string_view sCursorText(aEvt.maText.subView(0, aEvt.mnStart));
gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
return true;
}
gboolean GtkSalFrame::IMHandler::signalIMDeleteSurrounding( GtkIMContext*, gint offset, gint nchars,
gpointer im_handler )
{
GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(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 = SalFrame::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;
}
AbsoluteScreenPixelSize GtkSalDisplay::GetScreenSize( int nDisplayScreen )
{
AbsoluteScreenPixelRectangle aRect = m_pSys->GetDisplayScreenPosSizePixel( nDisplayScreen );
return AbsoluteScreenPixelSize( 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<sal_uIntPtr>(gdk_wayland_surface_get_wl_surface(pSurface));
}
#endif
return 0;
}
void GtkInstDragSource::set_datatransfer(const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& 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<GtkTargetEntry> GtkInstDragSource::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &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<css::datatransfer::XTransferable>& rTrans,
const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& 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<css::datatransfer::XTransferable>& 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
// appropriate 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<GdkDevice*>(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<GtkSalFrame*>(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<GtkSalFrame*>(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<GtkSalFrame*>(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<GtkSalFrame*>(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 (...)
{
GetGtkSalData()->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: */