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>
6437 lines
224 KiB
C++
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: */
|