Files
libreoffice/vcl/unx/gtk3/gtk3gtkframe.cxx
Caolán McNamara 2fdbe655bb gtk3: scrollbar damage area one pixel too narrow/short
under default Ambience theme Ubuntu 16.04

tools rectangle strikes again, rejig to be left/top +
width/height rather than left/top + right/bottom

Change-Id: Id5274f068863cd5219a89142b392c9ca53a1694d
Reviewed-on: https://gerrit.libreoffice.org/30850
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
Tested-by: Caolán McNamara <caolanm@redhat.com>
2016-11-14 16:28:00 +00:00

4246 lines
147 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 <unx/gtk/gtkframe.hxx>
#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <unx/gtk/gtkgdi.hxx>
#include <vcl/help.hxx>
#include <vcl/keycodes.hxx>
#include <vcl/layout.hxx>
#include <unx/wmadaptor.hxx>
#include <unx/sm.hxx>
#include <unx/salbmp.h>
#include <unx/genprn.h>
#include <unx/geninst.h>
#include <headless/svpgdi.hxx>
#include <osl/file.hxx>
#include <rtl/bootstrap.hxx>
#include <rtl/process.h>
#include <vcl/floatwin.hxx>
#include <vcl/svapp.hxx>
#include <vcl/window.hxx>
#include <vcl/settings.hxx>
#include <cppuhelper/exc_hlp.hxx>
#include <config_gio.h>
#if ENABLE_DBUS && ENABLE_GIO
# include <unx/gtk/gtksalmenu.hxx>
#endif
#if defined ENABLE_GMENU_INTEGRATION // defined in gtksalmenu.hxx above
# include <unx/gtk/hudawareness.h>
#endif
#include <gtk/gtk.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#if defined(GDK_WINDOWING_X11)
# include <gdk/gdkx.h>
#endif
#if defined(GDK_WINDOWING_WAYLAND)
# include <gdk/gdkwayland.h>
#endif
#include <dlfcn.h>
#include <vcl/salbtype.hxx>
#include <impbmp.hxx>
#include <svids.hrc>
#include <sal/macros.h>
#include <basegfx/range/b2ibox.hxx>
#include <basegfx/vector/b2ivector.hxx>
#include <algorithm>
#include <glib/gprintf.h>
#if OSL_DEBUG_LEVEL > 1
# include <cstdio>
#endif
#include <cstdlib>
#include <cmath>
#include <comphelper/processfactory.hxx>
#include <comphelper/sequenceashashmap.hxx>
#include <com/sun/star/accessibility/XAccessibleContext.hpp>
#include <com/sun/star/accessibility/AccessibleRole.hpp>
#include <com/sun/star/accessibility/XAccessibleStateSet.hpp>
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
#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/frame/ModuleManager.hpp>
#include <com/sun/star/frame/XFrame.hpp>
#include <com/sun/star/util/URLTransformer.hpp>
#include <config_folders.h>
#define IS_WIDGET_REALIZED gtk_widget_get_realized
#define IS_WIDGET_MAPPED gtk_widget_get_mapped
#ifndef GDK_IS_X11_DISPLAY
#define GDK_IS_X11_DISPLAY(foo) (true)
#endif
using namespace com::sun::star;
int GtkSalFrame::m_nFloats = 0;
#if defined ENABLE_GMENU_INTEGRATION
static GDBusConnection* pSessionBus = nullptr;
#endif
static sal_uInt16 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_MOD1_MASK) )
nCode |= KEY_MOD2;
if( (state & GDK_SUPER_MASK) )
nCode |= KEY_MOD3;
return nCode;
}
static sal_uInt16 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;
}
static sal_uInt16 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( keyval >= GDK_KEY_F1 && keyval <= GDK_KEY_F26 )
// KEY_F26 is the last function key known to keycodes.hxx
{
switch( keyval )
{
// - - - - - Sun keyboard, see vcl/unx/source/app/saldisp.cxx
// althopugh GDK_KEY_F1 ... GDK_KEY_L10 are known to
// gdk/gdkkeysyms.h, they are unlikely to be generated
// except possibly by Solaris systems
// this whole section needs review
case GDK_KEY_L2:
nCode = KEY_F12;
break;
case GDK_KEY_L3: nCode = KEY_PROPERTIES; break;
case GDK_KEY_L4: nCode = KEY_UNDO; break;
case GDK_KEY_L6: nCode = KEY_COPY; break; // KEY_F16
case GDK_KEY_L8: nCode = KEY_PASTE; break; // KEY_F18
case GDK_KEY_L10: nCode = KEY_CUT; break; // KEY_F20
default:
nCode = KEY_F1 + (keyval-GDK_KEY_F1); break;
}
}
else
{
switch( keyval )
{
case GDK_KEY_KP_Down:
case GDK_KEY_Down: nCode = KEY_DOWN; break;
case GDK_KEY_KP_Up:
case GDK_KEY_Up: nCode = KEY_UP; break;
case GDK_KEY_KP_Left:
case GDK_KEY_Left: nCode = KEY_LEFT; break;
case GDK_KEY_KP_Right:
case GDK_KEY_Right: nCode = KEY_RIGHT; break;
case GDK_KEY_KP_Begin:
case GDK_KEY_KP_Home:
case GDK_KEY_Begin:
case GDK_KEY_Home: nCode = KEY_HOME; break;
case GDK_KEY_KP_End:
case GDK_KEY_End: nCode = KEY_END; break;
case GDK_KEY_KP_Page_Up:
case GDK_KEY_Page_Up: nCode = KEY_PAGEUP; break;
case GDK_KEY_KP_Page_Down:
case GDK_KEY_Page_Down: nCode = KEY_PAGEDOWN; break;
case GDK_KEY_KP_Enter:
case GDK_KEY_Return: nCode = KEY_RETURN; break;
case GDK_KEY_Escape: nCode = KEY_ESCAPE; break;
case GDK_KEY_ISO_Left_Tab:
case GDK_KEY_KP_Tab:
case GDK_KEY_Tab: nCode = KEY_TAB; break;
case GDK_KEY_BackSpace: nCode = KEY_BACKSPACE; break;
case GDK_KEY_KP_Space:
case GDK_KEY_space: nCode = KEY_SPACE; break;
case GDK_KEY_KP_Insert:
case GDK_KEY_Insert: nCode = KEY_INSERT; break;
case GDK_KEY_KP_Delete:
case GDK_KEY_Delete: nCode = KEY_DELETE; break;
case GDK_KEY_plus:
case GDK_KEY_KP_Add: nCode = KEY_ADD; break;
case GDK_KEY_minus:
case GDK_KEY_KP_Subtract: nCode = KEY_SUBTRACT; break;
case GDK_KEY_asterisk:
case GDK_KEY_KP_Multiply: nCode = KEY_MULTIPLY; break;
case GDK_KEY_slash:
case GDK_KEY_KP_Divide: nCode = KEY_DIVIDE; break;
case GDK_KEY_period: nCode = KEY_POINT; break;
case GDK_KEY_decimalpoint: nCode = KEY_POINT; break;
case GDK_KEY_comma: nCode = KEY_COMMA; break;
case GDK_KEY_less: nCode = KEY_LESS; break;
case GDK_KEY_greater: nCode = KEY_GREATER; break;
case GDK_KEY_KP_Equal:
case GDK_KEY_equal: nCode = KEY_EQUAL; break;
case GDK_KEY_Find: nCode = KEY_FIND; break;
case GDK_KEY_Menu: nCode = KEY_CONTEXTMENU;break;
case GDK_KEY_Help: nCode = KEY_HELP; break;
case GDK_KEY_Undo: nCode = KEY_UNDO; break;
case GDK_KEY_Redo: nCode = KEY_REPEAT; break;
// on a sun keyboard this actually is usually SunXK_Stop = 0x0000FF69 (XK_Cancel),
// but VCL doesn't have a key definition for that
case GDK_KEY_Cancel: nCode = KEY_F11; break;
case GDK_KEY_KP_Decimal:
case GDK_KEY_KP_Separator: nCode = KEY_DECIMAL; break;
case GDK_KEY_asciitilde: nCode = KEY_TILDE; break;
case GDK_KEY_leftsinglequotemark:
case GDK_KEY_quoteleft: nCode = KEY_QUOTELEFT; break;
case GDK_KEY_bracketleft: nCode = KEY_BRACKETLEFT; break;
case GDK_KEY_bracketright: nCode = KEY_BRACKETRIGHT; break;
case GDK_KEY_semicolon: nCode = KEY_SEMICOLON; break;
case GDK_KEY_quoteright: nCode = KEY_QUOTERIGHT; break;
// some special cases, also see saldisp.cxx
// - - - - - - - - - - - - - Apollo - - - - - - - - - - - - - 0x1000
// These can be found in ap_keysym.h
case 0x1000FF02: // apXK_Copy
nCode = KEY_COPY;
break;
case 0x1000FF03: // apXK_Cut
nCode = KEY_CUT;
break;
case 0x1000FF04: // apXK_Paste
nCode = KEY_PASTE;
break;
case 0x1000FF14: // apXK_Repeat
nCode = KEY_REPEAT;
break;
// Exit, Save
// - - - - - - - - - - - - - - D E C - - - - - - - - - - - - - 0x1000
// These can be found in DECkeysym.h
case 0x1000FF00:
nCode = KEY_DELETE;
break;
// - - - - - - - - - - - - - - H P - - - - - - - - - - - - - 0x1000
// These can be found in HPkeysym.h
case 0x1000FF73: // hpXK_DeleteChar
nCode = KEY_DELETE;
break;
case 0x1000FF74: // hpXK_BackTab
case 0x1000FF75: // hpXK_KP_BackTab
nCode = KEY_TAB;
break;
// - - - - - - - - - - - - - - I B M - - - - - - - - - - - - -
// - - - - - - - - - - - - - - O S F - - - - - - - - - - - - - 0x1004
// These also can be found in HPkeysym.h
case 0x1004FF02: // osfXK_Copy
nCode = KEY_COPY;
break;
case 0x1004FF03: // osfXK_Cut
nCode = KEY_CUT;
break;
case 0x1004FF04: // osfXK_Paste
nCode = KEY_PASTE;
break;
case 0x1004FF07: // osfXK_BackTab
nCode = KEY_TAB;
break;
case 0x1004FF08: // osfXK_BackSpace
nCode = KEY_BACKSPACE;
break;
case 0x1004FF1B: // osfXK_Escape
nCode = KEY_ESCAPE;
break;
// Up, Down, Left, Right, PageUp, PageDown
// - - - - - - - - - - - - - - S C O - - - - - - - - - - - - -
// - - - - - - - - - - - - - - S G I - - - - - - - - - - - - - 0x1007
// - - - - - - - - - - - - - - S N I - - - - - - - - - - - - -
// - - - - - - - - - - - - - - S U N - - - - - - - - - - - - - 0x1005
// These can be found in Sunkeysym.h
case 0x1005FF10: // SunXK_F36
nCode = KEY_F11;
break;
case 0x1005FF11: // SunXK_F37
nCode = KEY_F12;
break;
case 0x1005FF70: // SunXK_Props
nCode = KEY_PROPERTIES;
break;
case 0x1005FF71: // SunXK_Front
nCode = KEY_FRONT;
break;
case 0x1005FF72: // SunXK_Copy
nCode = KEY_COPY;
break;
case 0x1005FF73: // SunXK_Open
nCode = KEY_OPEN;
break;
case 0x1005FF74: // SunXK_Paste
nCode = KEY_PASTE;
break;
case 0x1005FF75: // SunXK_Cut
nCode = KEY_CUT;
break;
// - - - - - - - - - - - - - X F 8 6 - - - - - - - - - - - - - 0x1008
// These can be found in XF86keysym.h
// but more importantly they are also available GTK/Gdk version 3
// and hence are already provided in gdk/gdkkeysyms.h, and hence
// in gdk/gdk.h
case GDK_KEY_Copy: nCode = KEY_COPY; break; // 0x1008ff57
case GDK_KEY_Cut: nCode = KEY_CUT; break; // 0x1008ff58
case GDK_KEY_Open: nCode = KEY_OPEN; break; // 0x1008ff6b
case GDK_KEY_Paste: nCode = KEY_PASTE; break; // 0x1008ff6d
}
}
return nCode;
}
static guint 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;
}
// 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 ) {}
};
inline 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;
}
static bool dumpframes = false;
void GtkSalFrame::doKeyCallback( guint state,
guint keyval,
guint16 hardware_keycode,
guint8 group,
guint32 time,
sal_Unicode aOrigCode,
bool bDown,
bool bSendRelease
)
{
SalKeyEvent aEvent;
aEvent.mnTime = time;
aEvent.mnCharCode = aOrigCode;
aEvent.mnRepeat = 0;
vcl::DeletionListener aDel( this );
#if 0
// shift-zero forces a re-draw and event is swallowed
if (keyval == GDK_KEY_0)
{
fprintf( stderr, "force widget_queue_draw\n");
gtk_widget_queue_draw(GTK_WIDGET(m_pFixedContainer));
return;
}
else if (keyval == GDK_KEY_1)
{
fprintf( stderr, "force repaint all\n");
TriggerPaintEvent();
return;
}
else if (keyval == GDK_KEY_2)
{
dumpframes = !dumpframes;
fprintf(stderr, "toggle dump frames to %d\n", dumpframes);
return;
}
#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( 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);
}
aEvent.mnCode |= GetKeyModCode( state );
if( bDown )
{
bool bHandled = CallCallbackExc( SalEvent::KeyInput, &aEvent );
// #i46889# copy AlternateKeyCode handling from generic plugin
if( ! bHandled )
{
KeyAlternate aAlternate = GetAlternateKeyCode( aEvent.mnCode );
if( aAlternate.nKeyCode )
{
aEvent.mnCode = aAlternate.nKeyCode;
if( aAlternate.nCharCode )
aEvent.mnCharCode = aAlternate.nCharCode;
CallCallbackExc( SalEvent::KeyInput, &aEvent );
}
}
if( bSendRelease && ! aDel.isDeleted() )
{
CallCallbackExc( SalEvent::KeyUp, &aEvent );
}
}
else
CallCallbackExc( SalEvent::KeyUp, &aEvent );
}
GtkSalFrame::GtkSalFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
: m_nXScreen( getDisplay()->GetDefaultXScreen() )
, m_pGraphics(nullptr)
, m_bGraphics(false)
{
getDisplay()->registerFrame( this );
m_bDefaultPos = true;
m_bDefaultSize = ( (nStyle & SalFrameStyleFlags::SIZEABLE) && ! pParent );
m_bWindowIsGtkPlug = false;
Init( pParent, nStyle );
}
GtkSalFrame::GtkSalFrame( SystemParentData* pSysData )
: m_nXScreen( getDisplay()->GetDefaultXScreen() )
, m_pGraphics(nullptr)
, m_bGraphics(false)
{
getDisplay()->registerFrame( this );
// permanently ignore errors from our unruly children ...
GetGenericData()->ErrorTrapPush();
m_bDefaultPos = true;
m_bDefaultSize = true;
Init( pSysData );
}
#ifdef ENABLE_GMENU_INTEGRATION
// AppMenu watch functions.
static void ObjectDestroyedNotify( gpointer data )
{
if ( data ) {
g_object_unref( data );
}
}
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();
}
}
static void activate_uno(GSimpleAction *action, GVariant*, gpointer)
{
uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create( xContext );
uno::Reference < css::frame::XFrame > xFrame(xDesktop->getActiveFrame());
if (!xFrame.is())
xFrame.set(xDesktop, uno::UNO_QUERY);
if (!xFrame.is())
return;
uno::Reference< css::frame::XDispatchProvider > xDispatchProvider(xFrame, uno::UNO_QUERY);
if (!xDispatchProvider.is())
return;
gchar *strval = nullptr;
g_object_get(action, "name", &strval, nullptr);
if (!strval)
return;
if (strcmp(strval, "New") == 0)
{
uno::Reference<frame::XModuleManager2> xModuleManager(frame::ModuleManager::create(xContext));
OUString aModuleId(xModuleManager->identify(xFrame));
if (aModuleId.isEmpty())
return;
comphelper::SequenceAsHashMap lModuleDescription(xModuleManager->getByName(aModuleId));
OUString sFactoryService;
lModuleDescription[OUString("ooSetupFactoryEmptyDocumentURL")] >>= sFactoryService;
if (sFactoryService.isEmpty())
return;
uno::Sequence < css::beans::PropertyValue > args(0);
xDesktop->loadComponentFromURL(sFactoryService, "_blank", 0, args);
return;
}
OUString sCommand(".uno:");
sCommand += OUString(strval, strlen(strval), RTL_TEXTENCODING_UTF8);
g_free(strval);
css::util::URL aCommand;
aCommand.Complete = sCommand;
uno::Reference< css::util::XURLTransformer > xParser = css::util::URLTransformer::create(xContext);
xParser->parseStrict(aCommand);
uno::Reference< css::frame::XDispatch > xDisp = xDispatchProvider->queryDispatch(aCommand, OUString(), 0);
if (!xDisp.is())
return;
xDisp->dispatch(aCommand, css::uno::Sequence< css::beans::PropertyValue >());
}
static const GActionEntry app_entries[] = {
{ "OptionsTreeDialog", activate_uno, nullptr, nullptr, nullptr, {0} },
{ "About", activate_uno, nullptr, nullptr, nullptr, {0} },
{ "HelpIndex", activate_uno, nullptr, nullptr, nullptr, {0} },
{ "Quit", activate_uno, nullptr, nullptr, nullptr, {0} },
{ "New", activate_uno, nullptr, nullptr, nullptr, {0} }
};
gboolean ensure_dbus_setup( gpointer data )
{
GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( data );
GdkWindow* gdkWindow = widget_get_window( pSalFrame->getWindow() );
if ( gdkWindow != nullptr && g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) == nullptr )
{
// Get a DBus session connection.
if(!pSessionBus)
pSessionBus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, nullptr);
if( !pSessionBus )
{
return FALSE;
}
// 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());
// Generate menu paths.
sal_uIntPtr windowId = pSalFrame->GetNativeWindowHandle(pSalFrame->getWindow());
gchar* aDBusWindowPath = g_strdup_printf( "/org/libreoffice/window/%lu", windowId );
gchar* aDBusMenubarPath = g_strdup_printf( "/org/libreoffice/window/%lu/menus/menubar", windowId );
// 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 );
GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
// fdo#70885 we don't want app menu under Unity
const bool bDesktopIsUnity = (SalGetDesktopEnvironment() == "UNITY");
#if defined(GDK_WINDOWING_X11)
if (GDK_IS_X11_DISPLAY(pDisplay))
{
gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_ID", "org.libreoffice" );
if (!bDesktopIsUnity)
gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APP_MENU_OBJECT_PATH", "/org/libreoffice/menus/appmenu" );
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 (GDK_IS_WAYLAND_DISPLAY(pDisplay))
{
gdk_wayland_window_set_dbus_properties_libgtk_only(gdkWindow, "org.libreoffice",
"/org/libreoffice/menus/appmenu",
!bDesktopIsUnity ? aDBusMenubarPath : nullptr,
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 );
//app menu, to-do translations, block normal menus when active, honor use appmenu settings
ResMgr* pMgr = ImplGetResMgr();
if( pMgr && !bDesktopIsUnity )
{
GMenu *menu = g_menu_new ();
GMenuItem* item;
GMenu *firstsubmenu = g_menu_new ();
OString sNew(OUStringToOString(ResId(SV_BUTTONTEXT_NEW, *pMgr).toString(),
RTL_TEXTENCODING_UTF8).replaceFirst("~", "_"));
item = g_menu_item_new(sNew.getStr(), "app.New");
g_menu_append_item( firstsubmenu, item );
g_object_unref(item);
g_menu_append_section( menu, nullptr, G_MENU_MODEL(firstsubmenu));
g_object_unref(firstsubmenu);
GMenu *secondsubmenu = g_menu_new ();
OString sPreferences(OUStringToOString(ResId(SV_STDTEXT_PREFERENCES, *pMgr).toString(),
RTL_TEXTENCODING_UTF8).replaceFirst("~", "_"));
item = g_menu_item_new(sPreferences.getStr(), "app.OptionsTreeDialog");
g_menu_append_item( secondsubmenu, item );
g_object_unref(item);
g_menu_append_section( menu, nullptr, G_MENU_MODEL(secondsubmenu));
g_object_unref(secondsubmenu);
GMenu *thirdsubmenu = g_menu_new ();
OString sHelp(OUStringToOString(ResId(SV_BUTTONTEXT_HELP, *pMgr).toString(),
RTL_TEXTENCODING_UTF8).replaceFirst("~", "_"));
item = g_menu_item_new(sHelp.getStr(), "app.HelpIndex");
g_menu_append_item( thirdsubmenu, item );
g_object_unref(item);
OString sAbout(OUStringToOString(ResId(SV_STDTEXT_ABOUT, *pMgr).toString(),
RTL_TEXTENCODING_UTF8).replaceFirst("~", "_"));
item = g_menu_item_new(sAbout.getStr(), "app.About");
g_menu_append_item( thirdsubmenu, item );
g_object_unref(item);
OString sQuit(OUStringToOString(ResId(SV_MENU_MAC_QUITAPP, *pMgr).toString(),
RTL_TEXTENCODING_UTF8).replaceFirst("~", "_"));
item = g_menu_item_new(sQuit.getStr(), "app.Quit");
g_menu_append_item( thirdsubmenu, item );
g_object_unref(item);
g_menu_append_section( menu, nullptr, G_MENU_MODEL(thirdsubmenu));
g_object_unref(thirdsubmenu);
GSimpleActionGroup *group = g_simple_action_group_new ();
g_action_map_add_action_entries (G_ACTION_MAP (group), app_entries, G_N_ELEMENTS (app_entries), nullptr);
GActionGroup* pAppActionGroup = G_ACTION_GROUP(group);
pSalFrame->m_nAppActionGroupExportId = g_dbus_connection_export_action_group( pSessionBus, "/org/libreoffice", pAppActionGroup, nullptr);
g_object_unref(pAppActionGroup);
pSalFrame->m_nAppMenuExportId = g_dbus_connection_export_menu_model (pSessionBus, "/org/libreoffice/menus/appmenu", G_MENU_MODEL (menu), nullptr);
g_object_unref(menu);
}
g_free( aDBusMenubarPath );
g_free( aDBusWindowPath );
}
return FALSE;
}
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);
pGtkSalMenu->UpdateFull();
}
}
// This is called when the registrar becomes unavailable. It shows the menubar.
void on_registrar_unavailable( GDBusConnection * /*connection*/,
const gchar * /*name*/,
gpointer user_data )
{
SolarMutexGuard aGuard;
SAL_INFO("vcl.unity", "on_registrar_unavailable");
//pSessionBus = NULL;
GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
SalMenu* pSalMenu = pSalFrame->GetMenu();
if ( pSalMenu ) {
GtkSalMenu* pGtkSalMenu = static_cast< GtkSalMenu* >( pSalMenu );
pGtkSalMenu->EnableUnity(false);
}
}
#endif
void GtkSalFrame::EnsureAppMenuWatch()
{
#ifdef ENABLE_GMENU_INTEGRATION
if ( !m_nWatcherId )
{
// Get a DBus session connection.
if ( pSessionBus == nullptr )
{
pSessionBus = g_bus_get_sync( G_BUS_TYPE_SESSION, nullptr, nullptr );
if ( pSessionBus == nullptr )
return;
}
// Publish the menu only if AppMenu registrar is available.
m_nWatcherId = g_bus_watch_name_on_connection( pSessionBus,
"com.canonical.AppMenu.Registrar",
G_BUS_NAME_WATCHER_FLAGS_NONE,
on_registrar_available,
on_registrar_unavailable,
this,
nullptr );
}
#else
(void) this; // loplugin:staticmethods
#endif
}
void GtkSalFrame::InvalidateGraphics()
{
if( m_pGraphics )
{
m_bGraphics = false;
}
}
GtkSalFrame::~GtkSalFrame()
{
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 );
}
delete m_pIMHandler;
GtkWidget *pEventWidget = getMouseEventWidget();
for (auto handler_id : m_aMouseSignalIds)
g_signal_handler_disconnect(G_OBJECT(pEventWidget), handler_id);
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) );
{
SolarMutexGuard aGuard;
#if defined ENABLE_GMENU_INTEGRATION
if(m_nWatcherId)
g_bus_unwatch_name(m_nWatcherId);
#endif
if( m_pWindow )
{
g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", nullptr );
#if defined ENABLE_GMENU_INTEGRATION
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_nAppMenuExportId )
g_dbus_connection_unexport_menu_model( pSessionBus, m_nAppMenuExportId );
if ( m_nActionGroupExportId )
g_dbus_connection_unexport_action_group( pSessionBus, m_nActionGroupExportId );
if ( m_nAppActionGroupExportId )
g_dbus_connection_unexport_action_group( pSessionBus, m_nAppActionGroupExportId );
}
#endif
gtk_widget_destroy( m_pWindow );
}
}
if( m_pForeignParent )
g_object_unref( G_OBJECT( m_pForeignParent ) );
if( m_pForeignTopLevel )
g_object_unref( G_OBJECT( m_pForeignTopLevel) );
delete m_pGraphics;
m_pGraphics = nullptr;
if (m_pSurface)
cairo_surface_destroy(m_pSurface);
}
void GtkSalFrame::moveWindow( long nX, long nY )
{
if( isChild( false ) )
{
if( m_pParent )
gtk_fixed_move( m_pParent->getFixedContainer(),
m_pWindow,
nX - m_pParent->maGeometry.nX, nY - m_pParent->maGeometry.nY );
}
else
gtk_window_move( GTK_WINDOW(m_pWindow), nX, nY );
}
void GtkSalFrame::widget_set_size_request(long nWidth, long nHeight)
{
gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), nWidth, nHeight );
}
void GtkSalFrame::window_resize(long nWidth, long nHeight)
{
m_nWidthRequest = nWidth;
m_nHeightRequest = nHeight;
gtk_window_resize(GTK_WINDOW(m_pWindow), nWidth, nHeight);
}
void GtkSalFrame::resizeWindow( long nWidth, long nHeight )
{
if( isChild( false ) )
{
widget_set_size_request(nWidth, nHeight);
}
else if( ! isChild( true, false ) )
window_resize(nWidth, nHeight);
}
static void
ooo_fixed_class_init(GtkFixedClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
widget_class->get_accessible = ooo_fixed_get_accessible;
}
/*
* Always use a sub-class of GtkFixed we can tag for a11y. This allows us to
* utilize GAIL for the toplevel window and toolkit implementation incl.
* key event listener support ..
*/
GType
ooo_fixed_get_type()
{
static GType type = 0;
if (!type) {
static const GTypeInfo tinfo =
{
sizeof (GtkFixedClass),
nullptr, /* base init */
nullptr, /* base finalize */
reinterpret_cast<GClassInitFunc>(ooo_fixed_class_init), /* class init */
nullptr, /* class finalize */
nullptr, /* class data */
sizeof (GtkFixed), /* instance size */
0, /* nb preallocs */
nullptr, /* instance init */
nullptr /* value table */
};
type = g_type_register_static( GTK_TYPE_FIXED, "OOoFixed",
&tinfo, (GTypeFlags) 0);
}
return type;
}
void GtkSalFrame::updateScreenNumber()
{
int nScreen = 0;
GdkScreen *pScreen = gtk_widget_get_screen( m_pWindow );
if( pScreen )
nScreen = getDisplay()->getSystem()->getScreenMonitorIdx( pScreen, maGeometry.nX, maGeometry.nY );
maGeometry.nDisplayScreenNumber = nScreen;
}
GtkWidget *GtkSalFrame::getMouseEventWidget() const
{
return GTK_WIDGET(m_pEventBox);
}
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);
}
void GtkSalFrame::InitCommon()
{
m_pSurface = nullptr;
m_nGrabLevel = 0;
m_aDamageHandler.handle = this;
m_aDamageHandler.damaged = ::damaged;
m_pTopLevelGrid = GTK_GRID(gtk_grid_new());
gtk_container_add(GTK_CONTAINER(m_pWindow), GTK_WIDGET(m_pTopLevelGrid));
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);
// add the fixed container child,
// fixed is needed since we have to position plugin windows
m_pFixedContainer = GTK_FIXED(g_object_new( ooo_fixed_get_type(), nullptr ));
gtk_container_add( GTK_CONTAINER(m_pEventBox), GTK_WIDGET(m_pFixedContainer) );
GtkWidget *pEventWidget = getMouseEventWidget();
gtk_widget_set_app_paintable(GTK_WIDGET(m_pFixedContainer), true);
/*non-X11 displays won't show anything at all without double-buffering
enabled*/
if (GDK_IS_X11_DISPLAY(getGdkDisplay()))
gtk_widget_set_double_buffered(GTK_WIDGET(m_pFixedContainer), false);
gtk_widget_set_redraw_on_allocate(GTK_WIDGET(m_pFixedContainer), false);
// connect signals
g_signal_connect( G_OBJECT(m_pWindow), "style-updated", G_CALLBACK(signalStyleUpdated), this );
gtk_widget_set_has_tooltip(pEventWidget, true);
m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "query-tooltip", G_CALLBACK(signalTooltipQuery), this ));
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), "motion-notify-event", G_CALLBACK(signalMotion), this ));
m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-release-event", G_CALLBACK(signalButton), this ));
//Drop Target Stuff
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 ));
//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 ));
m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "scroll-event", G_CALLBACK(signalScroll), this ));
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 );
#if GTK_CHECK_VERSION(3,14,0)
GtkGesture *pSwipe = gtk_gesture_swipe_new(pEventWidget);
g_signal_connect(pSwipe, "swipe", G_CALLBACK(gestureSwipe), this);
gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pSwipe), GTK_PHASE_TARGET);
g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pSwipe);
GtkGesture *pLongPress = gtk_gesture_long_press_new(pEventWidget);
g_signal_connect(pLongPress, "pressed", G_CALLBACK(gestureLongPress), this);
gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pLongPress), GTK_PHASE_TARGET);
g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pLongPress);
#endif
g_signal_connect( G_OBJECT(m_pWindow), "focus-in-event", G_CALLBACK(signalFocus), this );
g_signal_connect( G_OBJECT(m_pWindow), "focus-out-event", G_CALLBACK(signalFocus), this );
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), "configure-event", G_CALLBACK(signalConfigure), this );
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 );
g_signal_connect( G_OBJECT(m_pWindow), "delete-event", G_CALLBACK(signalDelete), this );
g_signal_connect( G_OBJECT(m_pWindow), "window-state-event", G_CALLBACK(signalWindowState), this );
g_signal_connect( G_OBJECT(m_pWindow), "leave-notify-event", G_CALLBACK(signalCrossing), this );
g_signal_connect( G_OBJECT(m_pWindow), "enter-notify-event", G_CALLBACK(signalCrossing), this );
g_signal_connect( G_OBJECT(m_pWindow), "visibility-notify-event", G_CALLBACK(signalVisibility), this );
g_signal_connect( G_OBJECT(m_pWindow), "destroy", G_CALLBACK(signalDestroy), this );
// init members
m_pCurrentCursor = nullptr;
m_nKeyModifiers = 0;
m_bFullscreen = false;
m_bSpanMonitorsWhenFullscreen = false;
m_nState = GDK_WINDOW_STATE_WITHDRAWN;
m_nVisibility = GDK_VISIBILITY_FULLY_OBSCURED;
m_bSendModChangeOnRelease = false;
m_pIMHandler = nullptr;
m_nExtStyle = 0;
m_pRegion = nullptr;
m_pDropTarget = nullptr;
m_pDragSource = nullptr;
m_bInDrag = false;
m_pFormatConversionRequest = nullptr;
m_ePointerStyle = static_cast<PointerStyle>(0xffff);
m_pSalMenu = nullptr;
m_nWatcherId = 0;
m_nMenuExportId = 0;
m_nAppMenuExportId = 0;
m_nActionGroupExportId = 0;
m_nAppActionGroupExportId = 0;
m_nHudAwarenessId = 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_VISIBILITY_NOTIFY_MASK | GDK_SCROLL_MASK
);
// show the widgets
gtk_widget_show_all(GTK_WIDGET(m_pTopLevelGrid));
// realize the window, we need an XWindow id
gtk_widget_realize( m_pWindow );
//system data
m_aSystemData.nSize = sizeof( SystemEnvData );
m_aSystemData.aWindow = 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.pAppContext = nullptr;
m_aSystemData.pShellWidget = m_aSystemData.pWidget;
m_aSystemData.pToolkit = "gtk3";
GdkScreen* pScreen = gtk_window_get_screen(GTK_WINDOW(m_pWindow));
GdkVisual* pVisual = gdk_screen_get_system_visual(pScreen);
m_aSystemData.nDepth = gdk_visual_get_depth(pVisual);
#if defined(GDK_WINDOWING_X11)
GdkDisplay *pDisplay = getGdkDisplay();
if (GDK_IS_X11_DISPLAY(pDisplay))
{
m_aSystemData.pDisplay = gdk_x11_display_get_xdisplay(pDisplay);
m_aSystemData.pVisual = gdk_x11_visual_get_xvisual(pVisual);
}
#endif
m_bGraphics = false;
m_pGraphics = nullptr;
m_nWidthRequest = 0;
m_nHeightRequest = 0;
// fake an initial geometry, gets updated via configure event or SetPosSize
if( m_bDefaultPos || m_bDefaultSize )
{
Size aDefSize = calcDefaultSize();
maGeometry.nX = -1;
maGeometry.nY = -1;
maGeometry.nWidth = aDefSize.Width();
maGeometry.nHeight = aDefSize.Height();
if( m_pParent )
{
// approximation
maGeometry.nTopDecoration = m_pParent->maGeometry.nTopDecoration;
maGeometry.nBottomDecoration = m_pParent->maGeometry.nBottomDecoration;
maGeometry.nLeftDecoration = m_pParent->maGeometry.nLeftDecoration;
maGeometry.nRightDecoration = m_pParent->maGeometry.nRightDecoration;
}
else
{
maGeometry.nTopDecoration = 0;
maGeometry.nBottomDecoration = 0;
maGeometry.nLeftDecoration = 0;
maGeometry.nRightDecoration = 0;
}
}
updateScreenNumber();
SetIcon(1);
}
GtkSalFrame *GtkSalFrame::getFromWindow( GtkWindow *pWindow )
{
return static_cast<GtkSalFrame *>(g_object_get_data( G_OBJECT( pWindow ), "SalFrame" ));
}
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);
m_pForeignParent = nullptr;
m_aForeignParentWindow = None;
m_pForeignTopLevel = nullptr;
m_aForeignTopLevelWindow = None;
m_nStyle = nStyle;
GtkWindowType eWinType = ( (nStyle & SalFrameStyleFlags::FLOAT) &&
! (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION)
)
? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL;
if( nStyle & SalFrameStyleFlags::SYSTEMCHILD )
{
m_pWindow = gtk_event_box_new();
if( m_pParent )
{
// insert into container
gtk_fixed_put( m_pParent->getFixedContainer(),
m_pWindow, 0, 0 );
}
}
else
{
m_pWindow = gtk_window_new(eWinType);
}
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( m_pParent && m_pParent->m_pWindow && ! isChild() )
gtk_window_set_screen( GTK_WINDOW(m_pWindow), gtk_window_get_screen( GTK_WINDOW(m_pParent->m_pWindow) ) );
if (m_pParent)
{
if (!(m_pParent->m_nStyle & SalFrameStyleFlags::PLUG))
gtk_window_set_transient_for( GTK_WINDOW(m_pWindow), GTK_WINDOW(m_pParent->m_pWindow) );
m_pParent->m_aChildren.push_back( this );
gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), GTK_WINDOW(m_pWindow));
}
else
{
gtk_window_group_add_window(gtk_window_group_new(), GTK_WINDOW(m_pWindow));
g_object_unref(gtk_window_get_group(GTK_WINDOW(m_pWindow)));
}
// set window type
bool bDecoHandling =
! isChild() &&
( ! (nStyle & SalFrameStyleFlags::FLOAT) ||
(nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) );
if( bDecoHandling )
{
GdkWindowTypeHint eType = GDK_WINDOW_TYPE_HINT_NORMAL;
if( (nStyle & SalFrameStyleFlags::DIALOG) && m_pParent != nullptr )
eType = GDK_WINDOW_TYPE_HINT_DIALOG;
if( (nStyle & SalFrameStyleFlags::INTRO) )
{
gtk_window_set_role( GTK_WINDOW(m_pWindow), "splashscreen" );
eType = GDK_WINDOW_TYPE_HINT_SPLASHSCREEN;
}
else if( (nStyle & SalFrameStyleFlags::TOOLWINDOW ) )
{
eType = GDK_WINDOW_TYPE_HINT_DIALOG;
gtk_window_set_skip_taskbar_hint( GTK_WINDOW(m_pWindow), true );
}
else if( (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) )
{
eType = GDK_WINDOW_TYPE_HINT_TOOLBAR;
gtk_window_set_accept_focus(GTK_WINDOW(m_pWindow), false);
gtk_window_set_decorated(GTK_WINDOW(m_pWindow), false);
}
gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), eType );
gtk_window_set_gravity( GTK_WINDOW(m_pWindow), GDK_GRAVITY_STATIC );
gtk_window_set_resizable( GTK_WINDOW(m_pWindow), bool(nStyle & SalFrameStyleFlags::SIZEABLE) );
}
else if( (nStyle & SalFrameStyleFlags::FLOAT) )
gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), GDK_WINDOW_TYPE_HINT_POPUP_MENU );
InitCommon();
if( eWinType == GTK_WINDOW_TOPLEVEL )
{
#ifdef ENABLE_GMENU_INTEGRATION
// Enable DBus native menu if available.
ensure_dbus_setup( this );
#endif
}
}
GdkNativeWindow GtkSalFrame::findTopLevelSystemWindow( GdkNativeWindow aWindow )
{
(void)aWindow;
//FIXME: no findToplevelSystemWindow
return 0;
}
void GtkSalFrame::Init( SystemParentData* pSysData )
{
m_pParent = nullptr;
m_aForeignParentWindow = (GdkNativeWindow)pSysData->aWindow;
m_pForeignParent = nullptr;
m_aForeignTopLevelWindow = findTopLevelSystemWindow( (GdkNativeWindow)pSysData->aWindow );
m_pForeignTopLevel = gdk_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 );
m_bWindowIsGtkPlug = true;
widget_set_can_default( m_pWindow, true );
widget_set_can_focus( m_pWindow, true );
gtk_widget_set_sensitive( m_pWindow, true );
}
else
{
m_pWindow = gtk_window_new( GTK_WINDOW_POPUP );
m_bWindowIsGtkPlug = false;
}
m_nStyle = SalFrameStyleFlags::PLUG;
InitCommon();
m_pForeignParent = gdk_window_foreign_new_for_display( getGdkDisplay(), m_aForeignParentWindow );
gdk_window_set_events( m_pForeignParent, GDK_STRUCTURE_MASK );
//FIXME: Handling embedded windows, is going to be fun ...
}
void GtkSalFrame::SetExtendedFrameStyle( SalExtStyle nStyle )
{
if( nStyle != m_nExtStyle && ! isChild() )
{
m_nExtStyle = nStyle;
updateWMClass();
}
}
SalGraphics* GtkSalFrame::AcquireGraphics()
{
if( m_bGraphics )
return nullptr;
if( !m_pGraphics )
{
m_pGraphics = new GtkSalGraphics( this, m_pWindow );
if (!m_pSurface)
{
AllocateFrame();
TriggerPaintEvent();
}
m_pGraphics->setSurface(m_pSurface);
}
m_bGraphics = true;
return m_pGraphics;
}
void GtkSalFrame::ReleaseGraphics( SalGraphics* pGraphics )
{
(void) pGraphics;
assert( pGraphics == m_pGraphics );
m_bGraphics = false;
}
bool GtkSalFrame::PostEvent(ImplSVEvent* pData)
{
getDisplay()->SendInternalEvent( this, pData );
return true;
}
void GtkSalFrame::SetTitle( const OUString& rTitle )
{
m_aTitle = rTitle;
if( m_pWindow && ! isChild() )
gtk_window_set_title( GTK_WINDOW(m_pWindow), OUStringToOString( rTitle, RTL_TEXTENCODING_UTF8 ).getStr() );
}
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");
gtk_window_set_icon_name (GTK_WINDOW (m_pWindow), appicon);
g_free (appicon);
}
void GtkSalFrame::SetMenu( SalMenu* pSalMenu )
{
// if(m_pSalMenu)
// {
// static_cast<GtkSalMenu*>(m_pSalMenu)->DisconnectFrame();
// }
m_pSalMenu = pSalMenu;
}
SalMenu* GtkSalFrame::GetMenu()
{
return m_pSalMenu;
}
void GtkSalFrame::DrawMenuBar()
{
}
void GtkSalFrame::Center()
{
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);
}
Size GtkSalFrame::calcDefaultSize()
{
return bestmaxFrameSizeForScreenSize(getDisplay()->GetScreenSize(GetDisplayScreen()));
}
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 )
{
if( bVisible )
{
getDisplay()->startupNotificationCompleted();
if( m_bDefaultPos )
Center();
if( m_bDefaultSize )
SetDefaultSize();
setMinMaxSize();
if (isFloatGrabWindow() && !getDisplay()->GetCaptureFrame())
{
m_pParent->grabPointer(true, true);
m_pParent->addGrabLevel();
}
gtk_widget_show(m_pWindow);
if( isFloatGrabWindow() )
{
m_nFloats++;
if (!getDisplay()->GetCaptureFrame())
{
grabPointer(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);
m_pParent->removeGrabLevel();
m_pParent->grabPointer(false);
}
}
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() )
{
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 ) );
}
}
}
void GtkSalFrame::SetMaxClientSize( long nWidth, long nHeight )
{
if( ! isChild() )
{
m_aMaxSize = Size( nWidth, nHeight );
setMinMaxSize();
}
}
void GtkSalFrame::SetMinClientSize( long nWidth, long nHeight )
{
if( ! isChild() )
{
m_aMinSize = Size( nWidth, nHeight );
if( m_pWindow )
{
widget_set_size_request(nWidth, nHeight);
setMinMaxSize();
}
}
}
// FIXME: we should really be an SvpSalFrame sub-class, and
// share their AllocateFrame !
void GtkSalFrame::AllocateFrame()
{
basegfx::B2IVector aFrameSize( maGeometry.nWidth, maGeometry.nHeight );
if (!m_pSurface || cairo_image_surface_get_width(m_pSurface) != aFrameSize.getX() ||
cairo_image_surface_get_height(m_pSurface) != aFrameSize.getY() )
{
if( aFrameSize.getX() == 0 )
aFrameSize.setX( 1 );
if( aFrameSize.getY() == 0 )
aFrameSize.setY( 1 );
if (m_pSurface)
cairo_surface_destroy(m_pSurface);
#if GTK_CHECK_VERSION(3,10,0)
m_pSurface = gdk_window_create_similar_image_surface(widget_get_window(m_pWindow),
CAIRO_FORMAT_ARGB32,
aFrameSize.getX(),
aFrameSize.getY(),
0);
#else
m_pSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
aFrameSize.getX(),
aFrameSize.getY());
#endif
cairo_surface_set_user_data(m_pSurface, SvpSalGraphics::getDamageKey(), &m_aDamageHandler, nullptr);
SAL_INFO("vcl.gtk3", "allocated Frame size of " << maGeometry.nWidth << " x " << maGeometry.nHeight);
if (m_pGraphics)
m_pGraphics->setSurface(m_pSurface);
}
}
void GtkSalFrame::SetPosSize( long nX, long nY, long nWidth, 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;
if( isChild( false ) )
widget_set_size_request(nWidth, nHeight);
else if( ! ( m_nState & GDK_WINDOW_STATE_MAXIMIZED ) )
window_resize(nWidth, nHeight);
setMinMaxSize();
}
else if( m_bDefaultSize )
SetDefaultSize();
m_bDefaultSize = false;
if( nFlags & ( SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ) )
{
if( m_pParent )
{
if( AllSettings::GetLayoutRTL() )
nX = m_pParent->maGeometry.nWidth-m_nWidthRequest-1-nX;
nX += m_pParent->maGeometry.nX;
nY += m_pParent->maGeometry.nY;
}
m_bDefaultPos = false;
moveWindow(nX, nY);
updateScreenNumber();
}
else if( m_bDefaultPos )
Center();
m_bDefaultPos = false;
}
void GtkSalFrame::GetClientSize( long& rWidth, long& rHeight )
{
if( m_pWindow && !(m_nState & GDK_WINDOW_STATE_ICONIFIED) )
{
rWidth = maGeometry.nWidth;
rHeight = maGeometry.nHeight;
}
else
rWidth = rHeight = 0;
}
void GtkSalFrame::GetWorkArea( Rectangle& rRect )
{
GdkScreen *pScreen = gtk_window_get_screen(GTK_WINDOW(m_pWindow));
Rectangle aRetRect;
int max = gdk_screen_get_n_monitors (pScreen);
for (int i = 0; i < max; ++i)
{
GdkRectangle aRect;
gdk_screen_get_monitor_workarea(pScreen, i, &aRect);
Rectangle aMonitorRect(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height);
aRetRect.Union(aMonitorRect);
}
rRect = aRetRect;
}
SalFrame* GtkSalFrame::GetParent() const
{
return m_pParent;
}
void GtkSalFrame::SetWindowState( const SalFrameState* pState )
{
if( ! m_pWindow || ! pState || isChild( true, false ) )
return;
const WindowStateMask nMaxGeometryMask =
WindowStateMask::X | WindowStateMask::Y |
WindowStateMask::Width | WindowStateMask::Height |
WindowStateMask::MaximizedX | WindowStateMask::MaximizedY |
WindowStateMask::MaximizedWidth | WindowStateMask::MaximizedHeight;
if( (pState->mnMask & WindowStateMask::State) &&
! ( m_nState & GDK_WINDOW_STATE_MAXIMIZED ) &&
(pState->mnState & WindowStateState::Maximized) &&
(pState->mnMask & nMaxGeometryMask) == nMaxGeometryMask )
{
resizeWindow( pState->mnWidth, pState->mnHeight );
moveWindow( pState->mnX, pState->mnY );
m_bDefaultPos = m_bDefaultSize = false;
updateScreenNumber();
m_nState = GdkWindowState( m_nState | GDK_WINDOW_STATE_MAXIMIZED );
m_aRestorePosSize = Rectangle( Point( pState->mnX, pState->mnY ),
Size( pState->mnWidth, pState->mnHeight ) );
}
else if( pState->mnMask & (WindowStateMask::X | WindowStateMask::Y |
WindowStateMask::Width | WindowStateMask::Height ) )
{
sal_uInt16 nPosSizeFlags = 0;
long nX = pState->mnX - (m_pParent ? m_pParent->maGeometry.nX : 0);
long nY = pState->mnY - (m_pParent ? m_pParent->maGeometry.nY : 0);
if( pState->mnMask & WindowStateMask::X )
nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
else
nX = maGeometry.nX - (m_pParent ? m_pParent->maGeometry.nX : 0);
if( pState->mnMask & WindowStateMask::Y )
nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
else
nY = maGeometry.nY - (m_pParent ? m_pParent->maGeometry.nY : 0);
if( pState->mnMask & WindowStateMask::Width )
nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
if( pState->mnMask & WindowStateMask::Height )
nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
SetPosSize( nX, nY, pState->mnWidth, pState->mnHeight, nPosSizeFlags );
}
if( pState->mnMask & WindowStateMask::State && ! isChild() )
{
if( pState->mnState & WindowStateState::Maximized )
gtk_window_maximize( GTK_WINDOW(m_pWindow) );
else
gtk_window_unmaximize( GTK_WINDOW(m_pWindow) );
/* #i42379# there is no rollup state in GDK; and rolled up windows are
* (probably depending on the WM) reported as iconified. If we iconify a
* window here that was e.g. a dialog, then it will be unmapped but still
* not be displayed in the task list, so it's an iconified window that
* the user cannot get out of this state. So do not set the iconified state
* on windows with a parent (that is transient frames) since these tend
* to not be represented in an icon task list.
*/
if( (pState->mnState & WindowStateState::Minimized)
&& ! m_pParent )
gtk_window_iconify( GTK_WINDOW(m_pWindow) );
else
gtk_window_deiconify( GTK_WINDOW(m_pWindow) );
}
TriggerPaintEvent();
}
namespace
{
void GetPosAndSize(GtkWindow *pWindow, long& rX, long &rY, long &rWidth, long &rHeight)
{
gint root_x, root_y;
gtk_window_get_position(GTK_WINDOW(pWindow), &root_x, &root_y);
rX = root_x;
rY = root_y;
gint width, height;
gtk_window_get_size(GTK_WINDOW(pWindow), &width, &height);
rWidth = width;
rHeight = height;
}
Rectangle GetPosAndSize(GtkWindow *pWindow)
{
long nX, nY, nWidth, nHeight;
GetPosAndSize(pWindow, nX, nY, nWidth, nHeight);
return Rectangle(nX, nY, nX + nWidth, nY + nHeight);
}
}
bool GtkSalFrame::GetWindowState( SalFrameState* pState )
{
pState->mnState = WindowStateState::Normal;
pState->mnMask = WindowStateMask::State;
// rollup ? gtk 2.2 does not seem to support the shaded state
if( (m_nState & GDK_WINDOW_STATE_ICONIFIED) )
pState->mnState |= WindowStateState::Minimized;
if( m_nState & GDK_WINDOW_STATE_MAXIMIZED )
{
pState->mnState |= WindowStateState::Maximized;
pState->mnX = m_aRestorePosSize.Left();
pState->mnY = m_aRestorePosSize.Top();
pState->mnWidth = m_aRestorePosSize.GetWidth();
pState->mnHeight = m_aRestorePosSize.GetHeight();
GetPosAndSize(GTK_WINDOW(m_pWindow), pState->mnMaximizedX, pState->mnMaximizedY,
pState->mnMaximizedWidth, pState->mnMaximizedHeight);
pState->mnMask |= WindowStateMask::MaximizedX |
WindowStateMask::MaximizedY |
WindowStateMask::MaximizedWidth |
WindowStateMask::MaximizedHeight;
}
else
{
GetPosAndSize(GTK_WINDOW(m_pWindow), pState->mnX, pState->mnY,
pState->mnWidth, pState->mnHeight);
}
pState->mnMask |= WindowStateMask::X |
WindowStateMask::Y |
WindowStateMask::Width |
WindowStateMask::Height;
return true;
}
void GtkSalFrame::SetScreen( unsigned int nNewScreen, SetType eType, Rectangle *pSize )
{
if( !m_pWindow )
return;
if (maGeometry.nDisplayScreenNumber == nNewScreen && eType == SetType::RetainSize)
return;
int nX = maGeometry.nX, nY = maGeometry.nY,
nWidth = maGeometry.nWidth, nHeight = maGeometry.nHeight;
GdkScreen *pScreen = nullptr;
GdkRectangle aNewMonitor;
bool bSpanAllScreens = nNewScreen == (unsigned int)-1;
m_bSpanMonitorsWhenFullscreen = bSpanAllScreens && getDisplay()->getSystem()->GetDisplayScreenCount() > 1;
gint nMonitor = -1;
if (m_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_window( 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 = IS_WIDGET_MAPPED( m_pWindow );
if( bVisible )
Show( false );
if( eType == SetType::Fullscreen )
{
nX = aNewMonitor.x;
nY = aNewMonitor.y;
nWidth = aNewMonitor.width;
nHeight = aNewMonitor.height;
m_nStyle |= SalFrameStyleFlags::PARTIAL_FULLSCREEN;
bResize = true;
// #i110881# for the benefit of compiz set a max size here
// else setting to fullscreen fails for unknown reasons
m_aMaxSize.Width() = aNewMonitor.width;
m_aMaxSize.Height() = aNewMonitor.height;
}
if( pSize && eType == SetType::UnFullscreen )
{
nX = pSize->Left();
nY = pSize->Top();
nWidth = pSize->GetWidth();
nHeight = pSize->GetHeight();
m_nStyle &= ~SalFrameStyleFlags::PARTIAL_FULLSCREEN;
bResize = true;
}
if (bResize)
{
// temporarily re-sizeable
if( !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
gtk_window_set_resizable( GTK_WINDOW(m_pWindow), TRUE );
window_resize(nWidth, nHeight);
}
gtk_window_move(GTK_WINDOW(m_pWindow), nX, nY);
#if GTK_CHECK_VERSION(3,8,0)
gdk_window_set_fullscreen_mode( widget_get_window(m_pWindow), m_bSpanMonitorsWhenFullscreen
? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR );
#endif
if( eType == SetType::Fullscreen )
{
if (m_bSpanMonitorsWhenFullscreen)
gtk_window_fullscreen(GTK_WINDOW(m_pWindow));
else
{
#if GTK_CHECK_VERSION(3,18,0)
gtk_window_fullscreen_on_monitor(GTK_WINDOW(m_pWindow), pScreen, nMonitor);
#else
gtk_window_fullscreen(GTK_WINDOW(m_pWindow));
#endif
}
}
else if( eType == SetType::UnFullscreen )
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( std::list< GtkSalFrame* >::iterator it = aChildren.begin(); it != aChildren.end(); ++it )
(*it)->SetScreen( nNewScreen, SetType::RetainSize );
m_bDefaultPos = m_bDefaultSize = false;
updateScreenNumber();
if( bVisible )
Show( true );
}
void GtkSalFrame::SetScreenNumber( unsigned int nNewScreen )
{
SetScreen( nNewScreen, SetType::RetainSize );
}
void GtkSalFrame::updateWMClass()
{
OString aResClass = OUStringToOString(m_sWMClass, RTL_TEXTENCODING_ASCII_US);
const char *pResClass = !aResClass.isEmpty() ? aResClass.getStr() :
SalGenericSystem::getFrameClassName();
Display *display;
if (!getDisplay()->IsX11Display())
return;
display = GDK_DISPLAY_XDISPLAY(getGdkDisplay());
if( IS_WIDGET_REALIZED( m_pWindow ) )
{
XClassHint* pClass = XAllocClassHint();
OString aResName = SalGenericSystem::getFrameResName();
pClass->res_name = const_cast<char*>(aResName.getStr());
pClass->res_class = const_cast<char*>(pResClass);
XSetClassHint( display,
widget_get_xid(m_pWindow),
pClass );
XFree( pClass );
}
}
void GtkSalFrame::SetApplicationID( const OUString &rWMClass )
{
if( rWMClass != m_sWMClass && ! isChild() )
{
m_sWMClass = rWMClass;
updateWMClass();
for( std::list< GtkSalFrame* >::iterator it = m_aChildren.begin(); it != m_aChildren.end(); ++it )
(*it)->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 = Rectangle();
}
}
void GtkSalFrame::StartPresentation( bool bStart )
{
boost::optional<guint> aWindow;
boost::optional<Display*> aDisplay;
if( getDisplay()->IsX11Display() )
{
aWindow = widget_get_xid(m_pWindow);
aDisplay = GDK_DISPLAY_XDISPLAY( getGdkDisplay() );
}
m_ScreenSaverInhibitor.inhibit( bStart,
"presentation",
getDisplay()->IsX11Display(),
aWindow,
aDisplay );
}
void GtkSalFrame::SetAlwaysOnTop( bool bOnTop )
{
if( m_pWindow )
gtk_window_set_keep_above( GTK_WINDOW( m_pWindow ), bOnTop );
}
static guint32 nLastUserInputTime = GDK_CURRENT_TIME;
guint32 GtkSalFrame::GetLastInputEventTime()
{
return nLastUserInputTime;
}
static void UpdateLastInputEventTime(guint32 nUserInputTime)
{
nLastUserInputTime = nUserInputTime;
}
void GtkSalFrame::ToTop( SalFrameToTop nFlags )
{
if( m_pWindow )
{
if( isChild( false ) )
gtk_widget_grab_focus( m_pWindow );
else if( IS_WIDGET_MAPPED( m_pWindow ) )
{
if (!(nFlags & SalFrameToTop::GrabFocusOnly))
gtk_window_present_with_time(GTK_WINDOW(m_pWindow), GetLastInputEventTime());
else
gdk_window_focus(widget_get_window(m_pWindow), GetLastInputEventTime());
}
else
{
if( nFlags & SalFrameToTop::RestoreWhenMin )
gtk_window_present( GTK_WINDOW(m_pWindow) );
}
}
}
void GtkSalFrame::SetPointer( PointerStyle ePointerStyle )
{
if( m_pWindow && ePointerStyle != m_ePointerStyle )
{
m_ePointerStyle = ePointerStyle;
GdkCursor *pCursor = getDisplay()->getCursor( ePointerStyle );
gdk_window_set_cursor( widget_get_window(m_pWindow), pCursor );
m_pCurrentCursor = pCursor;
// #i80791# use grabPointer the same way as CaptureMouse, respective float grab
if( getDisplay()->MouseCaptured( this ) )
grabPointer( true );
else if( m_nFloats > 0 )
grabPointer( true, true );
}
}
void GtkSalFrame::grabPointer( bool bGrab, bool bOwnerEvents )
{
static const char* pEnv = getenv( "SAL_NO_MOUSEGRABS" );
if (pEnv && *pEnv)
return;
if (!m_pWindow)
return;
#if GTK_CHECK_VERSION(3, 20, 0)
if (gtk_check_version(3, 20, 0) == nullptr)
{
GdkSeat* pSeat = gdk_display_get_default_seat(getGdkDisplay());
if (bGrab)
{
gdk_seat_grab(pSeat, widget_get_window(getMouseEventWidget()), GDK_SEAT_CAPABILITY_ALL_POINTING,
bOwnerEvents, nullptr, nullptr, nullptr, nullptr);
}
else
{
gdk_seat_ungrab(pSeat);
}
return;
}
#endif
//else older gtk3
const int nMask = (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(getGdkDisplay());
GdkDevice* pPointer = gdk_device_manager_get_client_pointer(pDeviceManager);
if (bGrab)
{
gdk_device_grab(pPointer, widget_get_window(getMouseEventWidget()), GDK_OWNERSHIP_NONE,
bOwnerEvents, (GdkEventMask) nMask, m_pCurrentCursor, gtk_get_current_event_time());
}
else
{
gdk_device_ungrab(pPointer, gtk_get_current_event_time());
}
}
void GtkSalFrame::CaptureMouse( bool bCapture )
{
getDisplay()->CaptureMouse( bCapture ? this : nullptr );
}
void GtkSalFrame::SetPointerPos( long nX, long nY )
{
GtkSalFrame* pFrame = this;
while( pFrame && pFrame->isChild( false ) )
pFrame = pFrame->m_pParent;
if( ! pFrame )
return;
GdkScreen *pScreen = gtk_window_get_screen( GTK_WINDOW(pFrame->m_pWindow) );
GdkDisplay *pDisplay = gdk_screen_get_display( pScreen );
/* when the application tries to center the mouse in the dialog the
* window isn't mapped already. So use coordinates relative to the root window.
*/
unsigned int nWindowLeft = maGeometry.nX + nX;
unsigned int nWindowTop = maGeometry.nY + nY;
XWarpPointer( GDK_DISPLAY_XDISPLAY (pDisplay), None,
GDK_WINDOW_XID (gdk_screen_get_root_window( pScreen ) ),
0, 0, 0, 0, nWindowLeft, nWindowTop);
// #i38648# ask for the next motion hint
gint x, y;
GdkModifierType mask;
gdk_window_get_pointer( widget_get_window(pFrame->m_pWindow) , &x, &y, &mask );
}
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 = (GdkModifierType) ( nModifiers | GDK_SHIFT_MASK );
if ( rKeyCode.IsMod1() )
nModifiers = (GdkModifierType) ( nModifiers | GDK_CONTROL_MASK );
if ( rKeyCode.IsMod2() )
nModifiers = (GdkModifierType) ( nModifiers | GDK_MOD1_MASK );
*pGdkModifiers = nModifiers;
// Get GDK keycode.
guint nKeyCode = 0;
guint nCode = rKeyCode.GetCode();
if ( nCode >= KEY_0 && nCode <= KEY_9 )
nKeyCode = ( nCode - KEY_0 ) + GDK_KEY_0;
else if ( nCode >= KEY_A && nCode <= KEY_Z )
nKeyCode = ( nCode - KEY_A ) + GDK_KEY_A;
else if ( nCode >= KEY_F1 && nCode <= KEY_F26 )
nKeyCode = ( nCode - KEY_F1 ) + GDK_KEY_F1;
else
{
switch (nCode)
{
case KEY_DOWN: nKeyCode = GDK_KEY_Down; break;
case KEY_UP: nKeyCode = GDK_KEY_Up; break;
case KEY_LEFT: nKeyCode = GDK_KEY_Left; break;
case KEY_RIGHT: nKeyCode = GDK_KEY_Right; break;
case KEY_HOME: nKeyCode = GDK_KEY_Home; break;
case KEY_END: nKeyCode = GDK_KEY_End; break;
case KEY_PAGEUP: nKeyCode = GDK_KEY_Page_Up; break;
case KEY_PAGEDOWN: nKeyCode = GDK_KEY_Page_Down; break;
case KEY_RETURN: nKeyCode = GDK_KEY_Return; break;
case KEY_ESCAPE: nKeyCode = GDK_KEY_Escape; break;
case KEY_TAB: nKeyCode = GDK_KEY_Tab; break;
case KEY_BACKSPACE: nKeyCode = GDK_KEY_BackSpace; break;
case KEY_SPACE: nKeyCode = GDK_KEY_space; break;
case KEY_INSERT: nKeyCode = GDK_KEY_Insert; break;
case KEY_DELETE: nKeyCode = GDK_KEY_Delete; break;
case KEY_ADD: nKeyCode = GDK_KEY_plus; break;
case KEY_SUBTRACT: nKeyCode = GDK_KEY_minus; break;
case KEY_MULTIPLY: nKeyCode = GDK_KEY_asterisk; break;
case KEY_DIVIDE: nKeyCode = GDK_KEY_slash; break;
case KEY_POINT: nKeyCode = GDK_KEY_period; break;
case KEY_COMMA: nKeyCode = GDK_KEY_comma; break;
case KEY_LESS: nKeyCode = GDK_KEY_less; break;
case KEY_GREATER: nKeyCode = GDK_KEY_greater; break;
case KEY_EQUAL: nKeyCode = GDK_KEY_equal; break;
case KEY_FIND: nKeyCode = GDK_KEY_Find; break;
case KEY_CONTEXTMENU: nKeyCode = GDK_KEY_Menu; break;
case KEY_HELP: nKeyCode = GDK_KEY_Help; break;
case KEY_UNDO: nKeyCode = GDK_KEY_Undo; break;
case KEY_REPEAT: nKeyCode = GDK_KEY_Redo; break;
case KEY_DECIMAL: nKeyCode = GDK_KEY_KP_Decimal; break;
case KEY_TILDE: nKeyCode = GDK_KEY_asciitilde; break;
case KEY_QUOTELEFT: nKeyCode = GDK_KEY_quoteleft; break;
case KEY_BRACKETLEFT: nKeyCode = GDK_KEY_bracketleft; break;
case KEY_BRACKETRIGHT: nKeyCode = GDK_KEY_bracketright; break;
case KEY_SEMICOLON: nKeyCode = GDK_KEY_semicolon; break;
case KEY_QUOTERIGHT: nKeyCode = GDK_KEY_quoteright; break;
// Special cases
case KEY_COPY: nKeyCode = GDK_KEY_Copy; break;
case KEY_CUT: nKeyCode = GDK_KEY_Cut; break;
case KEY_PASTE: nKeyCode = GDK_KEY_Paste; break;
case KEY_OPEN: nKeyCode = GDK_KEY_Open; break;
}
}
*pGdkKeyCode = nKeyCode;
}
OUString GtkSalFrame::GetKeyName( sal_uInt16 nKeyCode )
{
guint nGtkKeyCode;
GdkModifierType nGtkModifiers;
KeyCodeToGdkKey(nKeyCode, &nGtkKeyCode, &nGtkModifiers );
gchar* pName = gtk_accelerator_get_label(nGtkKeyCode, nGtkModifiers);
OUString aRet(pName, rtl_str_getLength(pName), RTL_TEXTENCODING_UTF8);
g_free(pName);
return aRet;
}
GdkDisplay *GtkSalFrame::getGdkDisplay()
{
return GetGtkSalData()->GetGdkDisplay();
}
GtkSalDisplay *GtkSalFrame::getDisplay()
{
return GetGtkSalData()->GetGtkDisplay();
}
SalFrame::SalPointerState GtkSalFrame::GetPointerState()
{
SalPointerState aState;
GdkScreen* pScreen;
gint x, y;
GdkModifierType aMask;
gdk_display_get_pointer( getGdkDisplay(), &pScreen, &x, &y, &aMask );
aState.maPos = Point( x - maGeometry.nX, y - maGeometry.nY );
aState.mnState = GetMouseModCode( aMask );
return aState;
}
KeyIndicatorState GtkSalFrame::GetIndicatorState()
{
KeyIndicatorState nState = KeyIndicatorState::NONE;
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 GTK_CHECK_VERSION(3,18,0)
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 = 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;
bool bFreeGraphics = false;
if( ! pGraphics )
{
pGraphics = static_cast<GtkSalGraphics*>(AcquireGraphics());
if ( !pGraphics )
{
SAL_WARN("vcl", "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::SetParent( SalFrame* pNewParent )
{
if (m_pParent)
{
gtk_window_group_remove_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), GTK_WINDOW(m_pWindow));
m_pParent->m_aChildren.remove(this);
}
m_pParent = static_cast<GtkSalFrame*>(pNewParent);
if (m_pParent)
{
m_pParent->m_aChildren.push_back(this);
gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), GTK_WINDOW(m_pWindow));
}
if( ! isChild() )
gtk_window_set_transient_for( GTK_WINDOW(m_pWindow),
(m_pParent && ! m_pParent->isChild(true,false)) ? GTK_WINDOW(m_pParent->m_pWindow) : nullptr
);
}
bool GtkSalFrame::SetPluginParent( SystemParentData* pSysParent )
{
(void)pSysParent;
//FIXME: no SetPluginParent impl. for gtk3
return false;
}
void GtkSalFrame::ResetClipRegion()
{
if( m_pWindow )
gdk_window_shape_combine_region( widget_get_window( m_pWindow ), nullptr, 0, 0 );
}
void GtkSalFrame::BeginSetClipRegion( sal_uLong )
{
if( m_pRegion )
cairo_region_destroy( m_pRegion );
m_pRegion = cairo_region_create();
}
void GtkSalFrame::UnionClipRegion( long nX, long nY, long nWidth, 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( m_pWindow && m_pRegion )
gdk_window_shape_combine_region( widget_get_window(m_pWindow), m_pRegion, 0, 0 );
}
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())
return false;
gtk_tooltip_set_text(tooltip,
OUStringToOString(pThis->m_aTooltip, RTL_TEXTENCODING_UTF8).getStr());
GdkRectangle aHelpArea;
aHelpArea.x = pThis->m_aHelpArea.Left();
aHelpArea.y = pThis->m_aHelpArea.Top();
aHelpArea.width = pThis->m_aHelpArea.GetWidth();
aHelpArea.height = pThis->m_aHelpArea.GetHeight();
if (AllSettings::GetLayoutRTL())
aHelpArea.x = pThis->maGeometry.nWidth-aHelpArea.width-1-aHelpArea.x;
gtk_tooltip_set_tip_area(tooltip, &aHelpArea);
return true;
}
bool GtkSalFrame::ShowTooltip(const OUString& rHelpText, const Rectangle& rHelpArea)
{
m_aTooltip = rHelpText;
m_aHelpArea = rHelpArea;
gtk_widget_trigger_tooltip_query(getMouseEventWidget());
return true;
}
#if GTK_CHECK_VERSION(3,12,0)
namespace
{
void set_pointing_to(GtkPopover *pPopOver, const Rectangle& rHelpArea)
{
GdkRectangle aRect;
aRect.x = rHelpArea.Left();
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);
}
}
#endif
sal_uIntPtr GtkSalFrame::ShowPopover(const OUString& rHelpText, const Rectangle& rHelpArea, QuickHelpFlags nFlags)
{
#if GTK_CHECK_VERSION(3,12,0)
GtkWidget *pWidget = gtk_popover_new(getMouseEventWidget());
OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
GtkWidget *pLabel = gtk_label_new(sUTF.getStr());
gtk_container_add(GTK_CONTAINER(pWidget), pLabel);
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), rHelpArea);
gtk_popover_set_modal(GTK_POPOVER(pWidget), false);
gtk_widget_show_all(pWidget);
return reinterpret_cast<sal_uIntPtr>(pWidget);
#else
(void)rHelpText;
(void)rHelpArea;
(void)nFlags;
return 0;
#endif
}
bool GtkSalFrame::UpdatePopover(sal_uIntPtr nId, const OUString& rHelpText, const Rectangle& rHelpArea)
{
#if GTK_CHECK_VERSION(3,12,0)
GtkWidget *pWidget = reinterpret_cast<GtkWidget*>(nId);
set_pointing_to(GTK_POPOVER(pWidget), rHelpArea);
GtkWidget *pLabel = gtk_bin_get_child(GTK_BIN(pWidget));
OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
gtk_label_set_text(GTK_LABEL(pLabel), sUTF.getStr());
return true;
#else
(void)nId;
(void)rHelpText;
(void)rHelpArea;
return false;
#endif
}
bool GtkSalFrame::HidePopover(sal_uIntPtr nId)
{
#if GTK_CHECK_VERSION(3,12,0)
GtkWidget *pWidget = reinterpret_cast<GtkWidget*>(nId);
gtk_widget_destroy(pWidget);
return true;
#else
(void)nId;
return false;
#endif
}
void GtkSalFrame::addGrabLevel()
{
if (m_nGrabLevel == 0)
gtk_grab_add(getMouseEventWidget());
++m_nGrabLevel;
}
void GtkSalFrame::removeGrabLevel()
{
--m_nGrabLevel;
if (m_nGrabLevel == 0)
gtk_grab_remove(getMouseEventWidget());
}
void GtkSalFrame::closePopup()
{
if (!m_nFloats)
return;
ImplSVData* pSVData = ImplGetSVData();
if (!pSVData->maWinData.mpFirstFloat)
return;
if (pSVData->maWinData.mpFirstFloat->ImplGetFrame() != this)
return;
pSVData->maWinData.mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll);
}
gboolean GtkSalFrame::signalButton( GtkWidget*, GdkEventButton* pEvent, gpointer frame )
{
UpdateLastInputEventTime(pEvent->time);
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
SalMouseEvent aEvent;
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;
}
switch( pEvent->button )
{
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 = pEvent->time;
aEvent.mnX = (long)pEvent->x_root - pThis->maGeometry.nX;
aEvent.mnY = (long)pEvent->y_root - pThis->maGeometry.nY;
aEvent.mnCode = GetMouseModCode( pEvent->state );
vcl::DeletionListener aDel( pThis );
if (pEvent->type == GDK_BUTTON_PRESS && pThis->isFloatGrabWindow())
{
bool bClosePopups = (pEvent->window != widget_get_window(pThis->getMouseEventWidget()));
if (bClosePopups)
pThis->closePopup();
}
// --- RTL --- (mirror mouse pos)
if( AllSettings::GetLayoutRTL() )
aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;
if (!aDel.isDeleted())
{
pThis->CallCallbackExc( nEventType, &aEvent );
}
return true;
}
gboolean GtkSalFrame::signalScroll(GtkWidget*, GdkEventScroll* pEvent, gpointer frame)
{
UpdateLastInputEventTime(pEvent->time);
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
SalWheelMouseEvent aEvent;
aEvent.mnTime = pEvent->time;
aEvent.mnX = (sal_uLong)pEvent->x;
// --- RTL --- (mirror mouse pos)
if (AllSettings::GetLayoutRTL())
aEvent.mnX = pThis->maGeometry.nWidth - 1 - aEvent.mnX;
aEvent.mnY = (sal_uLong)pEvent->y;
aEvent.mnCode = GetMouseModCode( pEvent->state );
switch (pEvent->direction)
{
case GDK_SCROLL_SMOOTH:
// rhbz#1344042 "Traditionally" in gtk3 we tool a single up/down event as
// equating to 3 scroll lines and a delta of 120. So scale the delta here
// by 120 where a single mouse wheel click is an incoming delta_x of 1
// and divide that by 40 to get the number of scrollines
if (pEvent->delta_x != 0.0)
{
aEvent.mnDelta = -pEvent->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;
if (aEvent.mnScrollLines == 0)
aEvent.mnScrollLines = 1;
pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
}
if (pEvent->delta_y != 0.0)
{
aEvent.mnDelta = -pEvent->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;
if (aEvent.mnScrollLines == 0)
aEvent.mnScrollLines = 1;
pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
}
break;
case GDK_SCROLL_UP:
aEvent.mnDelta = 120;
aEvent.mnNotchDelta = 1;
aEvent.mnScrollLines = 3;
aEvent.mbHorz = false;
pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
break;
case GDK_SCROLL_DOWN:
aEvent.mnDelta = -120;
aEvent.mnNotchDelta = -1;
aEvent.mnScrollLines = 3;
aEvent.mbHorz = false;
pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
break;
case GDK_SCROLL_LEFT:
aEvent.mnDelta = 120;
aEvent.mnNotchDelta = 1;
aEvent.mnScrollLines = 3;
aEvent.mbHorz = true;
pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
break;
case GDK_SCROLL_RIGHT:
aEvent.mnDelta = -120;
aEvent.mnNotchDelta = -1;
aEvent.mnScrollLines = 3;
aEvent.mbHorz = true;
pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
break;
}
return true;
}
#if GTK_CHECK_VERSION(3,14,0)
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))
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
SalSwipeEvent aEvent;
aEvent.mnVelocityX = velocity_x;
aEvent.mnVelocityY = velocity_y;
aEvent.mnX = x;
aEvent.mnY = y;
pThis->CallCallbackExc(SalEvent::Swipe, &aEvent);
}
}
void GtkSalFrame::gestureLongPress(GtkGestureLongPress* gesture, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
if(pThis)
{
SalLongPressEvent aEvent;
gdouble x, y;
GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y);
aEvent.mnX = x;
aEvent.mnY = y;
pThis->CallCallbackExc(SalEvent::LongPress, &aEvent);
}
}
#endif
gboolean GtkSalFrame::signalMotion( GtkWidget*, GdkEventMotion* pEvent, gpointer frame )
{
UpdateLastInputEventTime(pEvent->time);
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
//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() && pEvent->window != widget_get_window(pThis->getMouseEventWidget()))
return true;
vcl::DeletionListener aDel( pThis );
int frame_x = (int)(pEvent->x_root - pEvent->x);
int frame_y = (int)(pEvent->y_root - pEvent->y);
if (frame_x != pThis->maGeometry.nX || frame_y != pThis->maGeometry.nY)
{
pThis->maGeometry.nX = frame_x;
pThis->maGeometry.nY = frame_y;
pThis->CallCallbackExc(SalEvent::Move, nullptr);
}
if( ! aDel.isDeleted() )
{
SalMouseEvent aEvent;
aEvent.mnTime = pEvent->time;
aEvent.mnX = (long)pEvent->x_root - pThis->maGeometry.nX;
aEvent.mnY = (long)pEvent->y_root - pThis->maGeometry.nY;
aEvent.mnCode = GetMouseModCode( pEvent->state );
aEvent.mnButton = 0;
// --- RTL --- (mirror mouse pos)
if( AllSettings::GetLayoutRTL() )
aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;
pThis->CallCallbackExc( SalEvent::MouseMove, &aEvent );
}
if( ! aDel.isDeleted() )
{
// ask for the next hint
gint x, y;
GdkModifierType mask;
gdk_window_get_pointer( widget_get_window(GTK_WIDGET(pThis->m_pWindow)), &x, &y, &mask );
}
return true;
}
gboolean GtkSalFrame::signalCrossing( GtkWidget*, GdkEventCrossing* pEvent, gpointer frame )
{
UpdateLastInputEventTime(pEvent->time);
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
SalMouseEvent aEvent;
aEvent.mnTime = pEvent->time;
aEvent.mnX = (long)pEvent->x_root - pThis->maGeometry.nX;
aEvent.mnY = (long)pEvent->y_root - pThis->maGeometry.nY;
aEvent.mnCode = GetMouseModCode( pEvent->state );
aEvent.mnButton = 0;
pThis->CallCallbackExc( (pEvent->type == GDK_ENTER_NOTIFY) ? SalEvent::MouseMove : SalEvent::MouseLeave, &aEvent );
return true;
}
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 (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);
}
gtk_widget_queue_draw_area(GTK_WIDGET(m_pFixedContainer),
nExtentsX, nExtentsY,
nExtentsWidth, nExtentsHeight);
}
// blit our backing cairo surface to the target cairo context
gboolean GtkSalFrame::signalDraw(GtkWidget*, cairo_t *cr, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
cairo_set_source_surface(cr, pThis->m_pSurface, 0, 0);
cairo_paint(cr);
return false;
}
void GtkSalFrame::sizeAllocated(GtkWidget*, GdkRectangle *pAllocation, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
pThis->maGeometry.nWidth = pAllocation->width;
pThis->maGeometry.nHeight = pAllocation->height;
pThis->AllocateFrame();
pThis->CallCallbackExc( SalEvent::Resize, nullptr );
pThis->TriggerPaintEvent();
}
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( x != pThis->maGeometry.nX || y != pThis->maGeometry.nY )
{
bMoved = true;
pThis->maGeometry.nX = x;
pThis->maGeometry.nY = y;
}
// update decoration hints
GdkRectangle aRect;
gdk_window_get_frame_extents( widget_get_window(GTK_WIDGET(pThis->m_pWindow)), &aRect );
pThis->maGeometry.nTopDecoration = y - aRect.y;
pThis->maGeometry.nBottomDecoration = aRect.y + aRect.height - y - pEvent->height;
pThis->maGeometry.nLeftDecoration = x - aRect.x;
pThis->maGeometry.nRightDecoration = aRect.x + aRect.width - x - pEvent->width;
pThis->updateScreenNumber();
if (bMoved)
pThis->CallCallbackExc(SalEvent::Move, nullptr);
return false;
}
void GtkSalFrame::TriggerPaintEvent()
{
//Under gtk2 we can basically paint directly into the XWindow and on
//additional "expose-event" events we can re-render the missing pieces
//
//Under gtk3 we have to keep our own buffer up to date and flush it into
//the given cairo context on "draw". So we emit a paint event on
//opportune resize trigger events to initially fill our backbuffer and then
//keep it up to date with our direct paints and tell gtk those regions
//have changed and then blit them into the provided cairo context when
//we get the "draw"
//
//The other alternative was to always paint everything on "draw", but
//that duplicates the amount of drawing and is hideously slow
SAL_INFO("vcl.gtk3", "force painting" << 0 << "," << 0 << " " << maGeometry.nWidth << "x" << maGeometry.nHeight);
SalPaintEvent aPaintEvt(0, 0, maGeometry.nWidth, maGeometry.nHeight, true);
CallCallbackExc(SalEvent::Paint, &aPaintEvt);
gtk_widget_queue_draw(GTK_WIDGET(m_pFixedContainer));
}
gboolean GtkSalFrame::signalFocus( GtkWidget*, GdkEventFocus* pEvent, gpointer frame )
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
SalGenericInstance *pSalInstance =
static_cast< SalGenericInstance* >(GetSalData()->m_pInstance);
// check if printers have changed (analogous to salframe focus handler)
pSalInstance->updatePrinterUpdate();
if( !pEvent->in )
{
pThis->m_nKeyModifiers = 0;
pThis->m_bSendModChangeOnRelease = false;
}
if( pThis->m_pIMHandler )
pThis->m_pIMHandler->focusChanged( pEvent->in );
// 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 )
pThis->CallCallbackExc( pEvent->in ? SalEvent::GetFocus : SalEvent::LoseFocus, nullptr );
return false;
}
gboolean GtkSalFrame::signalMap(GtkWidget *, GdkEvent*, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
pThis->CallCallbackExc( SalEvent::Resize, nullptr );
pThis->TriggerPaintEvent();
return false;
}
gboolean GtkSalFrame::signalUnmap( GtkWidget*, GdkEvent*, gpointer frame )
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
pThis->CallCallbackExc( SalEvent::Resize, nullptr );
return false;
}
gboolean GtkSalFrame::signalKey(GtkWidget* pWidget, GdkEventKey* pEvent, gpointer frame)
{
UpdateLastInputEventTime(pEvent->time);
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
if (pThis->isFloatGrabWindow())
return signalKey(pWidget, pEvent, pThis->m_pParent);
vcl::DeletionListener aDel( pThis );
if( pThis->m_pIMHandler )
{
if( pThis->m_pIMHandler->handleKeyEvent( pEvent ) )
return true;
}
// 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 )
{
SalKeyModEvent aModEvt;
sal_uInt16 nModCode = GetKeyModCode( pEvent->state );
aModEvt.mnModKeyCode = 0; // emit no MODKEYCHANGE events
if( pEvent->type == GDK_KEY_PRESS && !pThis->m_nKeyModifiers )
pThis->m_bSendModChangeOnRelease = true;
else if( pEvent->type == GDK_KEY_RELEASE &&
pThis->m_bSendModChangeOnRelease )
{
aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
}
sal_uInt16 nExtModMask = 0;
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 = MODKEY_LMOD1;
nModMask = KEY_MOD1;
break;
case GDK_KEY_Control_R:
nExtModMask = MODKEY_RMOD1;
nModMask = KEY_MOD1;
break;
case GDK_KEY_Alt_L:
nExtModMask = MODKEY_LMOD2;
nModMask = KEY_MOD2;
break;
case GDK_KEY_Alt_R:
nExtModMask = MODKEY_RMOD2;
nModMask = KEY_MOD2;
break;
case GDK_KEY_Shift_L:
nExtModMask = MODKEY_LSHIFT;
nModMask = KEY_SHIFT;
break;
case GDK_KEY_Shift_R:
nExtModMask = MODKEY_RSHIFT;
nModMask = KEY_SHIFT;
break;
// Map Meta/Super to MOD3 modifier on all Unix systems
// except Mac OS X
case GDK_KEY_Meta_L:
case GDK_KEY_Super_L:
nExtModMask = MODKEY_LMOD3;
nModMask = KEY_MOD3;
break;
case GDK_KEY_Meta_R:
case GDK_KEY_Super_R:
nExtModMask = MODKEY_RMOD3;
nModMask = KEY_MOD3;
break;
}
if( pEvent->type == GDK_KEY_RELEASE )
{
nModCode &= ~nModMask;
pThis->m_nKeyModifiers &= ~nExtModMask;
}
else
{
nModCode |= nModMask;
pThis->m_nKeyModifiers |= nExtModMask;
}
aModEvt.mnCode = nModCode;
aModEvt.mnTime = pEvent->time;
aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
pThis->CallCallbackExc( SalEvent::KeyModChange, &aModEvt );
}
else
{
pThis->doKeyCallback( pEvent->state,
pEvent->keyval,
pEvent->hardware_keycode,
pEvent->group,
pEvent->time,
sal_Unicode(gdk_keyval_to_unicode( pEvent->keyval )),
(pEvent->type == GDK_KEY_PRESS),
false );
if( ! aDel.isDeleted() )
pThis->m_bSendModChangeOnRelease = false;
}
if( !aDel.isDeleted() && pThis->m_pIMHandler )
pThis->m_pIMHandler->updateIMSpotLocation();
return false;
}
gboolean GtkSalFrame::signalDelete( GtkWidget*, GdkEvent*, gpointer frame )
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
//If we went into the backdrop we disabled the toplevel window, if we
//receive a delete here, re-enable so we can process it
bool bBackDrop = (gtk_widget_get_state_flags(GTK_WIDGET(pThis->m_pWindow)) & GTK_STATE_FLAG_BACKDROP);
if (bBackDrop)
pThis->GetWindow()->Enable();
pThis->CallCallbackExc( SalEvent::Close, nullptr );
return true;
}
void GtkSalFrame::signalStyleUpdated(GtkWidget*, gpointer frame)
{
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 );
// fire off font-changed when the system cairo font hints change
GtkInstance *pInstance = static_cast<GtkInstance*>(GetSalData()->m_pInstance);
const cairo_font_options_t* pLastCairoFontOptions = pInstance->GetLastSeenCairoFontOptions();
const cairo_font_options_t* pCurrentCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
bool bFontSettingsChanged = true;
if (pLastCairoFontOptions && pCurrentCairoFontOptions)
bFontSettingsChanged = !cairo_font_options_equal(pLastCairoFontOptions, pCurrentCairoFontOptions);
else if (!pLastCairoFontOptions && !pCurrentCairoFontOptions)
bFontSettingsChanged = false;
if (bFontSettingsChanged)
{
pInstance->ResetLastSeenCairoFontOptions();
GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::FontChanged );
}
}
gboolean GtkSalFrame::signalWindowState( GtkWidget*, GdkEvent* pEvent, gpointer frame )
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
if( (pThis->m_nState & GDK_WINDOW_STATE_ICONIFIED) != (pEvent->window_state.new_window_state & GDK_WINDOW_STATE_ICONIFIED ) )
{
GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize );
pThis->TriggerPaintEvent();
}
if ((pEvent->window_state.new_window_state & GDK_WINDOW_STATE_MAXIMIZED) &&
!(pThis->m_nState & GDK_WINDOW_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;
#if OSL_DEBUG_LEVEL > 1
if( (pEvent->window_state.changed_mask & GDK_WINDOW_STATE_FULLSCREEN) )
{
fprintf( stderr, "window %p %s full screen state\n",
pThis,
(pEvent->window_state.new_window_state & GDK_WINDOW_STATE_FULLSCREEN) ? "enters" : "leaves");
}
#endif
return false;
}
gboolean GtkSalFrame::signalVisibility( GtkWidget*, GdkEventVisibility* pEvent, gpointer frame )
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
pThis->m_nVisibility = pEvent->state;
return true;
}
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;
}
}
class GtkDropTargetDropContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDropContext>
{
GdkDragContext *m_pContext;
guint m_nTime;
public:
GtkDropTargetDropContext(GdkDragContext *pContext, guint nTime)
: m_pContext(pContext)
, m_nTime(nTime)
{
}
// XDropTargetDropContext
virtual void SAL_CALL acceptDrop(sal_Int8 dragOperation) throw(std::exception) override
{
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;
gdk_drag_status(m_pContext, eAct, m_nTime);
}
virtual void SAL_CALL rejectDrop() throw(std::exception) override
{
gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
}
virtual void SAL_CALL dropComplete(sal_Bool bSuccess) throw(std::exception) override
{
gtk_drag_finish(m_pContext, bSuccess, false, m_nTime);
}
};
class GtkDnDTransferable : public GtkTransferable
{
GdkDragContext *m_pContext;
guint m_nTime;
GtkWidget *m_pWidget;
GtkSalFrame *m_pFrame;
GMainLoop *m_pLoop;
GtkSelectionData *m_pData;
public:
GtkDnDTransferable(GdkDragContext *pContext, guint nTime, GtkWidget *pWidget, GtkSalFrame *pFrame)
: m_pContext(pContext)
, m_nTime(nTime)
, m_pWidget(pWidget)
, m_pFrame(pFrame)
, m_pLoop(nullptr)
, m_pData(nullptr)
{
}
virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor)
throw(css::datatransfer::UnsupportedFlavorException,
css::io::IOException,
css::uno::RuntimeException, std::exception) override
{
css::datatransfer::DataFlavor aFlavor(rFlavor);
if (aFlavor.MimeType == "text/plain;charset=utf-16")
aFlavor.MimeType = "text/plain;charset=utf-8";
auto it = m_aMimeTypeToAtom.find(aFlavor.MimeType);
if (it == m_aMimeTypeToAtom.end())
return css::uno::Any();
/* 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_pFrame->SetFormatConversionRequest(this);
gtk_drag_get_data(m_pWidget, m_pContext, it->second, m_nTime);
if (g_main_loop_is_running(m_pLoop))
{
gdk_threads_leave();
g_main_loop_run(m_pLoop);
gdk_threads_enter();
}
g_main_loop_unref(m_pLoop);
m_pLoop = nullptr;
m_pFrame->SetFormatConversionRequest(nullptr);
}
css::uno::Any aRet;
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 = OUString(pText, rtl_str_getLength(pText), RTL_TEXTENCODING_UTF8);
g_free(pText);
aRet <<= aStr.replaceAll("\r\n", "\n");
}
else
{
gint length(0);
const guchar *rawdata = gtk_selection_data_get_data_with_length(m_pData,
&length);
css::uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
aRet <<= aSeq;
}
gtk_selection_data_free(m_pData);
return aRet;
}
virtual std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector() override
{
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());
}
void LoopEnd(GtkSelectionData *pData)
{
m_pData = pData;
g_main_loop_quit(m_pLoop);
}
};
// For LibreOffice internal D&D we provide the Transferable without Gtk
// intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
GtkDragSource* GtkDragSource::g_ActiveDragSource;
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;
css::datatransfer::dnd::DropTargetDropEvent aEvent;
aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(pThis->m_pDropTarget);
aEvent.Context = new GtkDropTargetDropContext(context, time);
aEvent.LocationX = x;
aEvent.LocationY = y;
aEvent.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
aEvent.SourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
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 (GtkDragSource::g_ActiveDragSource)
xTransferable = GtkDragSource::g_ActiveDragSource->GetTransferrable();
else
xTransferable = new GtkDnDTransferable(context, time, pWidget, pThis);
aEvent.Transferable = xTransferable;
pThis->m_pDropTarget->fire_drop(aEvent);
return true;
}
class GtkDropTargetDragContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDragContext>
{
GdkDragContext *m_pContext;
guint m_nTime;
public:
GtkDropTargetDragContext(GdkDragContext *pContext, guint nTime)
: m_pContext(pContext)
, m_nTime(nTime)
{
}
virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) throw(std::exception) override
{
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;
gdk_drag_status(m_pContext, eAct, m_nTime);
}
virtual void SAL_CALL rejectDrag() throw(std::exception) override
{
gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
}
};
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 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 (!pThis->m_pFormatConversionRequest)
return;
pThis->m_pFormatConversionRequest->LoopEnd(gtk_selection_data_copy(data));
}
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;
if (!pThis->m_bInDrag)
gtk_drag_highlight(pWidget);
css::datatransfer::dnd::DropTargetDragEnterEvent aEvent;
aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(pThis->m_pDropTarget);
GtkDropTargetDragContext* pContext = new GtkDropTargetDragContext(context, time);
//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
pContext->acceptDrag(GdkToVcl(gdk_drag_context_get_actions(context)));
aEvent.Context = pContext;
aEvent.LocationX = x;
aEvent.LocationY = y;
aEvent.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
aEvent.SourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
if (!pThis->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 (GtkDragSource::g_ActiveDragSource)
xTransferable = GtkDragSource::g_ActiveDragSource->GetTransferrable();
else
xTransferable = new GtkDnDTransferable(context, time, pWidget, pThis);
css::uno::Sequence<css::datatransfer::DataFlavor> aFormats = xTransferable->getTransferDataFlavors();
aEvent.SupportedDataFlavors = aFormats;
pThis->m_pDropTarget->fire_dragEnter(aEvent);
pThis->m_bInDrag = true;
}
else
{
pThis->m_pDropTarget->fire_dragOver(aEvent);
}
return true;
}
void GtkSalFrame::signalDragLeave(GtkWidget *pWidget, GdkDragContext * /*context*/, guint /*time*/, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
if (!pThis->m_pDropTarget)
return;
pThis->m_bInDrag = false;
gtk_drag_unhighlight(pWidget);
#if 0
css::datatransfer::dnd::DropTargetEvent aEvent;
aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(pThis->m_pDropTarget);
pThis->m_pDropTarget->fire_dragExit(aEvent);
#endif
}
void GtkSalFrame::signalDestroy( GtkWidget* pObj, gpointer frame )
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
if( pObj == pThis->m_pWindow )
{
pThis->m_pFixedContainer = nullptr;
pThis->m_pEventBox = nullptr;
pThis->m_pTopLevelGrid = nullptr;
pThis->m_pWindow = nullptr;
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 )
{
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 );
GetGenericData()->ErrorTrapPush();
gtk_im_context_set_client_window( m_pIMContext, widget_get_window(GTK_WIDGET(m_pFrame->m_pWindow)) );
gtk_im_context_focus_in( m_pIMContext );
GetGenericData()->ErrorTrapPop();
m_bFocused = true;
}
}
void GtkSalFrame::IMHandler::deleteIMContext()
{
if( m_pIMContext )
{
// first give IC a chance to deinitialize
GetGenericData()->ErrorTrapPush();
gtk_im_context_set_client_window( m_pIMContext, nullptr );
GetGenericData()->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;
GetGenericData()->ErrorTrapPush();
gtk_im_context_set_cursor_location( m_pIMContext, &aArea );
GetGenericData()->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 )
{
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[0];
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 )
{
GetGenericData()->ErrorTrapPush();
gtk_im_context_focus_in( m_pIMContext );
GetGenericData()->ErrorTrapPop();
if( m_aInputEvent.mpTextAttr )
{
sendEmptyCommit();
// begin preedit again
GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
}
}
else
{
GetGenericData()->ErrorTrapPush();
gtk_im_context_focus_out( m_pIMContext );
GetGenericData()->ErrorTrapPop();
// cancel an eventual event posted to begin preedit again
GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
}
}
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.push_back( PreviousKeyPress(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;
gboolean 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", "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 ) ) );
gboolean bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
g_object_unref( pRef );
if( aDel.isDeleted() )
return true;
m_bPreeditJustChanged = false;
std::list<PreviousKeyPress>::iterator iter = m_aPrevKeyPresses.begin();
std::list<PreviousKeyPress>::iterator iter_end = m_aPrevKeyPresses.end();
while (iter != iter_end)
{
// If we found a corresponding previous key press event, swallow the release
// and remove the earlier key press from our list
if (*iter == pEvent)
{
m_aPrevKeyPresses.erase(iter);
m_nPrevKeyPresses--;
return true;
}
++iter;
}
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 a 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, rKP.time, 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();
}
}
}
void GtkSalFrame::IMHandler::signalIMPreeditChanged( GtkIMContext*, gpointer im_handler )
{
GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
char* pText = nullptr;
PangoAttrList* pAttrs = nullptr;
gint nCursorPos = 0;
gtk_im_context_get_preedit_string( pThis->m_pIMContext,
&pText,
&pAttrs,
&nCursorPos );
if( pText && ! *pText ) // empty string
{
// change from nothing to nothing -> do not start preedit
// e.g. this will activate input into a calc cell without
// user input
if( pThis->m_aInputEvent.maText.getLength() == 0 )
{
g_free( pText );
pango_attr_list_unref( pAttrs );
return;
}
}
pThis->m_bPreeditJustChanged = true;
bool bEndPreedit = (!pText || !*pText) && pThis->m_aInputEvent.mpTextAttr != nullptr;
pThis->m_aInputEvent.maText = pText ? OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 ) : OUString();
pThis->m_aInputEvent.mnCursorPos = nCursorPos;
pThis->m_aInputEvent.mnCursorFlags = 0;
pThis->m_aInputFlags = std::vector<ExtTextInputAttr>( std::max( 1, (int)pThis->m_aInputEvent.maText.getLength() ), ExtTextInputAttr::NONE );
PangoAttrIterator *iter = pango_attr_list_get_iterator(pAttrs);
do
{
GSList *attr_list = nullptr;
GSList *tmp_list = nullptr;
gint start, end;
ExtTextInputAttr sal_attr = ExtTextInputAttr::NONE;
pango_attr_iterator_range (iter, &start, &end);
if (end == G_MAXINT)
end = pText ? strlen (pText) : 0;
if (end == start)
continue;
start = g_utf8_pointer_to_offset (pText, pText + start);
end = g_utf8_pointer_to_offset (pText, pText + end);
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;
pThis->m_aInputEvent.mnCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
break;
case PANGO_ATTR_UNDERLINE:
sal_attr |= ExtTextInputAttr::Underline;
break;
case PANGO_ATTR_STRIKETHROUGH:
sal_attr |= ExtTextInputAttr::RedText;
break;
default:
break;
}
pango_attribute_destroy (pango_attr);
tmp_list = tmp_list->next;
}
if (sal_attr == ExtTextInputAttr::NONE)
sal_attr |= ExtTextInputAttr::Underline;
g_slist_free (attr_list);
// Set the sal attributes on our text
for (int i = start; i < end; ++i)
{
SAL_WARN_IF(i >= static_cast<int>(pThis->m_aInputFlags.size()),
"vcl.gtk", "pango attrib out of range. Broken range: "
<< start << "," << end << " Legal range: 0,"
<< pThis->m_aInputFlags.size());
if (i >= static_cast<int>(pThis->m_aInputFlags.size()))
continue;
pThis->m_aInputFlags[i] |= sal_attr;
}
} while (pango_attr_iterator_next (iter));
pango_attr_iterator_destroy(iter);
pThis->m_aInputEvent.mpTextAttr = &pThis->m_aInputFlags[0];
g_free( pText );
pango_attr_list_unref( pAttrs );
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();
}
uno::Reference<accessibility::XAccessibleEditableText>
FindFocus(uno::Reference< accessibility::XAccessibleContext > const & xContext)
{
if (!xContext.is())
return uno::Reference< accessibility::XAccessibleEditableText >();
uno::Reference<accessibility::XAccessibleStateSet> xState = xContext->getAccessibleStateSet();
if (xState.is())
{
if (xState->contains(accessibility::AccessibleStateType::FOCUSED))
{
uno::Reference< accessibility::XAccessibleEditableText > xText =
uno::Reference<accessibility::XAccessibleEditableText>(xContext, uno::UNO_QUERY);
if (xText.is())
return xText;
}
if (xState->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS))
return uno::Reference< accessibility::XAccessibleEditableText >();
}
for (sal_Int32 i = 0; i < xContext->getAccessibleChildCount(); ++i)
{
uno::Reference< accessibility::XAccessible > xChild = xContext->getAccessibleChild(i);
if (!xChild.is())
continue;
uno::Reference< accessibility::XAccessibleContext > xChildContext = xChild->getAccessibleContext();
if (!xChildContext.is())
continue;
uno::Reference< accessibility::XAccessibleEditableText > xText = FindFocus(xChildContext);
if (xText.is())
return xText;
}
return uno::Reference< accessibility::XAccessibleEditableText >();
}
static uno::Reference<accessibility::XAccessibleEditableText> lcl_GetxText(vcl::Window *pFocusWin)
{
uno::Reference<accessibility::XAccessibleEditableText> xText;
try
{
uno::Reference< accessibility::XAccessible > xAccessible( pFocusWin->GetAccessible() );
if (xAccessible.is())
xText = FindFocus(xAccessible->getAccessibleContext());
}
catch(const uno::Exception& e)
{
SAL_WARN( "vcl.gtk", "Exception in getting input method surrounding text: " << e.Message);
}
return xText;
}
gboolean GtkSalFrame::IMHandler::signalIMRetrieveSurrounding( GtkIMContext* pContext, gpointer /*im_handler*/ )
{
vcl::Window *pFocusWin = Application::GetFocusWindow();
if (!pFocusWin)
return true;
uno::Reference<accessibility::XAccessibleEditableText> xText = lcl_GetxText(pFocusWin);
if (xText.is())
{
sal_Int32 nPosition = xText->getCaretPosition();
OUString sAllText = xText->getText();
OString sUTF = OUStringToOString(sAllText, RTL_TEXTENCODING_UTF8);
OUString sCursorText(sAllText.copy(0, nPosition));
gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
return true;
}
return false;
}
gboolean GtkSalFrame::IMHandler::signalIMDeleteSurrounding( GtkIMContext*, gint offset, gint nchars,
gpointer /*im_handler*/ )
{
vcl::Window *pFocusWin = Application::GetFocusWindow();
if (!pFocusWin)
return true;
uno::Reference<accessibility::XAccessibleEditableText> xText = lcl_GetxText(pFocusWin);
if (xText.is())
{
sal_Int32 nPosition = xText->getCaretPosition();
// #i111768# range checking
sal_Int32 nDeletePos = nPosition + offset;
sal_Int32 nDeleteEnd = nDeletePos + nchars;
if (nDeletePos < 0)
nDeletePos = 0;
if (nDeleteEnd < 0)
nDeleteEnd = 0;
if (nDeleteEnd > xText->getCharacterCount())
nDeleteEnd = xText->getCharacterCount();
xText->deleteText(nDeletePos, nDeleteEnd);
//tdf91641 adjust cursor if deleted chars shift it forward (normal case)
if (nDeletePos < nPosition)
{
if (nDeleteEnd <= nPosition)
nPosition = nPosition - (nDeleteEnd - nDeletePos);
else
nPosition = nDeletePos;
if (xText->getCharacterCount() >= nPosition)
xText->setCaretPosition( nPosition );
}
return true;
}
return false;
}
Size GtkSalDisplay::GetScreenSize( int nDisplayScreen )
{
Rectangle aRect = m_pSys->GetDisplayScreenPosSizePixel( nDisplayScreen );
return Size( aRect.GetWidth(), aRect.GetHeight() );
}
sal_uIntPtr GtkSalFrame::GetNativeWindowHandle(GtkWidget *pWidget)
{
(void) this; // Silence loplugin:staticmethods
GdkDisplay *pDisplay = getGdkDisplay();
GdkWindow *pWindow = gtk_widget_get_window(pWidget);
#if defined(GDK_WINDOWING_X11)
if (GDK_IS_X11_DISPLAY(pDisplay))
{
return GDK_WINDOW_XID(pWindow);
}
#endif
#if defined(GDK_WINDOWING_WAYLAND)
if (GDK_IS_WAYLAND_DISPLAY(pDisplay))
{
return reinterpret_cast<sal_uIntPtr>(gdk_wayland_window_get_wl_surface(pWindow));
}
#endif
return 0;
}
sal_uIntPtr GtkSalFrame::GetNativeWindowHandle()
{
return GetNativeWindowHandle(m_pWindow);
}
void GtkDragSource::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) throw(std::exception)
{
m_xListener = rListener;
m_xTrans = rTrans;
if (m_pFrame)
{
css::uno::Sequence<css::datatransfer::DataFlavor> aFormats = rTrans->getTransferDataFlavors();
std::vector<GtkTargetEntry> aGtkTargets(m_aConversionHelper.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;
}
// 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;
m_pFrame->startDrag(nDragButton, rEvent.DragOriginX, rEvent.DragOriginY,
VclToGdk(sourceActions), pTargetList);
gtk_target_list_unref(pTargetList);
for (auto &a : aGtkTargets)
g_free(a.target);
}
else
dragFailed();
}
void GtkSalFrame::startDrag(gint nButton, gint nDragOriginX, gint nDragOriginY,
GdkDragAction sourceActions, GtkTargetList* pTargetList)
{
SolarMutexGuard aGuard;
assert(m_pDragSource);
GdkEvent aFakeEvent;
memset(&aFakeEvent, 0, sizeof(GdkEvent));
aFakeEvent.type = GDK_BUTTON_PRESS;
aFakeEvent.button.window = widget_get_window(getMouseEventWidget());
aFakeEvent.button.time = GDK_CURRENT_TIME;
GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(getGdkDisplay());
aFakeEvent.button.device = gdk_device_manager_get_client_pointer(pDeviceManager);
#if GTK_CHECK_VERSION(3,10,0)
GdkDragContext *pContext = gtk_drag_begin_with_coordinates(getMouseEventWidget(),
pTargetList,
sourceActions,
nButton,
&aFakeEvent,
nDragOriginX,
nDragOriginY);
#else
GdkDragContext *pContext = gtk_drag_begin(getMouseEventWidget(),
pTargetList,
sourceActions,
nButton,
&aFakeEvent);
(void)nDragOriginX;
(void)nDragOriginY;
#endif
if (!pContext)
m_pDragSource->dragFailed();
}
void GtkDragSource::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);
}
}
gboolean GtkSalFrame::signalDragFailed(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkDragResult /*result*/, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
if (!pThis->m_pDragSource)
return false;
pThis->m_pDragSource->dragFailed();
return false;
}
void GtkDragSource::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);
}
}
void GtkSalFrame::signalDragDelete(GtkWidget* /*widget*/, GdkDragContext* /*context*/, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
if (!pThis->m_pDragSource)
return;
pThis->m_pDragSource->dragDelete();
}
void GtkDragSource::dragEnd(GdkDragContext* context)
{
if (m_xListener.is())
{
datatransfer::dnd::DragSourceDropEvent aEv;
aEv.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
aEv.DropSuccess = gdk_drag_drop_succeeded(context);
auto xListener = m_xListener;
m_xListener.clear();
xListener->dragDropEnd(aEv);
}
g_ActiveDragSource = nullptr;
}
void GtkSalFrame::signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
if (!pThis->m_pDragSource)
return;
pThis->m_pDragSource->dragEnd(context);
}
void GtkDragSource::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);
}
long GtkSalFrame::CallCallbackExc(SalEvent nEvent, const void* pEvent) const
{
long nRet = 0;
try
{
nRet = CallCallback(nEvent, pEvent);
}
catch (const css::uno::Exception&)
{
GtkData *pSalData = static_cast<GtkData*>(GetSalData());
pSalData->setException(::cppu::getCaughtException());
}
return nRet;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */