Quellcode-Bibliothek gtkinst.cxx
Sprache: 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/.
*/
#include <sal/config.h>
#include <deque>
#include <optional>
#include <stack>
#include <string.h>
#include <string_view>
#include <dndhelper.hxx>
#include <o3tl/test_info.hxx>
#include <osl/process.h>
#include <osl/file.hxx>
#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <unx/genprn.h>
#include <unx/salobj.h>
#include <unx/gtk/gtkgdi.hxx>
#include <unx/gtk/gtkframe.hxx>
#include <unx/gtk/gtkobject.hxx>
#include <unx/gtk/gtksalmenu.hxx>
#include <headless/svpvd.hxx>
#include <headless/svpbmp.hxx>
#include <utility>
#include <vcl/builder.hxx>
#include <vcl/inputtypes.hxx>
#include <vcl/specialchars.hxx>
#include <vcl/sysdata.hxx>
#include <vcl/transfer.hxx>
#include <vcl/toolkit/floatwin.hxx>
#include <unx/genpspgraphics.h>
#include <rtl/strbuf.hxx>
#include <sal/log.hxx>
#include <rtl/uri.hxx>
#include <basegfx/numeric/ftools.hxx>
#include <vcl/settings.hxx>
#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>
#if !GTK_CHECK_VERSION(4, 0, 0)
#include "a11y/atkwrapper.hxx"
#endif
#include <com/sun/star/accessibility/XAccessibleContext2.hpp>
#include <com/sun/star/awt/XVclWindowPeer.hpp>
#include <com/sun/star/datatransfer/XTransferable.hpp>
#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
#include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XSingleServiceFactory.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <comphelper/lok.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/propertyvalue.hxx>
#include <comphelper/sequence.hxx>
#include <comphelper/string.hxx>
#include <cppuhelper/compbase.hxx>
#include <cppuhelper/implbase.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <officecfg/Office/Common.hxx>
#include <rtl/bootstrap.hxx>
#include <o3tl/unreachable.hxx>
#include <o3tl/string_view.hxx>
#include <svl/zforlist.hxx>
#include <svl/zformat.hxx>
#include <tools/helpers.hxx>
#include <tools/fract.hxx>
#include <tools/stream.hxx>
#include <unotools/resmgr.hxx>
#include <unotools/tempfile.hxx>
#include <unx/gstsink.hxx>
#include <vcl/ImageTree.hxx>
#include <vcl/abstdlg.hxx>
#include <vcl/event.hxx>
#include <vcl/i18nhelp.hxx>
#include <vcl/quickselectionengine.hxx>
#include <vcl/mnemonic.hxx>
#include <vcl/filter/PngImageWriter.hxx>
#include <vcl/stdtext.hxx>
#include <vcl/syswin.hxx>
#include <vcl/virdev.hxx>
#include <vcl/weld.hxx>
#include <vcl/wrkwin.hxx>
#include "customcellrenderer.hxx"
#include <strings.hrc>
#include <window.h>
#include <numeric>
#include <boost/property_tree/ptree.hpp>
#include <opengl/zone.hxx>
using namespace com::sun::star;
using namespace com::sun::star::uno;
using namespace com::sun::star::lang;
extern "C"
{
#define GET_YIELD_MUTEX() static_cast<GtkYieldMutex*>(GetSalInstance()->GetYieldMutex ())
#if !GTK_CHECK_VERSION(4, 0, 0)
static void GdkThreadsEnter()
{
GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
pYieldMutex->ThreadsEnter();
}
static void GdkThreadsLeave()
{
GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
pYieldMutex->ThreadsLeave();
}
#endif
VCLPLUG_GTK_PUBLIC SalInstance* create_SalInstance()
{
SAL_INFO(
"vcl.gtk",
"create vcl plugin instance with gtk version " << gtk_get_major_version()
<< " " << gtk_get_minor_version() << " " << gtk_get_micro_version());
if (gtk_get_major_version() == 3 && gtk_get_minor_version() < 18)
{
g_warning("require gtk >= 3.18 for theme expectations");
return nullptr;
}
// for gtk2 it is always built with X support, so this is always called
// for gtk3 it is normally built with X and Wayland support, if
// X is supported GDK_WINDOWING_X11 is defined and this is always
// called, regardless of if we're running under X or Wayland.
// We can't use (DLSYM_GDK_IS_X11_DISPLAY(pDisplay)) to only do it under
// X, because we need to do it earlier than we have a display
#if defined(GDK_WINDOWING_X11)
/* #i92121# workaround deadlocks in the X11 implementation
*/
static const char* pNoXInitThreads = getenv( "SAL_NO_XINITTHREADS" );
/* #i90094#
from now on we know that an X connection will be
established, so protect X against itself
*/
if( ! ( pNoXInitThreads && *pNoXInitThreads ) )
XInitThreads();
#endif
#if !GTK_CHECK_VERSION(4, 0, 0)
gdk_threads_set_lock_functions (GdkThreadsEnter, GdkThreadsLeave);
SAL_INFO("vcl.gtk", "Hooked gdk threads locks");
#endif
auto pYieldMutex = std::make_unique<GtkYieldMutex>();
#if !GTK_CHECK_VERSION(4, 0, 0)
gdk_threads_init();
#endif
GtkInstance* pInstance = new GtkInstance( std::move(pYieldMutex) );
SAL_INFO("vcl.gtk", "creating GtkInstance " << pInstance);
// Create SalData, this does not leak
new GtkSalData();
return pInstance;
}
}
#if !GTK_CHECK_VERSION(4, 0, 0)
static VclInputFlags categorizeEvent(const GdkEvent *pEvent)
{
VclInputFlags nType = VclInputFlags::NONE;
switch (gdk_event_get_event_type(pEvent))
{
case GDK_MOTION_NOTIFY:
case GDK_BUTTON_PRESS:
#if !GTK_CHECK_VERSION(4, 0, 0)
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
#endif
case GDK_BUTTON_RELEASE:
case GDK_ENTER_NOTIFY:
case GDK_LEAVE_NOTIFY:
case GDK_SCROLL:
nType = VclInputFlags::MOUSE;
break;
case GDK_KEY_PRESS:
// case GDK_KEY_RELEASE: //similar to the X11SalInstance one
nType = VclInputFlags::KEYBOARD;
break;
#if !GTK_CHECK_VERSION(4, 0, 0)
case GDK_EXPOSE:
nType = VclInputFlags::PAINT;
break;
#endif
default:
nType = VclInputFlags::OTHER;
break;
}
return nType;
}
#endif
GtkInstance::GtkInstance( std::unique_ptr<SalYieldMutex> pMutex )
: SvpSalInstance( std::move(pMutex) )
, m_pTimer(nullptr)
, bNeedsInit(true)
, m_pLastCairoFontOptions(nullptr)
{
m_bSupportsOpenGL = true;
}
//We want to defer initializing gtk until we are after uno has been
//bootstrapped so we can ask the config what the UI language is so that we can
//force that in as $LANGUAGE to get gtk to render widgets RTL if we have a RTL
//UI in a LTR locale
void GtkInstance::AfterAppInit()
{
EnsureInit();
}
void GtkInstance::EnsureInit()
{
if (!bNeedsInit)
return;
// initialize SalData
GtkSalData *pSalData = GetGtkSalData();
pSalData->Init();
GtkSalData::initNWF();
ImplSVData* pSVData = ImplGetSVData();
#ifdef GTK_TOOLKIT_NAME
// [-loplugin:ostr] if we use a literal here, we get use-after-free on shutdown
pSVData->maAppData.mxToolkitName = OUString(GTK_TOOLKIT_NAME);
#else
// [-loplugin:ostr] if we use a literal here, we get use-after-free on shutdown
pSVData->maAppData.mxToolkitName = OUString("gtk3");
#endif
bNeedsInit = false;
}
GtkInstance::~GtkInstance()
{
assert( nullptr == m_pTimer );
ResetLastSeenCairoFontOptions(nullptr);
}
SalFrame* GtkInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
{
EnsureInit();
return new GtkSalFrame( pParent, nStyle );
}
SalFrame* GtkInstance::CreateChildFrame( SystemParentData* pParentData, SalFrameStyleFlags )
{
EnsureInit();
return new GtkSalFrame( pParentData );
}
SalObject* GtkInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow )
{
EnsureInit();
//FIXME: Missing CreateObject functionality ...
if (pWindowData && pWindowData->bClipUsingNativeWidget)
return new GtkSalObjectWidgetClip(static_cast<GtkSalFrame*>(pParent), bShow);
return new GtkSalObject(static_cast<GtkSalFrame*>(pParent), bShow);
}
extern "C"
{
typedef void*(* getDefaultFnc)();
typedef void(* addItemFnc)(void *, const char *);
}
void GtkInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString&, const OUString&)
{
EnsureInit();
OString sGtkURL;
rtl_TextEncoding aSystemEnc = osl_getThreadTextEncoding();
if ((aSystemEnc == RTL_TEXTENCODING_UTF8) || !rFileUrl.startsWith( "file://" ))
sGtkURL = OUStringToOString(rFileUrl, RTL_TEXTENCODING_UTF8);
else
{
//Non-utf8 locales are a bad idea if trying to work with non-ascii filenames
//Decode %XX components
OUString sDecodedUri = rtl::Uri::decode(rFileUrl.copy(7), rtl_UriDecodeToIuri, RTL_TEXTENCODING_UTF8);
//Convert back to system locale encoding
OString sSystemUrl = OUStringToOString(sDecodedUri, aSystemEnc);
//Encode to an escaped ASCII-encoded URI
gchar *g_uri = g_filename_to_uri(sSystemUrl.getStr(), nullptr, nullptr);
sGtkURL = OString(g_uri);
g_free(g_uri);
}
GtkRecentManager *manager = gtk_recent_manager_get_default ();
gtk_recent_manager_add_item (manager, sGtkURL.getStr());
}
SalInfoPrinter* GtkInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
ImplJobSetup* pSetupData )
{
EnsureInit();
mbPrinterInit = true;
// create and initialize SalInfoPrinter
PspSalInfoPrinter* pPrinter = new PspSalInfoPrinter;
configurePspInfoPrinter(pPrinter, pQueueInfo, pSetupData);
return pPrinter;
}
std::unique_ptr<SalPrinter> GtkInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
{
EnsureInit();
mbPrinterInit = true;
return std::unique_ptr<SalPrinter>(new PspSalPrinter(pInfoPrinter));
}
/*
* These methods always occur in pairs
* A ThreadsEnter is followed by a ThreadsLeave
* We need to queue up the recursive lock count
* for each pair, so we can accurately restore
* it later.
*/
thread_local std::stack<sal_uInt32> GtkYieldMutex::yieldCounts;
void GtkYieldMutex::ThreadsEnter()
{
acquire();
if (yieldCounts.empty())
return;
auto n = yieldCounts.top();
yieldCounts.pop();
const bool bUndoingLeaveWithoutEnter = n == 0;
// if the ThreadsLeave bLeaveWithoutEnter of true condition occurred to
// create this entry then return early undoing the initial acquire of the
// function
if G_UNLIKELY(bUndoingLeaveWithoutEnter)
{
release();
return;
}
assert(n > 0);
n--;
if (n > 0)
acquire(n);
}
void GtkYieldMutex::ThreadsLeave()
{
const bool bLeaveWithoutEnter = m_nCount == 0;
SAL_WARN_IF(bLeaveWithoutEnter, "vcl.gtk", "gdk_threads_leave without matching gdk_threads_enter");
yieldCounts.push(m_nCount);
if G_UNLIKELY(bLeaveWithoutEnter) // this ideally shouldn't happen, but can due to the gtk3 file dialog
return;
release(true);
}
std::unique_ptr<SalVirtualDevice> GtkInstance::CreateVirtualDevice( SalGraphics &rG,
tools::Long nDX, tools::Long nDY,
DeviceFormat /*eFormat*/ )
{
EnsureInit();
SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(&rG);
assert(pSvpSalGraphics);
// tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget
std::unique_ptr<SalVirtualDevice> xNew(new SvpSalVirtualDevice(pSvpSalGraphics->getSurface(), /*pPreExistingTarget*/nullptr));
if (!xNew->SetSize(nDX, nDY))
xNew.reset();
return xNew;
}
std::unique_ptr<SalVirtualDevice> GtkInstance::CreateVirtualDevice( SalGraphics &rG,
tools::Long &nDX, tools::Long &nDY,
DeviceFormat /*eFormat*/,
const SystemGraphicsData& rGd )
{
EnsureInit();
SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(&rG);
assert(pSvpSalGraphics);
// tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget
cairo_surface_t* pPreExistingTarget = static_cast<cairo_surface_t*>(rGd.pSurface);
std::unique_ptr<SalVirtualDevice> xNew(new SvpSalVirtualDevice(pSvpSalGraphics->getSurface(), pPreExistingTarget));
if (!xNew->SetSize(nDX, nDY))
xNew.reset();
return xNew;
}
std::shared_ptr<SalBitmap> GtkInstance::CreateSalBitmap()
{
EnsureInit();
return SvpSalInstance::CreateSalBitmap();
}
std::unique_ptr<SalMenu> GtkInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
{
EnsureInit();
GtkSalMenu* pSalMenu = new GtkSalMenu( bMenuBar );
pSalMenu->SetMenu( pVCLMenu );
return std::unique_ptr<SalMenu>(pSalMenu);
}
std::unique_ptr<SalMenuItem> GtkInstance::CreateMenuItem( const SalItemParams & rItemData )
{
EnsureInit();
return std::unique_ptr<SalMenuItem>(new GtkSalMenuItem( &rItemData ));
}
SalTimer* GtkInstance::CreateSalTimer()
{
EnsureInit();
assert( nullptr == m_pTimer );
if ( nullptr == m_pTimer )
m_pTimer = new GtkSalTimer();
return m_pTimer;
}
void GtkInstance::RemoveTimer ()
{
EnsureInit();
m_pTimer = nullptr;
}
bool GtkInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
{
EnsureInit();
return GetGtkSalData()->Yield( bWait, bHandleAllCurrentEvents );
}
bool GtkInstance::IsTimerExpired()
{
EnsureInit();
return (m_pTimer && m_pTimer->Expired());
}
namespace
{
bool DisplayHasAnyInput()
{
GdkDisplay* pDisplay = gdk_display_get_default();
#if defined(GDK_WINDOWING_WAYLAND)
if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
{
bool bRet = false;
wl_display* pWLDisplay = gdk_wayland_display_get_wl_display(pDisplay);
static auto wayland_display_get_fd = reinterpret_cast<int (*) (wl_display*)>(dlsym(nullptr, "wl_display_get_fd"));
if (wayland_display_get_fd)
{
GPollFD aPollFD;
aPollFD.fd = wayland_display_get_fd(pWLDisplay);
aPollFD.events = G_IO_IN | G_IO_ERR | G_IO_HUP;
bRet = g_poll(&aPollFD, 1, 0) > 0;
}
return bRet;
}
#endif
#if defined(GDK_WINDOWING_X11)
if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
{
GPollFD aPollFD;
aPollFD.fd = ConnectionNumber(gdk_x11_display_get_xdisplay(pDisplay));
aPollFD.events = G_IO_IN;
return g_poll(&aPollFD, 1, 0) > 0;
}
#endif
return false;
}
}
bool GtkInstance::AnyInput( VclInputFlags nType )
{
EnsureInit();
if( (nType & VclInputFlags::TIMER) && IsTimerExpired() )
return true;
// strip timer bits now
nType = nType & ~VclInputFlags::TIMER;
static constexpr VclInputFlags ANY_INPUT_EXCLUDING_TIMER = VCL_INPUT_ANY & ~VclInputFlags::TIMER;
const bool bCheckForAnyInput = nType == ANY_INPUT_EXCLUDING_TIMER;
bool bRet = false;
if (bCheckForAnyInput)
bRet = DisplayHasAnyInput();
#if !GTK_CHECK_VERSION(4, 0, 0)
GdkDisplay* pDisplay = gdk_display_get_default();
if (!gdk_display_has_pending(pDisplay))
return bRet;
if (bCheckForAnyInput)
return true;
std::deque<GdkEvent*> aEvents;
GdkEvent *pEvent = nullptr;
while ((pEvent = gdk_display_get_event(pDisplay)))
{
aEvents.push_back(pEvent);
VclInputFlags nEventType = categorizeEvent(pEvent);
if ( (nEventType & nType) || ( nEventType == VclInputFlags::NONE && (nType & VclInputFlags::OTHER) ) )
{
bRet = true;
}
}
while (!aEvents.empty())
{
pEvent = aEvents.front();
gdk_display_put_event(pDisplay, pEvent);
gdk_event_free(pEvent);
aEvents.pop_front();
}
#endif
return bRet;
}
std::unique_ptr<GenPspGraphics> GtkInstance::CreatePrintGraphics()
{
EnsureInit();
return std::make_unique<GenPspGraphics>();
}
const cairo_font_options_t* GtkInstance::GetCairoFontOptions()
{
#if !GTK_CHECK_VERSION(4, 0, 0)
const cairo_font_options_t* pCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
#else
auto pDefaultWin = ImplGetDefaultWindow();
assert(pDefaultWin);
SalFrame* pDefaultFrame = pDefaultWin->ImplGetFrame();
GtkSalFrame* pGtkFrame = dynamic_cast<GtkSalFrame*>(pDefaultFrame);
assert(pGtkFrame);
const cairo_font_options_t* pCairoFontOptions = pGtkFrame->get_font_options();
#endif
if (!m_pLastCairoFontOptions && pCairoFontOptions)
m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
return pCairoFontOptions;
}
const cairo_font_options_t* GtkInstance::GetLastSeenCairoFontOptions() const
{
return m_pLastCairoFontOptions;
}
void GtkInstance::ResetLastSeenCairoFontOptions(const cairo_font_options_t* pCairoFontOptions)
{
if (m_pLastCairoFontOptions)
cairo_font_options_destroy(m_pLastCairoFontOptions);
if (pCairoFontOptions)
m_pLastCairoFontOptions = cairo_font_options_copy(pCairoFontOptions);
else
m_pLastCairoFontOptions = nullptr;
}
namespace
{
struct TypeEntry
{
const char* pNativeType; // string corresponding to nAtom for the case of nAtom being uninitialized
const char* pType; // Mime encoding on our side
};
const TypeEntry aConversionTab[] =
{
{ "ISO10646-1", "text/plain;charset=utf-16" },
{ "UTF8_STRING", "text/plain;charset=utf-8" },
{ "UTF-8", "text/plain;charset=utf-8" },
{ "text/plain;charset=UTF-8", "text/plain;charset=utf-8" },
// ISO encodings
{ "ISO8859-2", "text/plain;charset=iso8859-2" },
{ "ISO8859-3", "text/plain;charset=iso8859-3" },
{ "ISO8859-4", "text/plain;charset=iso8859-4" },
{ "ISO8859-5", "text/plain;charset=iso8859-5" },
{ "ISO8859-6", "text/plain;charset=iso8859-6" },
{ "ISO8859-7", "text/plain;charset=iso8859-7" },
{ "ISO8859-8", "text/plain;charset=iso8859-8" },
{ "ISO8859-9", "text/plain;charset=iso8859-9" },
{ "ISO8859-10", "text/plain;charset=iso8859-10" },
{ "ISO8859-13", "text/plain;charset=iso8859-13" },
{ "ISO8859-14", "text/plain;charset=iso8859-14" },
{ "ISO8859-15", "text/plain;charset=iso8859-15" },
// asian encodings
{ "JISX0201.1976-0", "text/plain;charset=jisx0201.1976-0" },
{ "JISX0208.1983-0", "text/plain;charset=jisx0208.1983-0" },
{ "JISX0208.1990-0", "text/plain;charset=jisx0208.1990-0" },
{ "JISX0212.1990-0", "text/plain;charset=jisx0212.1990-0" },
{ "GB2312.1980-0", "text/plain;charset=gb2312.1980-0" },
{ "KSC5601.1992-0", "text/plain;charset=ksc5601.1992-0" },
// eastern european encodings
{ "KOI8-R", "text/plain;charset=koi8-r" },
{ "KOI8-U", "text/plain;charset=koi8-u" },
// String (== iso8859-1)
{ "STRING", "text/plain;charset=iso8859-1" },
// special for compound text
{ "COMPOUND_TEXT", "text/plain;charset=compound_text" },
// PIXMAP
{ "PIXMAP", "image/bmp" }
};
class DataFlavorEq
{
private:
const css::datatransfer::DataFlavor& m_rData;
public:
explicit DataFlavorEq(const css::datatransfer::DataFlavor& rData) : m_rData(rData) {}
bool operator() (const css::datatransfer::DataFlavor& rData) const
{
return rData.MimeType == m_rData.MimeType &&
rData.DataType == m_rData.DataType;
}
};
}
#if GTK_CHECK_VERSION(4, 0, 0)
std::vector<css::datatransfer::DataFlavor> GtkTransferable::getTransferDataFlavorsAsVector(const char * const *targets, gint n_targets)
#else
std::vector<css::datatransfer::DataFlavor> GtkTransferable::getTransferDataFlavorsAsVector(GdkAtom *targets, gint n_targets)
#endif
{
std::vector<css::datatransfer::DataFlavor> aVector;
bool bHaveText = false, bHaveUTF16 = false;
for (gint i = 0; i < n_targets; ++i)
{
#if GTK_CHECK_VERSION(4, 0, 0)
const gchar* pName = targets[i];
#else
gchar* pName = gdk_atom_name(targets[i]);
#endif
const char* pFinalName = pName;
css::datatransfer::DataFlavor aFlavor;
// omit text/plain;charset=unicode since it is not well defined
if (rtl_str_compare(pName, "text/plain;charset=unicode") == 0)
{
#if !GTK_CHECK_VERSION(4, 0, 0)
g_free(pName);
#endif
continue;
}
for (size_t j = 0; j < SAL_N_ELEMENTS(aConversionTab); ++j)
{
if (rtl_str_compare(pName, aConversionTab[j].pNativeType) == 0)
{
pFinalName = aConversionTab[j].pType;
break;
}
}
// There are more non-MIME-types reported that are not translated by
// aConversionTab, like "SAVE_TARGETS", "INTEGER", "ATOM"; just filter
// them out for now before they confuse this code's clients:
if (rtl_str_indexOfChar(pFinalName, '/') == -1)
{
#if !GTK_CHECK_VERSION(4, 0, 0)
g_free(pName);
#endif
continue;
}
aFlavor.MimeType = OUString(pFinalName,
strlen(pFinalName),
RTL_TEXTENCODING_UTF8);
m_aMimeTypeToGtkType[aFlavor.MimeType] = targets[i];
aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
sal_Int32 nIndex(0);
if (o3tl::getToken(aFlavor.MimeType, 0, ';', nIndex) == u"text/plain")
{
bHaveText = true;
std::u16string_view aToken(o3tl::getToken(aFlavor.MimeType, 0, ';', nIndex));
if (aToken == u"charset=utf-16")
{
bHaveUTF16 = true;
aFlavor.DataType = cppu::UnoType<OUString>::get();
}
}
aVector.push_back(aFlavor);
#if !GTK_CHECK_VERSION(4, 0, 0)
g_free(pName);
#endif
}
//If we have text, but no UTF-16 format which is basically the only
//text-format LibreOffice supports for cnp then claim we do and we
//will convert on demand
if (bHaveText && !bHaveUTF16)
{
css::datatransfer::DataFlavor aFlavor;
aFlavor.MimeType = "text/plain;charset=utf-16";
aFlavor.DataType = cppu::UnoType<OUString>::get();
aVector.push_back(aFlavor);
}
return aVector;
}
css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL GtkTransferable::getTransferDataFlavors()
{
return comphelper::containerToSequence(getTransferDataFlavorsAsVector());
}
sal_Bool SAL_CALL GtkTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
{
const std::vector<css::datatransfer::DataFlavor> aAll =
getTransferDataFlavorsAsVector();
return std::any_of(aAll.begin(), aAll.end(), DataFlavorEq(rFlavor));
}
#if GTK_CHECK_VERSION(4, 0, 0)
void read_transfer_result::read_block_async_completed(GObject* source, GAsyncResult* res, gpointer user_data)
{
GInputStream* stream = G_INPUT_STREAM(source);
read_transfer_result* pRes = static_cast<read_transfer_result*>(user_data);
gsize bytes_read = g_input_stream_read_finish(stream, res, nullptr);
bool bFinished = bytes_read == 0;
if (bFinished)
{
g_object_unref(stream);
pRes->aVector.resize(pRes->nRead);
pRes->bDone = true;
g_main_context_wakeup(nullptr);
return;
}
pRes->nRead += bytes_read;
pRes->aVector.resize(pRes->nRead + read_transfer_result::BlockSize);
g_input_stream_read_async(stream,
pRes->aVector.data() + pRes->nRead,
read_transfer_result::BlockSize,
G_PRIORITY_DEFAULT,
nullptr,
read_block_async_completed,
user_data);
}
OUString read_transfer_result::get_as_string() const
{
const char* pStr = reinterpret_cast<const char*>(aVector.data());
return OUString(pStr, aVector.size(), RTL_TEXTENCODING_UTF8).replaceAll("\r\n", "\n");
}
css::uno::Sequence<sal_Int8> read_transfer_result::get_as_sequence() const
{
return css::uno::Sequence<sal_Int8>(aVector.data(), aVector.size());
}
#endif
namespace {
GdkClipboard* clipboard_get(SelectionType eSelection)
{
#if GTK_CHECK_VERSION(4, 0, 0)
if (eSelection == SELECTION_CLIPBOARD)
return gdk_display_get_clipboard(gdk_display_get_default());
return gdk_display_get_primary_clipboard(gdk_display_get_default());
#else
return gtk_clipboard_get(eSelection == SELECTION_CLIPBOARD ? GDK_SELECTION_CLIPBOARD : GDK_SELECTION_PRIMARY);
#endif
}
#if GTK_CHECK_VERSION(4, 0, 0)
void read_clipboard_async_completed(GObject* source, GAsyncResult* res, gpointer user_data)
{
GdkClipboard* clipboard = GDK_CLIPBOARD(source);
read_transfer_result* pRes = static_cast<read_transfer_result*>(user_data);
GInputStream* pResult = gdk_clipboard_read_finish(clipboard, res, nullptr, nullptr);
if (!pResult)
{
pRes->bDone = true;
g_main_context_wakeup(nullptr);
return;
}
pRes->aVector.resize(read_transfer_result::BlockSize);
g_input_stream_read_async(pResult,
pRes->aVector.data(),
pRes->aVector.size(),
G_PRIORITY_DEFAULT,
nullptr,
read_transfer_result::read_block_async_completed,
user_data);
}
#endif
class GtkClipboardTransferable : public GtkTransferable
{
private:
SelectionType m_eSelection;
public:
explicit GtkClipboardTransferable(SelectionType eSelection)
: m_eSelection(eSelection)
{
}
/*
* XTransferable
*/
virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override
{
css::uno::Any aRet;
css::datatransfer::DataFlavor aFlavor(rFlavor);
if (aFlavor.MimeType == "text/plain;charset=utf-16")
aFlavor.MimeType = "text/plain;charset=utf-8";
GdkClipboard* clipboard = clipboard_get(m_eSelection);
#if !GTK_CHECK_VERSION(4, 0, 0)
if (aFlavor.MimeType == "text/plain;charset=utf-8")
{
gchar *pText = gtk_clipboard_wait_for_text(clipboard);
OUString aStr(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
g_free(pText);
aRet <<= aStr.replaceAll("\r\n", "\n");
return aRet;
}
#endif
auto it = m_aMimeTypeToGtkType.find(aFlavor.MimeType);
if (it == m_aMimeTypeToGtkType.end())
return css::uno::Any();
#if !GTK_CHECK_VERSION(4, 0, 0)
GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard,
it->second);
if (!data)
{
return css::uno::Any();
}
gint length;
const guchar *rawdata = gtk_selection_data_get_data_with_length(data,
&length);
Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
gtk_selection_data_free(data);
aRet <<= aSeq;
#else
SalInstance* pInstance = GetSalInstance();
read_transfer_result aRes;
const char *mime_types[] = { it->second.getStr(), nullptr };
gdk_clipboard_read_async(clipboard,
mime_types,
G_PRIORITY_DEFAULT,
nullptr,
read_clipboard_async_completed,
&aRes);
while (!aRes.bDone)
pInstance->DoYield(true, false);
if (aFlavor.MimeType == "text/plain;charset=utf-8")
aRet <<= aRes.get_as_string();
else
aRet <<= aRes.get_as_sequence();
#endif
return aRet;
}
std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector()
override
{
std::vector<css::datatransfer::DataFlavor> aVector;
GdkClipboard* clipboard = clipboard_get(m_eSelection);
#if GTK_CHECK_VERSION(4, 0, 0)
GdkContentFormats* pFormats = gdk_clipboard_get_formats(clipboard);
gsize n_targets;
const char * const *targets = gdk_content_formats_get_mime_types(pFormats, &n_targets);
aVector = GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
#else
GdkAtom *targets;
gint n_targets;
if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
{
aVector = GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
g_free(targets);
}
#endif
return aVector;
}
};
class VclGtkClipboard :
public cppu::WeakComponentImplHelper<
datatransfer::clipboard::XSystemClipboard,
datatransfer::clipboard::XFlushableClipboard,
XServiceInfo>
{
SelectionType m_eSelection;
osl::Mutex m_aMutex;
gulong m_nOwnerChangedSignalId;
ImplSVEvent* m_pSetClipboardEvent;
Reference<css::datatransfer::XTransferable> m_aContents;
Reference<css::datatransfer::clipboard::XClipboardOwner> m_aOwner;
std::vector< Reference<css::datatransfer::clipboard::XClipboardListener> > m_aListeners;
#if GTK_CHECK_VERSION(4, 0, 0)
std::vector<OString> m_aGtkTargets;
TransferableContent* m_pClipboardContent;
#else
std::vector<GtkTargetEntry> m_aGtkTargets;
#endif
VclToGtkHelper m_aConversionHelper;
DECL_LINK(AsyncSetGtkClipboard, void*, void);
#if GTK_CHECK_VERSION(4, 0, 0)
DECL_LINK(DetachClipboard, void*, void);
#endif
public:
explicit VclGtkClipboard(SelectionType eSelection);
virtual ~VclGtkClipboard() override;
/*
* XServiceInfo
*/
virtual OUString SAL_CALL getImplementationName() override;
virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
/*
* XClipboard
*/
virtual Reference< css::datatransfer::XTransferable > SAL_CALL getContents() override;
virtual void SAL_CALL setContents(
const Reference< css::datatransfer::XTransferable >& xTrans,
const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner ) override;
virtual OUString SAL_CALL getName() override;
/*
* XClipboardEx
*/
virtual sal_Int8 SAL_CALL getRenderingCapabilities() override;
/*
* XFlushableClipboard
*/
virtual void SAL_CALL flushClipboard() override;
/*
* XClipboardNotifier
*/
virtual void SAL_CALL addClipboardListener(
const Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
virtual void SAL_CALL removeClipboardListener(
const Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
#if !GTK_CHECK_VERSION(4, 0, 0)
void ClipboardGet(GtkSelectionData *selection_data, guint info);
#endif
void OwnerPossiblyChanged(GdkClipboard *clipboard);
void ClipboardClear();
void SetGtkClipboard();
void SyncGtkClipboard();
};
}
OUString VclGtkClipboard::getImplementationName()
{
return u"com.sun.star.datatransfer.VclGtkClipboard"_ustr;
}
Sequence< OUString > VclGtkClipboard::getSupportedServiceNames()
{
Sequence<OUString> aRet { u"com.sun.star.datatransfer.clipboard.SystemClipboard"_ustr };
return aRet;
}
sal_Bool VclGtkClipboard::supportsService( const OUString& ServiceName )
{
return cppu::supportsService(this, ServiceName);
}
Reference< css::datatransfer::XTransferable > VclGtkClipboard::getContents()
{
if (!m_aContents.is())
{
//tdf#93887 This is the system clipboard/selection. We fetch it when we are not
//the owner of the clipboard and have not already fetched it.
m_aContents = new GtkClipboardTransferable(m_eSelection);
#if GTK_CHECK_VERSION(4, 0, 0)
if (m_pClipboardContent)
transerable_content_set_transferable(m_pClipboardContent, m_aContents.get());
#endif
}
return m_aContents;
}
#if !GTK_CHECK_VERSION(4, 0, 0)
void VclGtkClipboard::ClipboardGet(GtkSelectionData *selection_data, guint info)
{
if (!m_aContents.is())
return;
// tdf#129809 take a reference in case m_aContents is replaced during this
// call
Reference<datatransfer::XTransferable> xCurrentContents(m_aContents);
m_aConversionHelper.setSelectionData(xCurrentContents, selection_data, info);
}
namespace
{
const OString& getPID()
{
static OString sPID;
if (!sPID.getLength())
{
oslProcessIdentifier aProcessId = 0;
oslProcessInfo info;
info.Size = sizeof (oslProcessInfo);
if (osl_getProcessInfo(nullptr, osl_Process_IDENTIFIER, &info) == osl_Process_E_None)
aProcessId = info.Ident;
sPID = OString::number(aProcessId);
}
return sPID;
}
void ClipboardGetFunc(GdkClipboard* /*clipboard*/, GtkSelectionData *selection_data,
guint info,
gpointer user_data_or_owner)
{
VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
pThis->ClipboardGet(selection_data, info);
}
void ClipboardClearFunc(GdkClipboard* /*clipboard*/, gpointer user_data_or_owner)
{
VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
pThis->ClipboardClear();
}
}
#endif
namespace
{
#if GTK_CHECK_VERSION(4, 0, 0)
void handle_owner_change(GdkClipboard *clipboard, gpointer user_data)
{
VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data);
pThis->OwnerPossiblyChanged(clipboard);
}
#else
void handle_owner_change(GdkClipboard *clipboard, GdkEvent* /*event*/, gpointer user_data)
{
VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data);
pThis->OwnerPossiblyChanged(clipboard);
}
#endif
}
void VclGtkClipboard::OwnerPossiblyChanged(GdkClipboard* clipboard)
{
SyncGtkClipboard(); // tdf#138183 do any pending SetGtkClipboard calls
if (!m_aContents.is())
return;
#if GTK_CHECK_VERSION(4, 0, 0)
bool bSelf = gdk_clipboard_is_local(clipboard);
#else
//if gdk_display_supports_selection_notification is not supported, e.g. like
//right now under wayland, then you only get owner-changed notifications at
//opportune times when the selection might have changed. So here
//we see if the selection supports a dummy selection type identifying
//our pid, in which case it's us.
bool bSelf = false;
//disconnect and reconnect after gtk_clipboard_wait_for_targets to
//avoid possible recursion
g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
OString sTunnel = "application/x-libreoffice-internal-id-" + getPID();
GdkAtom *targets;
gint n_targets;
if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
{
for (gint i = 0; i < n_targets && !bSelf; ++i)
{
gchar* pName = gdk_atom_name(targets[i]);
if (strcmp(pName, sTunnel.getStr()) == 0)
{
bSelf = true;
}
g_free(pName);
}
g_free(targets);
}
m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
G_CALLBACK(handle_owner_change), this);
#endif
if (!bSelf)
{
//null out m_aContents to return control to the system-one which
//will be retrieved if getContents is called again
setContents(Reference<css::datatransfer::XTransferable>(),
Reference<css::datatransfer::clipboard::XClipboardOwner>());
}
}
void VclGtkClipboard::ClipboardClear()
{
if (m_pSetClipboardEvent)
{
Application::RemoveUserEvent(m_pSetClipboardEvent);
m_pSetClipboardEvent = nullptr;
}
#if !GTK_CHECK_VERSION(4, 0, 0)
for (auto &a : m_aGtkTargets)
g_free(a.target);
#endif
m_aGtkTargets.clear();
}
#if GTK_CHECK_VERSION(4, 0, 0)
IMPL_LINK_NOARG(VclGtkClipboard, DetachClipboard, void*, void)
{
ClipboardClear();
}
OString VclToGtkHelper::makeGtkTargetEntry(const css::datatransfer::DataFlavor& rFlavor)
{
OString aEntry = OUStringToOString(rFlavor.MimeType, RTL_TEXTENCODING_UTF8);
auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
DataFlavorEq(rFlavor));
if (it == aInfoToFlavor.end())
aInfoToFlavor.push_back(rFlavor);
return aEntry;
}
#else
GtkTargetEntry VclToGtkHelper::makeGtkTargetEntry(const css::datatransfer::DataFlavor& rFlavor)
{
GtkTargetEntry aEntry;
aEntry.target =
g_strdup(OUStringToOString(rFlavor.MimeType, RTL_TEXTENCODING_UTF8).getStr());
aEntry.flags = 0;
auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
DataFlavorEq(rFlavor));
if (it != aInfoToFlavor.end())
aEntry.info = std::distance(aInfoToFlavor.begin(), it);
else
{
aEntry.info = aInfoToFlavor.size();
aInfoToFlavor.push_back(rFlavor);
}
return aEntry;
}
#endif
#if GTK_CHECK_VERSION(4, 0, 0)
namespace
{
void write_mime_type_done(GObject* pStream, GAsyncResult* pResult, gpointer pTaskPtr)
{
GTask* pTask = static_cast<GTask*>(pTaskPtr);
GError* pError = nullptr;
if (!g_output_stream_write_all_finish(G_OUTPUT_STREAM(pStream),
pResult, nullptr, &pError))
{
g_task_return_error(pTask, pError);
}
else
{
g_task_return_boolean(pTask, true);
}
g_object_unref(pTask);
}
class MimeTypeEq
{
private:
const OUString& m_rMimeType;
public:
explicit MimeTypeEq(const OUString& rMimeType) : m_rMimeType(rMimeType) {}
bool operator() (const css::datatransfer::DataFlavor& rData) const
{
return rData.MimeType == m_rMimeType;
}
};
}
void VclToGtkHelper::setSelectionData(const Reference<css::datatransfer::XTransferable> &rTrans,
GdkContentProvider* provider,
const char* mime_type,
GOutputStream* stream,
int io_priority,
GCancellable* cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task = g_task_new(provider, cancellable, callback, user_data);
g_task_set_priority(task, io_priority);
OUString sMimeType(mime_type, strlen(mime_type), RTL_TEXTENCODING_UTF8);
auto it = std::find_if(aInfoToFlavor.begin(), aInfoToFlavor.end(),
MimeTypeEq(sMimeType));
if (it == aInfoToFlavor.end())
{
SAL_WARN( "vcl.gtk", "unknown mime-type request from clipboard");
g_task_return_new_error(task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"unknown mime-type “%s” request from clipboard", mime_type);
g_object_unref(task);
return;
}
css::datatransfer::DataFlavor aFlavor(*it);
if (aFlavor.MimeType == "UTF8_STRING" || aFlavor.MimeType == "STRING")
aFlavor.MimeType = "text/plain;charset=utf-8";
Sequence<sal_Int8> aData;
Any aValue;
try
{
aValue = rTrans->getTransferData(aFlavor);
}
catch (...)
{
}
if (aValue.getValueTypeClass() == TypeClass_STRING)
{
OUString aString;
aValue >>= aString;
aData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aString.getStr()), aString.getLength() * sizeof( sal_Unicode ) );
}
else if (aValue.getValueType() == cppu::UnoType<Sequence< sal_Int8 >>::get())
{
aValue >>= aData;
}
else if (aFlavor.MimeType == "text/plain;charset=utf-8")
{
//didn't have utf-8, try utf-16 and convert
aFlavor.MimeType = "text/plain;charset=utf-16";
aFlavor.DataType = cppu::UnoType<OUString>::get();
try
{
aValue = rTrans->getTransferData(aFlavor);
}
catch (...)
{
}
OUString aString;
aValue >>= aString;
OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
g_output_stream_write_all_async(stream, aUTF8String.getStr(), aUTF8String.getLength(),
io_priority, cancellable, write_mime_type_done, task);
return;
}
g_output_stream_write_all_async(stream, aData.getArray(), aData.getLength(),
io_priority, cancellable, write_mime_type_done, task);
}
#else
void VclToGtkHelper::setSelectionData(const Reference<css::datatransfer::XTransferable> &rTrans,
GtkSelectionData *selection_data, guint info)
{
GdkAtom type(gdk_atom_intern(OUStringToOString(aInfoToFlavor[info].MimeType,
RTL_TEXTENCODING_UTF8).getStr(),
false));
css::datatransfer::DataFlavor aFlavor(aInfoToFlavor[info]);
if (aFlavor.MimeType == "UTF8_STRING" || aFlavor.MimeType == "STRING")
aFlavor.MimeType = "text/plain;charset=utf-8";
Sequence<sal_Int8> aData;
Any aValue;
try
{
aValue = rTrans->getTransferData(aFlavor);
}
catch (...)
{
}
if (aValue.getValueTypeClass() == TypeClass_STRING)
{
OUString aString;
aValue >>= aString;
aData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aString.getStr()), aString.getLength() * sizeof( sal_Unicode ) );
}
else if (aValue.getValueType() == cppu::UnoType<Sequence< sal_Int8 >>::get())
{
aValue >>= aData;
}
else if (aFlavor.MimeType == "text/plain;charset=utf-8")
{
//didn't have utf-8, try utf-16 and convert
aFlavor.MimeType = "text/plain;charset=utf-16";
aFlavor.DataType = cppu::UnoType<OUString>::get();
try
{
aValue = rTrans->getTransferData(aFlavor);
}
catch (...)
{
}
OUString aString;
aValue >>= aString;
OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
gtk_selection_data_set(selection_data, type, 8,
reinterpret_cast<const guchar *>(aUTF8String.getStr()),
aUTF8String.getLength());
return;
}
gtk_selection_data_set(selection_data, type, 8,
reinterpret_cast<const guchar *>(aData.getArray()),
aData.getLength());
}
#endif
VclGtkClipboard::VclGtkClipboard(SelectionType eSelection)
: cppu::WeakComponentImplHelper<datatransfer::clipboard::XSystemClipboard,
datatransfer::clipboard::XFlushableClipboard, XServiceInfo>
(m_aMutex)
, m_eSelection(eSelection)
, m_pSetClipboardEvent(nullptr)
#if GTK_CHECK_VERSION(4, 0, 0)
, m_pClipboardContent(nullptr)
#endif
{
GdkClipboard* clipboard = clipboard_get(m_eSelection);
#if GTK_CHECK_VERSION(4, 0, 0)
m_nOwnerChangedSignalId = g_signal_connect(clipboard, "changed",
G_CALLBACK(handle_owner_change), this);
#else
m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
G_CALLBACK(handle_owner_change), this);
#endif
}
void VclGtkClipboard::flushClipboard()
{
#if !GTK_CHECK_VERSION(4, 0, 0)
SolarMutexGuard aGuard;
if (m_eSelection != SELECTION_CLIPBOARD)
return;
GdkClipboard* clipboard = clipboard_get(m_eSelection);
gtk_clipboard_store(clipboard);
#endif
}
VclGtkClipboard::~VclGtkClipboard()
{
GdkClipboard* clipboard = clipboard_get(m_eSelection);
g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
if (!m_aGtkTargets.empty())
{
#if GTK_CHECK_VERSION(4, 0, 0)
gdk_clipboard_set_content(clipboard, nullptr);
m_pClipboardContent = nullptr;
#else
gtk_clipboard_clear(clipboard);
#endif
ClipboardClear();
}
assert(!m_pSetClipboardEvent);
assert(m_aGtkTargets.empty());
}
#if GTK_CHECK_VERSION(4, 0, 0)
std::vector<OString> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
#else
std::vector<GtkTargetEntry> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
#endif
{
#if GTK_CHECK_VERSION(4, 0, 0)
std::vector<OString> aGtkTargets;
#else
std::vector<GtkTargetEntry> aGtkTargets;
#endif
bool bHaveText(false), bHaveUTF8(false);
for (const css::datatransfer::DataFlavor& rFlavor : rFormats)
{
sal_Int32 nIndex(0);
if (o3tl::getToken(rFlavor.MimeType, 0, ';', nIndex) == u"text/plain")
{
bHaveText = true;
std::u16string_view aToken(o3tl::getToken(rFlavor.MimeType, 0, ';', nIndex));
if (aToken == u"charset=utf-8")
{
bHaveUTF8 = true;
}
}
aGtkTargets.push_back(makeGtkTargetEntry(rFlavor));
}
if (bHaveText)
{
css::datatransfer::DataFlavor aFlavor;
aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
if (!bHaveUTF8)
{
aFlavor.MimeType = "text/plain;charset=utf-8";
aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
}
aFlavor.MimeType = "UTF8_STRING";
aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
aFlavor.MimeType = "STRING";
aGtkTargets.push_back(makeGtkTargetEntry(aFlavor));
}
return aGtkTargets;
}
IMPL_LINK_NOARG(VclGtkClipboard, AsyncSetGtkClipboard, void*, void)
{
osl::Guard aGuard( m_aMutex );
m_pSetClipboardEvent = nullptr;
SetGtkClipboard();
}
void VclGtkClipboard::SyncGtkClipboard()
{
osl::Guard aGuard(m_aMutex);
if (m_pSetClipboardEvent)
{
Application::RemoveUserEvent(m_pSetClipboardEvent);
m_pSetClipboardEvent = nullptr;
SetGtkClipboard();
}
}
void VclGtkClipboard::SetGtkClipboard()
{
GdkClipboard* clipboard = clipboard_get(m_eSelection);
#if GTK_CHECK_VERSION(4, 0, 0)
m_pClipboardContent = TRANSFERABLE_CONTENT(transerable_content_new(&m_aConversionHelper, m_aContents.get()));
transerable_content_set_detach_clipboard_link(m_pClipboardContent, LINK(this, VclGtkClipboard, DetachClipboard));
gdk_clipboard_set_content(clipboard, GDK_CONTENT_PROVIDER(m_pClipboardContent));
#else
gtk_clipboard_set_with_data(clipboard, m_aGtkTargets.data(), m_aGtkTargets.size(),
ClipboardGetFunc, ClipboardClearFunc, this);
gtk_clipboard_set_can_store(clipboard, m_aGtkTargets.data(), m_aGtkTargets.size());
#endif
}
void VclGtkClipboard::setContents(
const Reference< css::datatransfer::XTransferable >& xTrans,
const Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner )
{
css::uno::Sequence<css::datatransfer::DataFlavor> aFormats;
if (xTrans.is())
{
aFormats = xTrans->getTransferDataFlavors();
}
osl::ClearableMutexGuard aGuard( m_aMutex );
Reference< datatransfer::clipboard::XClipboardOwner > xOldOwner( m_aOwner );
Reference< datatransfer::XTransferable > xOldContents( m_aContents );
m_aContents = xTrans;
#if GTK_CHECK_VERSION(4, 0, 0)
if (m_pClipboardContent)
transerable_content_set_transferable(m_pClipboardContent, m_aContents.get());
#endif
m_aOwner = xClipboardOwner;
std::vector< Reference< datatransfer::clipboard::XClipboardListener > > aListeners( m_aListeners );
datatransfer::clipboard::ClipboardEvent aEv;
GdkClipboard* clipboard = clipboard_get(m_eSelection);
if (!m_aGtkTargets.empty())
{
#if GTK_CHECK_VERSION(4, 0, 0)
gdk_clipboard_set_content(clipboard, nullptr);
m_pClipboardContent = nullptr;
#else
gtk_clipboard_clear(clipboard);
#endif
ClipboardClear();
}
assert(m_aGtkTargets.empty());
if (m_aContents.is())
{
#if GTK_CHECK_VERSION(4, 0, 0)
std::vector<OString> aGtkTargets(m_aConversionHelper.FormatsToGtk(aFormats));
#else
std::vector<GtkTargetEntry> aGtkTargets(m_aConversionHelper.FormatsToGtk(aFormats));
#endif
if (!aGtkTargets.empty())
{
#if !GTK_CHECK_VERSION(4, 0, 0)
GtkTargetEntry aEntry;
OString sTunnel = "application/x-libreoffice-internal-id-" + getPID();
aEntry.target = g_strdup(sTunnel.getStr());
aEntry.flags = 0;
aEntry.info = 0;
aGtkTargets.push_back(aEntry);
#endif
m_aGtkTargets = std::move(aGtkTargets);
if (!m_pSetClipboardEvent)
m_pSetClipboardEvent = Application::PostUserEvent(LINK(this, VclGtkClipboard, AsyncSetGtkClipboard));
}
}
aEv.Contents = getContents();
aGuard.clear();
if (xOldOwner.is() && xOldOwner != xClipboardOwner)
xOldOwner->lostOwnership( this, xOldContents );
for (auto const& listener : aListeners)
{
listener->changedContents( aEv );
}
}
OUString VclGtkClipboard::getName()
{
return (m_eSelection == SELECTION_CLIPBOARD) ? u"CLIPBOARD"_ustr : u"PRIMARY"_ustr;
}
sal_Int8 VclGtkClipboard::getRenderingCapabilities()
{
return 0;
}
void VclGtkClipboard::addClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
{
osl::Guard aGuard( m_aMutex );
m_aListeners.push_back( listener );
}
void VclGtkClipboard::removeClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
{
osl::Guard aGuard( m_aMutex );
std::erase(m_aListeners, listener);
}
Reference<css::datatransfer::clipboard::XClipboard>
GtkInstance::CreateClipboard(const Sequence<Any>& arguments)
{
if ( o3tl::IsRunningUnitTest() || o3tl::IsRunningUITest() )
return SalInstance::CreateClipboard( arguments );
OUString sel;
if (!arguments.hasElements()) {
sel = "CLIPBOARD";
} else if (arguments.getLength() != 1 || !(arguments[0] >>= sel)) {
throw css::lang::IllegalArgumentException(
u"bad GtkInstance::CreateClipboard arguments"_ustr,
css::uno::Reference<css::uno::XInterface>(), -1);
}
SelectionType eSelection = (sel == "CLIPBOARD") ? SELECTION_CLIPBOARD : SELECTION_PRIMARY;
if (m_aClipboards[eSelection].is())
return m_aClipboards[eSelection];
Reference<css::datatransfer::clipboard::XClipboard> xClipboard(new VclGtkClipboard(eSelection));
m_aClipboards[eSelection] = xClipboard;
return xClipboard;
}
GtkInstDropTarget::GtkInstDropTarget()
: WeakComponentImplHelper(m_aMutex)
, m_pFrame(nullptr)
, m_pFormatConversionRequest(nullptr)
, m_bActive(false)
#if !GTK_CHECK_VERSION(4, 0, 0)
, m_bInDrag(false)
#endif
, m_nDefaultActions(0)
{
}
GtkInstDropTarget::GtkInstDropTarget(GtkSalFrame* pFrame)
: GtkInstDropTarget()
{
assert(pFrame && "missing SalFrame");
m_pFrame = pFrame;
m_pFrame->registerDropTarget(this);
m_bActive = true;
}
OUString SAL_CALL GtkInstDropTarget::getImplementationName()
{
return u"com.sun.star.datatransfer.dnd.VclGtkDropTarget"_ustr;
}
sal_Bool SAL_CALL GtkInstDropTarget::supportsService(OUString const & ServiceName)
{
return cppu::supportsService(this, ServiceName);
}
css::uno::Sequence<OUString> SAL_CALL GtkInstDropTarget::getSupportedServiceNames()
{
Sequence<OUString> aRet { u"com.sun.star.datatransfer.dnd.GtkDropTarget"_ustr };
return aRet;
}
GtkInstDropTarget::~GtkInstDropTarget()
{
if (m_pFrame)
m_pFrame->deregisterDropTarget(this);
}
void GtkInstDropTarget::deinitialize()
{
m_pFrame = nullptr;
m_bActive = false;
}
void GtkInstDropTarget::addDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
{
::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
m_aListeners.push_back( xListener );
}
void GtkInstDropTarget::removeDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
{
::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
std::erase(m_aListeners, xListener);
}
void GtkInstDropTarget::fire_drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde)
{
osl::ClearableGuard<osl::Mutex> aGuard( m_aMutex );
std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
aGuard.clear();
for (auto const& listener : aListeners)
{
listener->drop( dtde );
}
}
void GtkInstDropTarget::fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde)
{
osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
aGuard.clear();
for (auto const& listener : aListeners)
{
listener->dragEnter( dtde );
}
}
void GtkInstDropTarget::fire_dragOver(const css::datatransfer::dnd::DropTargetDragEvent& dtde)
{
osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
aGuard.clear();
for (auto const& listener : aListeners)
{
listener->dragOver( dtde );
}
}
void GtkInstDropTarget::fire_dragExit(const css::datatransfer::dnd::DropTargetEvent& dte)
{
osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
std::vector<css::uno::Reference<css::datatransfer::dnd::XDropTargetListener>> aListeners(m_aListeners);
aGuard.clear();
for (auto const& listener : aListeners)
{
listener->dragExit( dte );
}
}
sal_Bool GtkInstDropTarget::isActive()
{
return m_bActive;
}
void GtkInstDropTarget::setActive(sal_Bool bActive)
{
m_bActive = bActive;
}
sal_Int8 GtkInstDropTarget::getDefaultActions()
{
return m_nDefaultActions;
}
void GtkInstDropTarget::setDefaultActions(sal_Int8 nDefaultActions)
{
m_nDefaultActions = nDefaultActions;
}
css::uno::Reference<css::datatransfer::dnd::XDropTarget>
GtkInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv)
{
return new GtkInstDropTarget(static_cast<GtkSalFrame*>(pSysEnv->pSalFrame));
}
GtkInstDragSource::GtkInstDragSource(GtkSalFrame* pFrame)
: GtkInstDragSource()
{
assert(pFrame && "missing SalFrame");
m_pFrame = pFrame;
m_pFrame->registerDragSource(this);
}
GtkInstDragSource::~GtkInstDragSource()
{
if (m_pFrame)
m_pFrame->deregisterDragSource(this);
if (GtkInstDragSource::g_ActiveDragSource == this)
{
SAL_WARN( "vcl.gtk", "dragEnd should have been called on GtkInstDragSource before dtor");
GtkInstDragSource::g_ActiveDragSource = nullptr;
}
}
void GtkInstDragSource::deinitialize()
{
m_pFrame = nullptr;
}
sal_Bool GtkInstDragSource::isDragImageSupported()
{
return true;
}
sal_Int32 GtkInstDragSource::getDefaultCursor( sal_Int8 )
{
return 0;
}
OUString SAL_CALL GtkInstDragSource::getImplementationName()
{
return u"com.sun.star.datatransfer.dnd.VclGtkDragSource"_ustr;
}
sal_Bool SAL_CALL GtkInstDragSource::supportsService(OUString const & ServiceName)
{
return cppu::supportsService(this, ServiceName);
}
css::uno::Sequence<OUString> SAL_CALL GtkInstDragSource::getSupportedServiceNames()
{
Sequence<OUString> aRet { u"com.sun.star.datatransfer.dnd.GtkDragSource"_ustr };
return aRet;
}
css::uno::Reference<css::datatransfer::dnd::XDragSource>
GtkInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv)
{
return new GtkInstDragSource(static_cast<GtkSalFrame*>(pSysEnv->pSalFrame));
}
namespace {
class GtkOpenGLContext : public OpenGLContext
{
GLWindow m_aGLWin;
GtkWidget *m_pGLArea;
GdkGLContext *m_pContext;
gulong m_nDestroySignalId;
gulong m_nRenderSignalId;
guint m_nAreaFrameBuffer;
guint m_nFrameBuffer;
guint m_nRenderBuffer;
guint m_nDepthBuffer;
guint m_nFrameScratchBuffer;
guint m_nRenderScratchBuffer;
guint m_nDepthScratchBuffer;
public:
GtkOpenGLContext()
: m_pGLArea(nullptr)
, m_pContext(nullptr)
, m_nDestroySignalId(0)
, m_nRenderSignalId(0)
, m_nAreaFrameBuffer(0)
, m_nFrameBuffer(0)
, m_nRenderBuffer(0)
, m_nDepthBuffer(0)
, m_nFrameScratchBuffer(0)
, m_nRenderScratchBuffer(0)
, m_nDepthScratchBuffer(0)
{
}
virtual void initWindow() override
{
if( !m_pChildWindow )
{
SystemWindowData winData = generateWinData(mpWindow, mbRequestLegacyContext);
m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
}
if (m_pChildWindow)
{
InitChildWindow(m_pChildWindow.get());
}
}
private:
virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }
static void signalDestroy(GtkWidget*, gpointer context)
{
GtkOpenGLContext* pThis = static_cast<GtkOpenGLContext*>(context);
pThis->m_pGLArea = nullptr;
pThis->m_nDestroySignalId = 0;
pThis->m_nRenderSignalId = 0;
}
static gboolean signalRender(GtkGLArea*, GdkGLContext*, gpointer window)
{
GtkOpenGLContext* pThis = static_cast<GtkOpenGLContext*>(window);
int scale = gtk_widget_get_scale_factor(pThis->m_pGLArea);
int width = pThis->m_aGLWin.Width * scale;
int height = pThis->m_aGLWin.Height * scale;
glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
glBindFramebuffer(GL_READ_FRAMEBUFFER, pThis->m_nAreaFrameBuffer);
glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
gdk_gl_context_make_current(pThis->m_pContext);
return true;
}
virtual void adjustToNewSize() override
{
if (!m_pGLArea)
return;
int scale = gtk_widget_get_scale_factor(m_pGLArea);
int width = m_aGLWin.Width * scale;
int height = m_aGLWin.Height * scale;
// seen in tdf#124729 width/height of 0 leading to GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
int allocwidth = std::max(width, 1);
int allocheight = std::max(height, 1);
gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea));
if (GError *pError = gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea)))
{
SAL_WARN("vcl.gtk", "gtk gl area error: " << pError->message);
return;
}
glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, allocwidth, allocheight);
glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, allocwidth, allocheight);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nAreaFrameBuffer);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_RENDERBUFFER_EXT, m_nRenderBuffer);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
GL_RENDERBUFFER_EXT, m_nDepthBuffer);
gdk_gl_context_make_current(m_pContext);
glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthBuffer);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameBuffer);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_RENDERBUFFER_EXT, m_nRenderBuffer);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
GL_RENDERBUFFER_EXT, m_nDepthBuffer);
glViewport(0, 0, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderScratchBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, allocwidth, allocheight);
glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthScratchBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, allocwidth, allocheight);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
GL_RENDERBUFFER_EXT, m_nDepthScratchBuffer);
glViewport(0, 0, width, height);
}
// Use a throw away toplevel to determine the OpenGL version because once
// an GdkGLContext is created for a window then it seems that
// glGenVertexArrays will always be called when the window gets rendered.
static int GetOpenGLVersion()
{
int nMajorGLVersion(0);
GtkWidget* pWindow;
#if !GTK_CHECK_VERSION(4,0,0)
pWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
#else
pWindow = gtk_window_new();
#endif
gtk_widget_realize(pWindow);
if (GdkSurface* pSurface = widget_get_surface(pWindow))
{
if (GdkGLContext* pContext = surface_create_gl_context(pSurface))
{
if (gdk_gl_context_realize(pContext, nullptr))
{
OpenGLZone aZone;
gdk_gl_context_make_current(pContext);
gdk_gl_context_get_version(pContext, &nMajorGLVersion, nullptr);
gdk_gl_context_clear_current();
}
g_object_unref(pContext);
}
}
#if !GTK_CHECK_VERSION(4,0,0)
gtk_widget_destroy(pWindow);
#else
gtk_window_destroy(GTK_WINDOW(pWindow));
#endif
return nMajorGLVersion;
}
virtual bool ImplInit() override
{
static int nOpenGLVersion = GetOpenGLVersion();
if (nOpenGLVersion < 3)
{
SAL_WARN("vcl.gtk", "gtk GL requires glGenVertexArrays which is OpenGL 3, while system provides: " << nOpenGLVersion);
return false;
}
const SystemEnvData* pEnvData = m_pChildWindow->GetSystemData();
GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
m_pGLArea = gtk_gl_area_new();
m_nDestroySignalId = g_signal_connect(G_OBJECT(m_pGLArea), "destroy", G_CALLBACK(signalDestroy), this);
m_nRenderSignalId = g_signal_connect(G_OBJECT(m_pGLArea), "render", G_CALLBACK(signalRender), this);
gtk_gl_area_set_has_depth_buffer(GTK_GL_AREA(m_pGLArea), true);
gtk_gl_area_set_auto_render(GTK_GL_AREA(m_pGLArea), false);
gtk_widget_set_hexpand(m_pGLArea, true);
gtk_widget_set_vexpand(m_pGLArea, true);
#if !GTK_CHECK_VERSION(4, 0, 0)
gtk_container_add(GTK_CONTAINER(pParent), m_pGLArea);
gtk_widget_show_all(pParent);
#else
gtk_grid_attach(GTK_GRID(pParent), m_pGLArea, 0, 0, 1, 1);
gtk_widget_set_visible(pParent, true);
gtk_widget_set_visible(m_pGLArea, true);
#endif
gtk_gl_area_make_current(GTK_GL_AREA(m_pGLArea));
if (GError *pError = gtk_gl_area_get_error(GTK_GL_AREA(m_pGLArea)))
{
SAL_WARN("vcl.gtk", "gtk gl area error: " << pError->message);
return false;
}
gtk_gl_area_attach_buffers(GTK_GL_AREA(m_pGLArea));
glGenFramebuffersEXT(1, &m_nAreaFrameBuffer);
GdkSurface* pWindow = widget_get_surface(pParent);
m_pContext = surface_create_gl_context(pWindow);
if (!m_pContext)
return false;
if (!gdk_gl_context_realize(m_pContext, nullptr))
return false;
gdk_gl_context_make_current(m_pContext);
glGenFramebuffersEXT(1, &m_nFrameBuffer);
glGenRenderbuffersEXT(1, &m_nRenderBuffer);
glGenRenderbuffersEXT(1, &m_nDepthBuffer);
glGenFramebuffersEXT(1, &m_nFrameScratchBuffer);
glGenRenderbuffersEXT(1, &m_nRenderScratchBuffer);
glGenRenderbuffersEXT(1, &m_nDepthScratchBuffer);
bool bRet = InitGL();
InitGLDebugging();
return bRet;
}
virtual void restoreDefaultFramebuffer() override
{
OpenGLContext::restoreDefaultFramebuffer();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
}
virtual void makeCurrent() override
{
if (isCurrent())
return;
clearCurrent();
if (m_pGLArea)
{
int scale = gtk_widget_get_scale_factor(m_pGLArea);
int width = m_aGLWin.Width * scale;
int height = m_aGLWin.Height * scale;
gdk_gl_context_make_current(m_pContext);
glBindRenderbuffer(GL_RENDERBUFFER, m_nRenderScratchBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, m_nDepthScratchBuffer);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_nFrameScratchBuffer);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_RENDERBUFFER_EXT, m_nRenderScratchBuffer);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
GL_RENDERBUFFER_EXT, m_nDepthScratchBuffer);
glViewport(0, 0, width, height);
}
registerAsCurrent();
}
virtual void destroyCurrentContext() override
{
gdk_gl_context_clear_current();
}
virtual bool isCurrent() override
{
return m_pGLArea && gdk_gl_context_get_current() == m_pContext;
}
virtual void sync() override
{
}
virtual void resetCurrent() override
{
clearCurrent();
gdk_gl_context_clear_current();
}
virtual void swapBuffers() override
{
int scale = gtk_widget_get_scale_factor(m_pGLArea);
int width = m_aGLWin.Width * scale;
int height = m_aGLWin.Height * scale;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_nFrameBuffer);
glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_nFrameScratchBuffer);
glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height,
GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_nFrameScratchBuffer);
glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
gtk_gl_area_queue_render(GTK_GL_AREA(m_pGLArea));
BuffersSwapped();
}
virtual ~GtkOpenGLContext() override
{
if (m_nDestroySignalId)
g_signal_handler_disconnect(m_pGLArea, m_nDestroySignalId);
if (m_nRenderSignalId)
g_signal_handler_disconnect(m_pGLArea, m_nRenderSignalId);
if (m_pContext)
g_clear_object(&m_pContext);
}
};
}
OpenGLContext* GtkInstance::CreateOpenGLContext()
{
return new GtkOpenGLContext;
}
// tdf#123800 avoid requiring wayland at runtime just because it existed at buildtime
bool DLSYM_GDK_IS_WAYLAND_DISPLAY(GdkDisplay* pDisplay)
{
static auto get_type = reinterpret_cast<GType (*) (void)>(dlsym(nullptr, "gdk_wayland_display_get_type"));
if (!get_type)
return false;
static bool bResult = G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
return bResult;
}
bool DLSYM_GDK_IS_X11_DISPLAY(GdkDisplay* pDisplay)
{
static auto get_type = reinterpret_cast<GType (*) (void)>(dlsym(nullptr, "gdk_x11_display_get_type"));
if (!get_type)
return false;
static bool bResult = G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
return bResult;
}
namespace
{
class GtkInstanceBuilder;
void set_help_id(const GtkWidget *pWidget, std::u16string_view rHelpId)
{
gchar *helpid = g_strdup(OUStringToOString(rHelpId, RTL_TEXTENCODING_UTF8).getStr());
g_object_set_data_full(G_OBJECT(pWidget), "g-lo-helpid", helpid, g_free);
}
OUString get_help_id(const GtkWidget *pWidget)
{
void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-helpid");
const gchar* pStr = static_cast<const gchar*>(pData);
return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
}
KeyEvent CreateKeyEvent(guint keyval, guint16 hardware_keycode, guint state, guint8 group)
{
sal_uInt16 nKeyCode = GtkSalFrame::GetKeyCode(keyval);
#if !GTK_CHECK_VERSION(4, 0, 0)
if (nKeyCode == 0)
{
guint updated_keyval = GtkSalFrame::GetKeyValFor(gdk_keymap_get_default(), hardware_keycode, group);
nKeyCode = GtkSalFrame::GetKeyCode(updated_keyval);
}
#else
(void)hardware_keycode;
(void)group;
#endif
nKeyCode |= GtkSalFrame::GetKeyModCode(state);
return KeyEvent(gdk_keyval_to_unicode(keyval), nKeyCode, 0);
}
#if !GTK_CHECK_VERSION(4, 0, 0)
KeyEvent GtkToVcl(const GdkEventKey& rEvent)
{
return CreateKeyEvent(rEvent.keyval, rEvent.hardware_keycode, rEvent.state, rEvent.group);
}
#endif
}
static MouseEventModifiers ImplGetMouseButtonMode(sal_uInt16 nButton, sal_uInt16 nCode)
{
MouseEventModifiers nMode = MouseEventModifiers::NONE;
if ( nButton == MOUSE_LEFT )
nMode |= MouseEventModifiers::SIMPLECLICK;
if ( (nButton == MOUSE_LEFT) && !(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT)) )
nMode |= MouseEventModifiers::SELECT;
if ( (nButton == MOUSE_LEFT) && (nCode & KEY_MOD1) &&
!(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_SHIFT)) )
nMode |= MouseEventModifiers::MULTISELECT;
if ( (nButton == MOUSE_LEFT) && (nCode & KEY_SHIFT) &&
!(nCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_MOD1)) )
nMode |= MouseEventModifiers::RANGESELECT;
return nMode;
}
static MouseEventModifiers ImplGetMouseMoveMode(sal_uInt16 nCode)
{
MouseEventModifiers nMode = MouseEventModifiers::NONE;
if ( !nCode )
nMode |= MouseEventModifiers::SIMPLEMOVE;
if ( (nCode & MOUSE_LEFT) && !(nCode & KEY_MOD1) )
nMode |= MouseEventModifiers::DRAGMOVE;
if ( (nCode & MOUSE_LEFT) && (nCode & KEY_MOD1) )
nMode |= MouseEventModifiers::DRAGCOPY;
return nMode;
}
namespace
{
bool SwapForRTL(GtkWidget* pWidget)
{
GtkTextDirection eDir = gtk_widget_get_direction(pWidget);
if (eDir == GTK_TEXT_DIR_RTL)
return true;
if (eDir == GTK_TEXT_DIR_LTR)
return false;
return AllSettings::GetLayoutRTL();
}
GtkWidget* getPopupRect(GtkWidget* pWidget, const tools::Rectangle& rInRect, GdkRectangle& rOutRect)
{
if (GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pWidget))
{
// this is the relatively unusual case where pParent is the toplevel GtkSalFrame and not a stock GtkWidget
// so use the same style of logic as GtkSalMenu::ShowNativePopupMenu to get the right position
AbsoluteScreenPixelRectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pFrame->GetWindow(), rInRect);
aFloatRect.Move(-pFrame->GetUnmirroredGeometry().x(), -pFrame->GetUnmirroredGeometry().y());
rOutRect = GdkRectangle{static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
pWidget = pFrame->getMouseEventWidget();
}
else
{
rOutRect = GdkRectangle{static_cast<int>(rInRect.Left()), static_cast<int>(rInRect.Top()),
static_cast<int>(rInRect.GetWidth()), static_cast<int>(rInRect.GetHeight())};
if (GTK_IS_ICON_VIEW(pWidget))
{
// GtkIconView is a little weird in its positioning with scrolling, so adjust here to match what
// it expects
gint nOffsetX(0), nOffsetY(0);
#if !GTK_CHECK_VERSION(4, 0, 0)
gtk_icon_view_convert_widget_to_bin_window_coords(GTK_ICON_VIEW(pWidget), 0, 0, &nOffsetX, &nOffsetY);
#else
GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pWidget));
nOffsetY = pVAdjustment ? gtk_adjustment_get_value(pVAdjustment) : 0;
GtkAdjustment* pHAdjustment = gtk_scrollable_get_hadjustment(GTK_SCROLLABLE(pWidget));
nOffsetX = pHAdjustment ? gtk_adjustment_get_value(pHAdjustment) : 0;
#endif
rOutRect.x -= nOffsetX;
rOutRect.y -= nOffsetY;
}
if (SwapForRTL(pWidget))
rOutRect.x = gtk_widget_get_allocated_width(pWidget) - rOutRect.width - 1 - rOutRect.x;
}
return pWidget;
}
void replaceWidget(GtkWidget* pWidget, GtkWidget* pReplacement)
{
// remove the widget and replace it with pReplacement
GtkWidget* pParent = gtk_widget_get_parent(pWidget);
// if pWidget was un-parented then don't bother
if (!pParent)
return;
g_object_ref(pWidget);
gint nTopAttach(0), nLeftAttach(0), nHeight(1), nWidth(1);
if (GTK_IS_GRID(pParent))
{
#if !GTK_CHECK_VERSION(4, 0, 0)
gtk_container_child_get(GTK_CONTAINER(pParent), pWidget,
"left-attach", &nLeftAttach,
"top-attach", &nTopAttach,
"width", &nWidth,
"height", &nHeight,
nullptr);
#else
gtk_grid_query_child(GTK_GRID(pParent), pWidget,
&nLeftAttach, &nTopAttach,
&nWidth, &nHeight);
#endif
}
#if !GTK_CHECK_VERSION(4, 0, 0)
gboolean bExpand(false), bFill(false);
GtkPackType ePackType(GTK_PACK_START);
guint nPadding(0);
gint nPosition(0);
if (GTK_IS_BOX(pParent))
{
gtk_container_child_get(GTK_CONTAINER(pParent), pWidget,
"expand", &bExpand,
"fill", &bFill,
"pack-type", &ePackType,
"padding", &nPadding,
"position", &nPosition,
nullptr);
}
#endif
#if !GTK_CHECK_VERSION(4, 0, 0)
// for gtk3 remove before replacement inserted, or there are warnings
// from GTK_BIN about having two children
container_remove(pParent, pWidget);
#endif
gtk_widget_set_visible(pReplacement, gtk_widget_get_visible(pWidget));
#if !GTK_CHECK_VERSION(4, 0, 0)
gtk_widget_set_no_show_all(pReplacement, gtk_widget_get_no_show_all(pWidget));
#endif
int nReqWidth, nReqHeight;
gtk_widget_get_size_request(pWidget, &nReqWidth, &nReqHeight);
gtk_widget_set_size_request(pReplacement, nReqWidth, nReqHeight);
static GQuark quark_size_groups = g_quark_from_static_string("gtk-widget-size-groups");
GSList* pSizeGroups = static_cast<GSList*>(g_object_get_qdata(G_OBJECT(pWidget), quark_size_groups));
while (pSizeGroups)
{
GtkSizeGroup *pSizeGroup = static_cast<GtkSizeGroup*>(pSizeGroups->data);
pSizeGroups = pSizeGroups->next;
gtk_size_group_remove_widget(pSizeGroup, pWidget);
gtk_size_group_add_widget(pSizeGroup, pReplacement);
}
// tdf#135368 change the mnemonic to point to our replacement
GList* pLabels = gtk_widget_list_mnemonic_labels(pWidget);
for (GList* pLabel = g_list_first(pLabels); pLabel; pLabel = g_list_next(pLabel))
{
GtkWidget* pLabelWidget = static_cast<GtkWidget*>(pLabel->data);
if (!GTK_IS_LABEL(pLabelWidget))
continue;
gtk_label_set_mnemonic_widget(GTK_LABEL(pLabelWidget), pReplacement);
}
g_list_free(pLabels);
if (GTK_IS_GRID(pParent))
{
gtk_grid_attach(GTK_GRID(pParent), pReplacement, nLeftAttach, nTopAttach, nWidth, nHeight);
}
else if (GTK_IS_BOX(pParent))
{
#if !GTK_CHECK_VERSION(4, 0, 0)
gtk_box_pack_start(GTK_BOX(pParent), pReplacement, bExpand, bFill, nPadding);
gtk_container_child_set(GTK_CONTAINER(pParent), pReplacement,
"pack-type", ePackType,
"position", nPosition,
nullptr);
#else
gtk_box_insert_child_after(GTK_BOX(pParent), pReplacement, pWidget);
#endif
}
#if !GTK_CHECK_VERSION(4, 0, 0)
else
gtk_container_add(GTK_CONTAINER(pParent), pReplacement);
#endif
if (gtk_widget_get_hexpand_set(pWidget))
gtk_widget_set_hexpand(pReplacement, gtk_widget_get_hexpand(pWidget));
if (gtk_widget_get_vexpand_set(pWidget))
gtk_widget_set_vexpand(pReplacement, gtk_widget_get_vexpand(pWidget));
gtk_widget_set_halign(pReplacement, gtk_widget_get_halign(pWidget));
gtk_widget_set_valign(pReplacement, gtk_widget_get_valign(pWidget));
#if GTK_CHECK_VERSION(4, 0, 0)
// for gtk4 remove after replacement inserted so we could use gtk_box_insert_child_after
container_remove(pParent, pWidget);
#endif
// coverity[freed_arg : FALSE] - this does not free pWidget, it is reffed by pReplacement
g_object_unref(pWidget);
}
void insertAsParent(GtkWidget* pWidget, GtkWidget* pReplacement)
{
g_object_ref(pWidget);
replaceWidget(pWidget, pReplacement);
// coverity[pass_freed_arg : FALSE] - pWidget is not freed here due to initial g_object_ref
container_add(pReplacement, pWidget);
// coverity[freed_arg : FALSE] - this does not free pWidget, it is reffed by pReplacement
g_object_unref(pWidget);
}
GtkWidget* ensureEventWidget(GtkWidget* pWidget)
{
#if GTK_CHECK_VERSION(4, 0, 0)
return pWidget;
#else
if (!pWidget)
return nullptr;
GtkWidget* pMouseEventBox;
// not every widget has a GdkWindow and can get any event, so if we
// want an event it doesn't have, insert a GtkEventBox so we can get
// those
if (gtk_widget_get_has_window(pWidget))
pMouseEventBox = pWidget;
else
{
// remove the widget and replace it with an eventbox and put the old
// widget into it
pMouseEventBox = gtk_event_box_new();
gtk_event_box_set_above_child(GTK_EVENT_BOX(pMouseEventBox), false);
gtk_event_box_set_visible_window(GTK_EVENT_BOX(pMouseEventBox), false);
insertAsParent(pWidget, pMouseEventBox);
}
return pMouseEventBox;
#endif
}
}
namespace {
#if !GTK_CHECK_VERSION(4, 0, 0)
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;
}
#endif
GtkWindow* get_active_window()
{
GtkWindow* pFocus = nullptr;
GList* pList = gtk_window_list_toplevels();
for (GList* pEntry = pList; pEntry; pEntry = pEntry->next)
{
#if GTK_CHECK_VERSION(4, 0, 0)
if (gtk_window_is_active(GTK_WINDOW(pEntry->data)))
#else
if (gtk_window_has_toplevel_focus(GTK_WINDOW(pEntry->data)))
#endif
{
pFocus = GTK_WINDOW(pEntry->data);
break;
}
}
g_list_free(pList);
return pFocus;
}
void LocalizeDecimalSeparator(guint& keyval)
{
const bool bDecimalKey = keyval == GDK_KEY_KP_Decimal || keyval == GDK_KEY_KP_Separator;
// #i1820# (and tdf#154623) use locale specific decimal separator
if (bDecimalKey && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
{
GtkWindow* pFocusWin = get_active_window();
GtkWidget* pFocus = pFocusWin ? gtk_window_get_focus(pFocusWin) : nullptr;
// tdf#138932 except if the target is a GtkEntry used for passwords
// GTK4: TODO is it a GtkEntry or a child GtkText that has the focus in this situation?
if (!pFocus || !GTK_IS_ENTRY(pFocus) || gtk_entry_get_visibility(GTK_ENTRY(pFocus)))
{
OUString aSep(Application::GetSettings().GetLocaleDataWrapper().getNumDecimalSep());
keyval = aSep[0];
}
}
}
void set_cursor(GtkWidget* pWidget, const char *pName)
{
if (!gtk_widget_get_realized(pWidget))
gtk_widget_realize(pWidget);
GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
#if GTK_CHECK_VERSION(4, 0, 0)
GdkCursor *pCursor = pName ? gdk_cursor_new_from_name(pName, nullptr) : nullptr;
#else
GdkCursor *pCursor = pName ? gdk_cursor_new_from_name(pDisplay, pName) : nullptr;
#endif
widget_set_cursor(pWidget, pCursor);
gdk_display_flush(pDisplay);
if (pCursor)
g_object_unref(pCursor);
}
vcl::Font get_font(GtkWidget* pWidget)
{
PangoContext* pContext = gtk_widget_get_pango_context(pWidget);
return pango_to_vcl(pango_context_get_font_description(pContext),
Application::GetSettings().GetUILanguageTag().getLocale());
}
}
OUString get_buildable_id(GtkBuildable* pWidget)
{
#if GTK_CHECK_VERSION(4, 0, 0)
const gchar* pStr = gtk_buildable_get_buildable_id(pWidget);
#else
const gchar* pStr = gtk_buildable_get_name(pWidget);
#endif
return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
}
void set_buildable_id(GtkBuildable* pWidget, const OUString& rId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
GtkBuildableIface *iface = GTK_BUILDABLE_GET_IFACE(pWidget);
(*iface->set_id)(pWidget, OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
#else
gtk_buildable_set_name(pWidget, OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
#endif
}
namespace {
class GtkInstanceWidget : public virtual weld::Widget
{
protected:
GtkWidget* m_pWidget;
GtkWidget* m_pMouseEventBox;
GtkInstanceBuilder* m_pBuilder;
#if !GTK_CHECK_VERSION(4, 0, 0)
DECL_LINK(async_drag_cancel, void*, void);
#endif
bool IsFirstFreeze() const { return m_nFreezeCount == 0; }
bool IsLastThaw() const { return m_nFreezeCount == 1; }
#if GTK_CHECK_VERSION(4, 0, 0)
static void signalFocusIn(GtkEventControllerFocus*, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
SolarMutexGuard aGuard;
pThis->signal_focus_in();
}
#else
static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
SolarMutexGuard aGuard;
pThis->signal_focus_in();
return false;
}
#endif
void signal_focus_in()
{
GtkWidget* pTopLevel = widget_get_toplevel(m_pWidget);
// see commentary in GtkSalObjectWidgetClip::Show
if (pTopLevel && g_object_get_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange"))
return;
weld::Widget::signal_focus_in();
}
static gboolean signalMnemonicActivate(GtkWidget*, gboolean, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
SolarMutexGuard aGuard;
return pThis->signal_mnemonic_activate();
}
#if GTK_CHECK_VERSION(4, 0, 0)
static void signalFocusOut(GtkEventControllerFocus*, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
SolarMutexGuard aGuard;
pThis->signal_focus_out();
}
#else
static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
SolarMutexGuard aGuard;
pThis->signal_focus_out();
return false;
}
#endif
#if !GTK_CHECK_VERSION(4, 0, 0)
void launch_drag_cancel(GdkDragContext* context)
{
// post our drag cancel to happen at the next available event cycle
if (m_pDragCancelEvent)
return;
g_object_ref(context);
m_pDragCancelEvent = Application::PostUserEvent(LINK(this, GtkInstanceWidget, async_drag_cancel), context);
}
#endif
void signal_focus_out()
{
GtkWidget* pTopLevel = widget_get_toplevel(m_pWidget);
// see commentary in GtkSalObjectWidgetClip::Show
if (pTopLevel && g_object_get_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange"))
return;
weld::Widget::signal_focus_out();
}
virtual void ensureMouseEventWidget()
{
if (!m_pMouseEventBox)
m_pMouseEventBox = ::ensureEventWidget(m_pWidget);
}
void ensureButtonPressSignal()
{
if (!m_nButtonPressSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
GtkEventController* pClickController = get_click_controller();
m_nButtonPressSignalId = g_signal_connect(pClickController, "pressed", G_CALLBACK(signalButtonPress), this);
#else
ensureMouseEventWidget();
m_nButtonPressSignalId = g_signal_connect(m_pMouseEventBox, "button-press-event", G_CALLBACK(signalButtonPress), this);
#endif
}
}
void ensureButtonReleaseSignal()
{
if (!m_nButtonReleaseSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
GtkEventController* pClickController = get_click_controller();
m_nButtonReleaseSignalId = g_signal_connect(pClickController, "released", G_CALLBACK(signalButtonRelease), this);
#else
ensureMouseEventWidget();
m_nButtonReleaseSignalId = g_signal_connect(m_pMouseEventBox, "button-release-event", G_CALLBACK(signalButtonRelease), this);
#endif
}
}
#if !GTK_CHECK_VERSION(4, 0, 0)
static gboolean signalPopupMenu(GtkWidget* pWidget, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
SolarMutexGuard aGuard;
//center it when we don't know where else to use
Point aPos(gtk_widget_get_allocated_width(pWidget) / 2,
gtk_widget_get_allocated_height(pWidget) / 2);
CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, false);
return pThis->signal_popup_menu(aCEvt);
}
#endif
virtual void connect_style_updated(const Link<Widget&, void>& rLink) override
{
if (m_aStyleUpdatedHdl.IsSet())
ImplGetDefaultWindow()->RemoveEventListener(LINK(this, GtkInstanceWidget, SettingsChangedHdl));
weld::Widget::connect_style_updated(rLink);
if (m_aStyleUpdatedHdl.IsSet())
ImplGetDefaultWindow()->AddEventListener(LINK(this, GtkInstanceWidget, SettingsChangedHdl));
}
bool SwapForRTL() const
{
return ::SwapForRTL(m_pWidget);
}
void do_enable_drag_source(const rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants)
{
ensure_drag_source();
#if !GTK_CHECK_VERSION(4, 0, 0)
auto aFormats = rHelper->getTransferDataFlavors();
std::vector<GtkTargetEntry> aGtkTargets(m_xDragSource->FormatsToGtk(aFormats));
m_eDragAction = VclToGdk(eDNDConstants);
drag_source_set(aGtkTargets, m_eDragAction);
for (auto &a : aGtkTargets)
g_free(a.target);
m_xDragSource->set_datatransfer(rHelper, rHelper);
#else
(void)rHelper;
(void)eDNDConstants;
#endif
}
void localizeDecimalSeparator()
{
// tdf#128867 if localize decimal separator is active we will always
// need to be able to change the output of the decimal key press
if (!m_nKeyPressSignalId && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
{
#if GTK_CHECK_VERSION(4, 0, 0)
m_nKeyPressSignalId = g_signal_connect(get_key_controller(), "key-pressed", G_CALLBACK(signalKeyPressed), this);
#else
m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
#endif
}
}
void ensure_drag_begin_end()
{
if (!m_nDragBeginSignalId)
{
// using "after" due to https://gitlab.gnome.org/GNOME/pygobject/issues/251
#if GTK_CHECK_VERSION(4, 0, 0)
m_nDragBeginSignalId = g_signal_connect_after(get_drag_controller(), "drag-begin", G_CALLBACK(signalDragBegin), this);
#else
m_nDragBeginSignalId = g_signal_connect_after(m_pWidget, "drag-begin", G_CALLBACK(signalDragBegin), this);
#endif
}
if (!m_nDragEndSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
m_nDragEndSignalId = g_signal_connect(get_drag_controller(), "drag-end", G_CALLBACK(signalDragEnd), this);
#else
m_nDragEndSignalId = g_signal_connect(m_pWidget, "drag-end", G_CALLBACK(signalDragEnd), this);
#endif
}
}
void DisconnectMouseEvents()
{
if (m_nButtonPressSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
g_signal_handler_disconnect(get_click_controller(), m_nButtonPressSignalId);
#else
g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonPressSignalId);
#endif
m_nButtonPressSignalId = 0;
}
if (m_nMotionSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
g_signal_handler_disconnect(get_motion_controller(), m_nMotionSignalId);
#else
g_signal_handler_disconnect(m_pMouseEventBox, m_nMotionSignalId);
#endif
m_nMotionSignalId = 0;
}
if (m_nLeaveSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
g_signal_handler_disconnect(get_motion_controller(), m_nLeaveSignalId);
#else
g_signal_handler_disconnect(m_pMouseEventBox, m_nLeaveSignalId);
#endif
m_nLeaveSignalId = 0;
}
if (m_nEnterSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
g_signal_handler_disconnect(get_motion_controller(), m_nEnterSignalId);
#else
g_signal_handler_disconnect(m_pMouseEventBox, m_nEnterSignalId);
#endif
m_nEnterSignalId = 0;
}
if (m_nButtonReleaseSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
g_signal_handler_disconnect(get_click_controller(), m_nButtonReleaseSignalId);
#else
g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonReleaseSignalId);
#endif
m_nButtonReleaseSignalId = 0;
}
#if !GTK_CHECK_VERSION(4, 0, 0)
if (!m_pMouseEventBox || m_pMouseEventBox == m_pWidget)
return;
// GtkWindow replacement for GtkPopover case
if (!GTK_IS_EVENT_BOX(m_pMouseEventBox))
{
m_pMouseEventBox = nullptr;
return;
}
// put things back they way we found them
GtkWidget* pParent = gtk_widget_get_parent(m_pMouseEventBox);
g_object_ref(m_pWidget);
gtk_container_remove(GTK_CONTAINER(m_pMouseEventBox), m_pWidget);
gtk_widget_destroy(m_pMouseEventBox);
gtk_container_add(GTK_CONTAINER(pParent), m_pWidget);
// coverity[freed_arg : FALSE] - this does not free m_pWidget, it is reffed by pParent
g_object_unref(m_pWidget);
m_pMouseEventBox = m_pWidget;
#endif
}
private:
bool m_bTakeOwnership;
#if !GTK_CHECK_VERSION(4, 0, 0)
bool m_bDraggedOver;
#endif
int m_nWaitCount;
int m_nFreezeCount;
sal_uInt16 m_nLastMouseButton;
#if !GTK_CHECK_VERSION(4, 0, 0)
sal_uInt16 m_nLastMouseClicks;
#endif
int m_nPressedButton;
#if !GTK_CHECK_VERSION(4, 0, 0)
protected:
int m_nPressStartX;
int m_nPressStartY;
private:
#endif
ImplSVEvent* m_pDragCancelEvent;
GtkCssProvider* m_pBgCssProvider;
#if !GTK_CHECK_VERSION(4, 0, 0)
GdkDragAction m_eDragAction;
#endif
gulong m_nFocusInSignalId;
gulong m_nMnemonicActivateSignalId;
gulong m_nFocusOutSignalId;
gulong m_nKeyPressSignalId;
gulong m_nKeyReleaseSignalId;
protected:
gulong m_nSizeAllocateSignalId;
private:
gulong m_nButtonPressSignalId;
gulong m_nMotionSignalId;
gulong m_nLeaveSignalId;
gulong m_nEnterSignalId;
gulong m_nButtonReleaseSignalId;
gulong m_nDragMotionSignalId;
gulong m_nDragDropSignalId;
gulong m_nDragDropReceivedSignalId;
gulong m_nDragLeaveSignalId;
gulong m_nDragBeginSignalId;
gulong m_nDragEndSignalId;
gulong m_nDragFailedSignalId;
gulong m_nDragDataDeleteignalId;
gulong m_nDragGetSignalId;
// whether mouse has explicitly been grabbed from LO application code
bool m_bMouseGrabbed;
#if GTK_CHECK_VERSION(4, 0, 0)
GtkEventController* m_pFocusController;
GtkEventController* m_pClickController;
GtkEventController* m_pMotionController;
GtkEventController* m_pDragController;
GtkEventController* m_pKeyController;
#endif
rtl::Reference<GtkInstDropTarget> m_xDropTarget;
rtl::Reference<GtkInstDragSource> m_xDragSource;
static void signalSizeAllocate(GtkWidget*, GdkRectangle* allocation, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
SolarMutexGuard aGuard;
pThis->signal_size_allocate(allocation->width, allocation->height);
}
#if GTK_CHECK_VERSION(4, 0, 0)
static gboolean signalKeyPressed(GtkEventControllerKey*, guint keyval, guint keycode, GdkModifierType state, gpointer widget)
{
LocalizeDecimalSeparator(keyval);
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
return pThis->signal_key_press(keyval, keycode, state);
}
static gboolean signalKeyReleased(GtkEventControllerKey*, guint keyval, guint keycode, GdkModifierType state, gpointer widget)
{
LocalizeDecimalSeparator(keyval);
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
return pThis->signal_key_release(keyval, keycode, state);
}
#else
static gboolean signalKey(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
{
LocalizeDecimalSeparator(pEvent->keyval);
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
if (pEvent->type == GDK_KEY_PRESS)
return pThis->signal_key_press(pEvent);
return pThis->signal_key_release(pEvent);
}
#endif
virtual bool signal_popup_menu(const CommandEvent&)
{
return false;
}
#if GTK_CHECK_VERSION(4, 0, 0)
static void signalButtonPress(GtkGestureClick* pGesture, int n_press, gdouble x, gdouble y, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
SolarMutexGuard aGuard;
pThis->signal_button(pGesture, SalEvent::MouseButtonDown, n_press, x, y);
}
static void signalButtonRelease(GtkGestureClick* pGesture, int n_press, gdouble x, gdouble y, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
SolarMutexGuard aGuard;
pThis->signal_button(pGesture, SalEvent::MouseButtonUp, n_press, x, y);
}
void signal_button(GtkGestureClick* pGesture, SalEvent nEventType, int n_press, gdouble x, gdouble y)
{
m_nPressedButton = -1;
Point aPos(x, y);
if (SwapForRTL())
aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
if (n_press == 1)
{
GdkEventSequence* pSequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(pGesture));
GdkEvent* pEvent = gtk_gesture_get_last_event(GTK_GESTURE(pGesture), pSequence);
if (gdk_event_triggers_context_menu(pEvent))
{
//if handled for context menu, stop processing
CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, true);
if (signal_popup_menu(aCEvt))
{
gtk_gesture_set_state(GTK_GESTURE(pGesture), GTK_EVENT_SEQUENCE_CLAIMED);
return;
}
}
}
GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pGesture));
int nButton = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(pGesture));
switch (nButton)
{
case 1:
m_nLastMouseButton = MOUSE_LEFT;
break;
case 2:
m_nLastMouseButton = MOUSE_MIDDLE;
break;
case 3:
m_nLastMouseButton = MOUSE_RIGHT;
break;
default:
return;
}
sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(eType);
// strip out which buttons are involved from the nModCode and replace with m_nLastMouseButton
sal_uInt16 nCode = m_nLastMouseButton | (nModCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2));
MouseEvent aMEvt(aPos, n_press, ImplGetMouseButtonMode(m_nLastMouseButton, nModCode), nCode, nCode);
if (nEventType == SalEvent::MouseButtonDown && signal_mouse_press(aMEvt))
gtk_gesture_set_state(GTK_GESTURE(pGesture), GTK_EVENT_SEQUENCE_CLAIMED);
if (nEventType == SalEvent::MouseButtonUp && signal_mouse_release(aMEvt))
gtk_gesture_set_state(GTK_GESTURE(pGesture), GTK_EVENT_SEQUENCE_CLAIMED);
}
#else
static gboolean signalButtonPress(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
SolarMutexGuard aGuard;
return pThis->signal_button(pEvent);
}
static gboolean signalButtonRelease(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
SolarMutexGuard aGuard;
return pThis->signal_button(pEvent);
}
bool signal_button(GdkEventButton* pEvent)
{
m_nPressedButton = -1;
Point aPos(pEvent->x, pEvent->y);
if (SwapForRTL())
aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
if (gdk_event_triggers_context_menu(reinterpret_cast<GdkEvent*>(pEvent)) && pEvent->type == GDK_BUTTON_PRESS)
{
//if handled for context menu, stop processing
CommandEvent aCEvt(aPos, CommandEventId::ContextMenu, true);
if (signal_popup_menu(aCEvt))
return true;
}
/* Save press to possibly begin a drag */
if (pEvent->type != GDK_BUTTON_RELEASE)
{
m_nPressedButton = pEvent->button;
m_nPressStartX = pEvent->x;
m_nPressStartY = pEvent->y;
}
if (!m_aMousePressHdl.IsSet() && !m_aMouseReleaseHdl.IsSet())
return false;
SalEvent nEventType = SalEvent::NONE;
switch (pEvent->type)
{
case GDK_BUTTON_PRESS:
if (GdkEvent* pPeekEvent = gdk_event_peek())
{
bool bSkip = pPeekEvent->type == GDK_2BUTTON_PRESS ||
pPeekEvent->type == GDK_3BUTTON_PRESS;
gdk_event_free(pPeekEvent);
if (bSkip)
{
return false;
}
}
nEventType = SalEvent::MouseButtonDown;
m_nLastMouseClicks = 1;
break;
case GDK_2BUTTON_PRESS:
m_nLastMouseClicks = 2;
nEventType = SalEvent::MouseButtonDown;
break;
case GDK_3BUTTON_PRESS:
m_nLastMouseClicks = 3;
nEventType = SalEvent::MouseButtonDown;
break;
case GDK_BUTTON_RELEASE:
nEventType = SalEvent::MouseButtonUp;
break;
default:
return false;
}
switch (pEvent->button)
{
case 1:
m_nLastMouseButton = MOUSE_LEFT;
break;
case 2:
m_nLastMouseButton = MOUSE_MIDDLE;
break;
case 3:
m_nLastMouseButton = MOUSE_RIGHT;
break;
default:
return false;
}
sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
// strip out which buttons are involved from the nModCode and replace with m_nLastMouseButton
sal_uInt16 nCode = m_nLastMouseButton | (nModCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2));
MouseEvent aMEvt(aPos, m_nLastMouseClicks, ImplGetMouseButtonMode(m_nLastMouseButton, nModCode), nCode, nCode);
if (nEventType == SalEvent::MouseButtonDown)
{
if (!m_aMousePressHdl.IsSet())
return false;
return signal_mouse_press(aMEvt);
}
if (!m_aMouseReleaseHdl.IsSet())
return false;
return signal_mouse_release(aMEvt);
}
#endif
bool simple_signal_motion(double x, double y, guint nState)
{
if (!m_aMouseMotionHdl.IsSet())
return false;
Point aPos(x, y);
if (SwapForRTL())
aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(nState);
MouseEvent aMEvt(aPos, 0, ImplGetMouseMoveMode(nModCode), nModCode, nModCode);
return signal_mouse_motion(aMEvt);
}
#if GTK_CHECK_VERSION(4, 0, 0)
static void signalMotion(GtkEventControllerMotion *pController, double x, double y, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
SolarMutexGuard aGuard;
pThis->simple_signal_motion(x, y, eType);
}
#else
static gboolean signalMotion(GtkWidget*, GdkEventMotion* pEvent, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
SolarMutexGuard aGuard;
return pThis->signal_motion(pEvent);
}
bool signal_motion(const GdkEventMotion* pEvent)
{
const bool bDragData = m_eDragAction != 0 && m_nPressedButton != -1 && m_xDragSource.is() && gtk_drag_source_get_target_list(m_pWidget);
bool bUnsetDragIcon(false);
if (bDragData && gtk_drag_check_threshold(m_pWidget, m_nPressStartX, m_nPressStartY, pEvent->x, pEvent->y) && !do_signal_drag_begin(bUnsetDragIcon))
{
GdkDragContext* pContext = gtk_drag_begin_with_coordinates(m_pWidget,
gtk_drag_source_get_target_list(m_pWidget),
m_eDragAction,
m_nPressedButton,
const_cast<GdkEvent*>(reinterpret_cast<const GdkEvent*>(pEvent)),
m_nPressStartX, m_nPressStartY);
if (pContext && bUnsetDragIcon)
{
cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
gtk_drag_set_icon_surface(pContext, surface);
cairo_surface_destroy(surface);
}
m_nPressedButton = -1;
return false;
}
return simple_signal_motion(pEvent->x, pEvent->y, pEvent->state);
}
#endif
bool signal_crossing(double x, double y, guint nState, MouseEventModifiers eMouseEventModifiers)
{
if (!m_aMouseMotionHdl.IsSet())
return false;
Point aPos(x, y);
if (SwapForRTL())
aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(nState);
MouseEventModifiers eModifiers = ImplGetMouseMoveMode(nModCode);
eModifiers = eModifiers | eMouseEventModifiers;
MouseEvent aMEvt(aPos, 0, eModifiers, nModCode, nModCode);
signal_mouse_motion(aMEvt);
return false;
}
#if GTK_CHECK_VERSION(4, 0, 0)
static void signalEnter(GtkEventControllerMotion *pController, double x, double y, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
SolarMutexGuard aGuard;
pThis->signal_crossing(x, y, eType, MouseEventModifiers::ENTERWINDOW);
}
static void signalLeave(GtkEventControllerMotion *pController, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
SolarMutexGuard aGuard;
pThis->signal_crossing(-1, -1, eType, MouseEventModifiers::LEAVEWINDOW);
}
#else
static gboolean signalCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
MouseEventModifiers eMouseEventModifiers = pEvent->type == GDK_ENTER_NOTIFY ? MouseEventModifiers::ENTERWINDOW : MouseEventModifiers::LEAVEWINDOW;
SolarMutexGuard aGuard;
return pThis->signal_crossing(pEvent->x, pEvent->y, pEvent->state, eMouseEventModifiers);
}
#endif
virtual void drag_started()
{
}
#if !GTK_CHECK_VERSION(4, 0, 0)
static gboolean signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
if (!pThis->m_bDraggedOver)
{
pThis->m_bDraggedOver = true;
pThis->drag_started();
}
return pThis->m_xDropTarget->signalDragMotion(pWidget, context, x, y, time);
}
static gboolean signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
return pThis->m_xDropTarget->signalDragDrop(pWidget, context, x, y, time);
}
static void signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
pThis->m_xDropTarget->signalDragDropReceived(pWidget, context, x, y, data, ttype, time);
}
#endif
virtual void drag_ended()
{
}
#if !GTK_CHECK_VERSION(4, 0, 0)
static void signalDragLeave(GtkWidget* pWidget, GdkDragContext*, guint /*time*/, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
pThis->m_xDropTarget->signalDragLeave(pWidget);
if (pThis->m_bDraggedOver)
{
pThis->m_bDraggedOver = false;
pThis->drag_ended();
}
}
#endif
#if GTK_CHECK_VERSION(4, 0, 0)
static void signalDragBegin(GtkDragSource* context, GdkDrag*, gpointer widget)
#else
static void signalDragBegin(GtkWidget*, GdkDragContext* context, gpointer widget)
#endif
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
pThis->signal_drag_begin(context);
}
void ensure_drag_source()
{
if (!m_xDragSource)
{
m_xDragSource.set(new GtkInstDragSource);
#if !GTK_CHECK_VERSION(4, 0, 0)
m_nDragFailedSignalId = g_signal_connect(m_pWidget, "drag-failed", G_CALLBACK(signalDragFailed), this);
m_nDragDataDeleteignalId = g_signal_connect(m_pWidget, "drag-data-delete", G_CALLBACK(signalDragDelete), this);
m_nDragGetSignalId = g_signal_connect(m_pWidget, "drag-data-get", G_CALLBACK(signalDragDataGet), this);
#endif
ensure_drag_begin_end();
}
}
virtual bool do_signal_drag_begin(bool& rUnsetDragIcon)
{
rUnsetDragIcon = false;
return false;
}
#if GTK_CHECK_VERSION(4, 0, 0)
virtual void drag_set_icon(GtkDragSource*)
#else
virtual void drag_set_icon(GdkDragContext*)
#endif
{
}
#if GTK_CHECK_VERSION(4, 0, 0)
void signal_drag_begin(GtkDragSource* context)
#else
void signal_drag_begin(GdkDragContext* context)
#endif
{
bool bUnsetDragIcon(false);
if (do_signal_drag_begin(bUnsetDragIcon))
{
#if !GTK_CHECK_VERSION(4, 0, 0)
launch_drag_cancel(context);
#endif
return;
}
if (bUnsetDragIcon)
{
#if !GTK_CHECK_VERSION(4, 0, 0)
cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
gtk_drag_set_icon_surface(context, surface);
cairo_surface_destroy(surface);
#endif
}
else
{
drag_set_icon(context);
}
if (!m_xDragSource)
return;
m_xDragSource->setActiveDragSource();
}
virtual void do_signal_drag_end()
{
}
#if GTK_CHECK_VERSION(4, 0, 0)
static void signalDragEnd(GtkGestureDrag* /*gesture*/, double /*offset_x*/, double /*offset_y*/, gpointer widget)
#else
static void signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer widget)
#endif
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
pThis->do_signal_drag_end();
#if !GTK_CHECK_VERSION(4, 0, 0)
if (pThis->m_xDragSource.is())
pThis->m_xDragSource->dragEnd(context);
#endif
}
#if !GTK_CHECK_VERSION(4, 0, 0)
static gboolean signalDragFailed(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkDragResult /*result*/, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
pThis->m_xDragSource->dragFailed();
return false;
}
static void signalDragDelete(GtkWidget* /*widget*/, GdkDragContext* /*context*/, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
pThis->m_xDragSource->dragDelete();
}
static void signalDragDataGet(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkSelectionData *data, guint info,
guint /*time*/, gpointer widget)
{
GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
pThis->m_xDragSource->dragDataGet(data, info);
}
#endif
#if !GTK_CHECK_VERSION(4, 0, 0)
virtual void drag_source_set(const std::vector<GtkTargetEntry>& rGtkTargets, GdkDragAction eDragAction)
{
if (rGtkTargets.empty() && !eDragAction)
gtk_drag_source_unset(m_pWidget);
else
gtk_drag_source_set(m_pWidget, GDK_BUTTON1_MASK, rGtkTargets.data(), rGtkTargets.size(), eDragAction);
}
#endif
void do_set_background(const Color& rColor)
{
const bool bRemoveColor = rColor == COL_AUTO;
if (bRemoveColor && !m_pBgCssProvider)
return;
GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(GTK_WIDGET(m_pWidget));
if (m_pBgCssProvider)
{
gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider));
m_pBgCssProvider = nullptr;
}
if (bRemoveColor)
return;
OUString sColor = rColor.AsRGBHexString();
m_pBgCssProvider = gtk_css_provider_new();
OUString aBuffer = "* { background-color: #" + sColor + "; }";
OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
css_provider_load_from_data(m_pBgCssProvider, aResult.getStr(), aResult.getLength());
gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
}
DECL_LINK(SettingsChangedHdl, VclWindowEvent&, void);
#if !GTK_CHECK_VERSION(4, 0, 0)
static void update_style(GtkWidget* pWidget, gpointer pData)
{
if (GTK_IS_CONTAINER(pWidget))
gtk_container_foreach(GTK_CONTAINER(pWidget), update_style, pData);
GtkWidgetClass* pWidgetClass = GTK_WIDGET_GET_CLASS(pWidget);
pWidgetClass->style_updated(pWidget);
}
#endif
public:
GtkInstanceWidget(GtkWidget* pWidget, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
: m_pWidget(pWidget)
, m_pMouseEventBox(nullptr)
, m_pBuilder(pBuilder)
, m_bTakeOwnership(bTakeOwnership)
#if !GTK_CHECK_VERSION(4, 0, 0)
, m_bDraggedOver(false)
#endif
, m_nWaitCount(0)
, m_nFreezeCount(0)
, m_nLastMouseButton(0)
#if !GTK_CHECK_VERSION(4, 0, 0)
, m_nLastMouseClicks(0)
#endif
, m_nPressedButton(-1)
#if !GTK_CHECK_VERSION(4, 0, 0)
, m_nPressStartX(-1)
, m_nPressStartY(-1)
#endif
, m_pDragCancelEvent(nullptr)
, m_pBgCssProvider(nullptr)
#if !GTK_CHECK_VERSION(4, 0, 0)
, m_eDragAction(GdkDragAction(0))
#endif
, m_nFocusInSignalId(0)
, m_nMnemonicActivateSignalId(0)
, m_nFocusOutSignalId(0)
, m_nKeyPressSignalId(0)
, m_nKeyReleaseSignalId(0)
, m_nSizeAllocateSignalId(0)
, m_nButtonPressSignalId(0)
, m_nMotionSignalId(0)
, m_nLeaveSignalId(0)
, m_nEnterSignalId(0)
, m_nButtonReleaseSignalId(0)
, m_nDragMotionSignalId(0)
, m_nDragDropSignalId(0)
, m_nDragDropReceivedSignalId(0)
, m_nDragLeaveSignalId(0)
, m_nDragBeginSignalId(0)
, m_nDragEndSignalId(0)
, m_nDragFailedSignalId(0)
, m_nDragDataDeleteignalId(0)
, m_nDragGetSignalId(0)
, m_bMouseGrabbed(false)
#if GTK_CHECK_VERSION(4, 0, 0)
, m_pFocusController(nullptr)
, m_pClickController(nullptr)
, m_pMotionController(nullptr)
, m_pDragController(nullptr)
, m_pKeyController(nullptr)
#endif
{
if (!bTakeOwnership)
g_object_ref(m_pWidget);
#if !GTK_CHECK_VERSION(4, 0, 0)
const char* pId = gtk_buildable_get_name(GTK_BUILDABLE(m_pWidget));
if (pId)
{
static auto func = reinterpret_cast<void(*)(AtkObject*, const char*)>(dlsym(nullptr, "atk_object_set_accessible_id"));
if (func)
{
AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
assert(pAtkObject);
(*func)(pAtkObject, pId);
}
}
#endif
localizeDecimalSeparator();
}
virtual void connect_key_press(const Link<const KeyEvent&, bool>& rLink) override
{
if (!m_nKeyPressSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
m_nKeyPressSignalId = g_signal_connect(get_key_controller(), "key-pressed", G_CALLBACK(signalKeyPressed), this);
#else
m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
#endif
}
weld::Widget::connect_key_press(rLink);
}
virtual void connect_key_release(const Link<const KeyEvent&, bool>& rLink) override
{
if (!m_nKeyReleaseSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
m_nKeyReleaseSignalId = g_signal_connect(get_key_controller(), "key-released", G_CALLBACK(signalKeyReleased), this);
#else
m_nKeyReleaseSignalId = g_signal_connect(m_pWidget, "key-release-event", G_CALLBACK(signalKey), this);
#endif
}
weld::Widget::connect_key_release(rLink);
}
virtual void connect_mouse_press(const Link<const MouseEvent&, bool>& rLink) override
{
ensureButtonPressSignal();
weld::Widget::connect_mouse_press(rLink);
}
virtual void connect_mouse_move(const Link<const MouseEvent&, bool>& rLink) override
{
#if GTK_CHECK_VERSION(4, 0, 0)
GtkEventController* pMotionController = get_motion_controller();
if (!m_nMotionSignalId)
m_nMotionSignalId = g_signal_connect(pMotionController, "motion", G_CALLBACK(signalMotion), this);
if (!m_nLeaveSignalId)
m_nLeaveSignalId = g_signal_connect(pMotionController, "leave", G_CALLBACK(signalEnter), this);
if (!m_nEnterSignalId)
m_nEnterSignalId = g_signal_connect(pMotionController, "enter", G_CALLBACK(signalLeave), this);
#else
ensureMouseEventWidget();
if (!m_nMotionSignalId)
m_nMotionSignalId = g_signal_connect(m_pMouseEventBox, "motion-notify-event", G_CALLBACK(signalMotion), this);
if (!m_nLeaveSignalId)
m_nLeaveSignalId = g_signal_connect(m_pMouseEventBox, "leave-notify-event", G_CALLBACK(signalCrossing), this);
if (!m_nEnterSignalId)
m_nEnterSignalId = g_signal_connect(m_pMouseEventBox, "enter-notify-event", G_CALLBACK(signalCrossing), this);
#endif
weld::Widget::connect_mouse_move(rLink);
}
virtual void connect_mouse_release(const Link<const MouseEvent&, bool>& rLink) override
{
ensureButtonReleaseSignal();
weld::Widget::connect_mouse_release(rLink);
}
virtual void set_sensitive(bool sensitive) override
{
gtk_widget_set_sensitive(m_pWidget, sensitive);
}
virtual bool get_sensitive() const override
{
return gtk_widget_get_sensitive(m_pWidget);
}
virtual bool get_visible() const override
{
return gtk_widget_get_visible(m_pWidget);
}
virtual bool is_visible() const override
{
return gtk_widget_is_visible(m_pWidget);
}
virtual void set_can_focus(bool bCanFocus) override
{
gtk_widget_set_can_focus(m_pWidget, bCanFocus);
}
virtual void grab_focus() override
{
if (has_focus())
return;
gtk_widget_grab_focus(m_pWidget);
}
virtual bool has_focus() const override
{
return gtk_widget_has_focus(m_pWidget);
}
virtual bool is_active() const override
{
GtkWindow* pTopLevel = GTK_WINDOW(widget_get_toplevel(m_pWidget));
return pTopLevel && gtk_window_is_active(pTopLevel) && has_focus();
}
// is the focus in a child of this widget, where a transient popup attached
// to a widget is considered a child of that widget
virtual bool has_child_focus() const override
{
GtkWindow* pFocusWin = get_active_window();
if (!pFocusWin)
return false;
GtkWidget* pFocus = gtk_window_get_focus(pFocusWin);
if (pFocus && gtk_widget_is_ancestor(pFocus, m_pWidget))
return true;
#if !GTK_CHECK_VERSION(4, 0, 0)
GtkWidget* pAttachedTo = gtk_window_get_attached_to(pFocusWin);
if (!pAttachedTo)
return false;
if (pAttachedTo == m_pWidget || gtk_widget_is_ancestor(pAttachedTo, m_pWidget))
return true;
#endif
return false;
}
virtual void show() override
{
gtk_widget_set_visible(m_pWidget, true);
}
virtual void hide() override
{
gtk_widget_set_visible(m_pWidget, false);
}
virtual void set_size_request(int nWidth, int nHeight) override
{
GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
if (GTK_IS_VIEWPORT(pParent))
pParent = gtk_widget_get_parent(pParent);
if (GTK_IS_SCROLLED_WINDOW(pParent))
{
gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(pParent), nWidth);
gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(pParent), nHeight);
}
gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
}
virtual Size get_size_request() const override
{
int nWidth, nHeight;
gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
return Size(nWidth, nHeight);
}
virtual Size get_preferred_size() const override
{
GtkRequisition size;
gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
return Size(size.width, size.height);
}
virtual float get_approximate_digit_width() const override
{
PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
PangoFontMetrics* pMetrics = pango_context_get_metrics(pContext,
pango_context_get_font_description(pContext),
pango_context_get_language(pContext));
float nDigitWidth = pango_font_metrics_get_approximate_digit_width(pMetrics);
pango_font_metrics_unref(pMetrics);
return nDigitWidth / PANGO_SCALE;
}
virtual int get_text_height() const override
{
PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
PangoFontMetrics* pMetrics = pango_context_get_metrics(pContext,
pango_context_get_font_description(pContext),
pango_context_get_language(pContext));
int nLineHeight = pango_font_metrics_get_ascent(pMetrics) + pango_font_metrics_get_descent(pMetrics);
pango_font_metrics_unref(pMetrics);
return nLineHeight / PANGO_SCALE;
}
virtual Size get_pixel_size(const OUString& rText) const override
{
OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
PangoLayout* pLayout = gtk_widget_create_pango_layout(m_pWidget, aStr.getStr());
gint nWidth, nHeight;
pango_layout_get_pixel_size(pLayout, &nWidth, &nHeight);
g_object_unref(pLayout);
return Size(nWidth, nHeight);
}
virtual vcl::Font get_font() override
{
return ::get_font(m_pWidget);
}
virtual void set_hexpand(bool bExpand) override
{
gtk_widget_set_hexpand(m_pWidget, bExpand);
}
virtual bool get_hexpand() const override
{
return gtk_widget_get_hexpand(m_pWidget);
}
virtual void set_vexpand(bool bExpand) override
{
gtk_widget_set_vexpand(m_pWidget, bExpand);
}
virtual bool get_vexpand() const override
{
return gtk_widget_get_vexpand(m_pWidget);
}
virtual void set_margin_top(int nMargin) override
{
gtk_widget_set_margin_top(m_pWidget, nMargin);
}
virtual void set_margin_bottom(int nMargin) override
{
gtk_widget_set_margin_bottom(m_pWidget, nMargin);
}
virtual void set_margin_start(int nMargin) override
{
gtk_widget_set_margin_start(m_pWidget, nMargin);
}
virtual void set_margin_end(int nMargin) override
{
gtk_widget_set_margin_end(m_pWidget, nMargin);
}
virtual int get_margin_top() const override
{
return gtk_widget_get_margin_top(m_pWidget);
}
virtual int get_margin_bottom() const override
{
return gtk_widget_get_margin_bottom(m_pWidget);
}
virtual int get_margin_start() const override
{
return gtk_widget_get_margin_start(m_pWidget);
}
virtual int get_margin_end() const override
{
return gtk_widget_get_margin_end(m_pWidget);
}
virtual void set_accessible_name(const OUString& rName) override
{
#if GTK_CHECK_VERSION(4, 0, 0)
gtk_accessible_update_property(GTK_ACCESSIBLE(m_pWidget), GTK_ACCESSIBLE_PROPERTY_LABEL,
OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr(), -1);
#else
AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
if (!pAtkObject)
return;
atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
#endif
}
virtual void set_accessible_description(const OUString& rDescription) override
{
#if GTK_CHECK_VERSION(4, 0, 0)
gtk_accessible_update_property(GTK_ACCESSIBLE(m_pWidget), GTK_ACCESSIBLE_PROPERTY_DESCRIPTION,
OUStringToOString(rDescription, RTL_TEXTENCODING_UTF8).getStr(), -1);
#else
AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
if (!pAtkObject)
return;
atk_object_set_description(pAtkObject, OUStringToOString(rDescription, RTL_TEXTENCODING_UTF8).getStr());
#endif
}
virtual OUString get_accessible_name() const override
{
#if !GTK_CHECK_VERSION(4, 0, 0)
AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
const char* pStr = pAtkObject ? atk_object_get_name(pAtkObject) : nullptr;
return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
#else
char* pStr = gtk_test_accessible_check_property(GTK_ACCESSIBLE(m_pWidget), GTK_ACCESSIBLE_PROPERTY_LABEL, nullptr);
OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
g_free(pStr);
return sRet;
#endif
}
virtual OUString get_accessible_description() const override
{
#if !GTK_CHECK_VERSION(4, 0, 0)
AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
const char* pStr = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
#else
char* pStr = gtk_test_accessible_check_property(GTK_ACCESSIBLE(m_pWidget), GTK_ACCESSIBLE_PROPERTY_DESCRIPTION, nullptr);
OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
g_free(pStr);
return sRet;
#endif
}
virtual OUString get_accessible_id() const override
{
#if !GTK_CHECK_VERSION(4, 0, 0)
#if ATK_CHECK_VERSION(2, 34, 0)
AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
const char* pStr = pAtkObject ? atk_object_get_accessible_id(pAtkObject) : nullptr;
return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
#else
return OUString();
#endif
#else
return OUString();
#endif
}
virtual void set_accessible_relation_labeled_by(weld::Widget* pLabel) override
{
GtkWidget* pGtkLabel = pLabel ? dynamic_cast<GtkInstanceWidget&>(*pLabel).getWidget() : nullptr;
#if GTK_CHECK_VERSION(4, 0, 0)
gtk_accessible_update_relation(GTK_ACCESSIBLE(m_pWidget),
GTK_ACCESSIBLE_RELATION_LABELLED_BY,
pGtkLabel, nullptr,
-1);
#else
AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
if (!pAtkObject)
return;
AtkObject *pAtkLabel = pGtkLabel ? gtk_widget_get_accessible(pGtkLabel) : nullptr;
AtkRelationSet *pRelationSet = atk_object_ref_relation_set(pAtkObject);
AtkRelation *pRelation = atk_relation_set_get_relation_by_type(pRelationSet, ATK_RELATION_LABELLED_BY);
if (pRelation)
{
// clear ATK_RELATION_LABEL_FOR from old label
GPtrArray* pOldLabelTarget = atk_relation_get_target(pRelation);
guint nElements = pOldLabelTarget ? pOldLabelTarget->len : 0;
for (guint i = 0; i < nElements; ++i)
{
gpointer pOldLabelObject = g_ptr_array_index(pOldLabelTarget, i);
AtkRelationSet *pOldLabelRelationSet = atk_object_ref_relation_set(ATK_OBJECT(pOldLabelObject));
if (AtkRelation *pOldLabelRelation = atk_relation_set_get_relation_by_type(pRelationSet, ATK_RELATION_LABEL_FOR))
atk_relation_set_remove(pOldLabelRelationSet, pOldLabelRelation);
g_object_unref(pOldLabelRelationSet);
}
atk_relation_set_remove(pRelationSet, pRelation);
}
if (pAtkLabel)
{
AtkObject *obj_array_labelled_by[1];
obj_array_labelled_by[0] = pAtkLabel;
pRelation = atk_relation_new(obj_array_labelled_by, 1, ATK_RELATION_LABELLED_BY);
atk_relation_set_add(pRelationSet, pRelation);
// add ATK_RELATION_LABEL_FOR to new label to match
AtkRelationSet *pNewLabelRelationSet = atk_object_ref_relation_set(pAtkLabel);
AtkRelation *pNewLabelRelation = atk_relation_set_get_relation_by_type(pNewLabelRelationSet, ATK_RELATION_LABEL_FOR);
if (pNewLabelRelation)
atk_relation_set_remove(pNewLabelRelationSet, pRelation);
AtkObject *obj_array_label_for[1];
obj_array_label_for[0] = pAtkObject;
pNewLabelRelation = atk_relation_new(obj_array_label_for, 1, ATK_RELATION_LABEL_FOR);
atk_relation_set_add(pNewLabelRelationSet, pNewLabelRelation);
g_object_unref(pNewLabelRelationSet);
}
g_object_unref(pRelationSet);
#endif
}
virtual bool get_extents_relative_to(const weld::Widget& rRelative, int& x, int &y, int& width, int &height) const override
{
//for toplevel windows this is sadly futile under wayland, so we can't tell where a dialog is in order to allow
//the document underneath to auto-scroll to place content in a visible location
gtk_coord fX(0.0), fY(0.0);
bool ret = gtk_widget_translate_coordinates(m_pWidget,
dynamic_cast<const GtkInstanceWidget&>(rRelative).getWidget(),
0, 0, &fX, &fY);
x = fX;
y = fY;
width = gtk_widget_get_allocated_width(m_pWidget);
height = gtk_widget_get_allocated_height(m_pWidget);
return ret;
}
virtual void set_tooltip_text(const OUString& rTip) override
{
gtk_widget_set_tooltip_text(m_pWidget, OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
}
virtual OUString get_tooltip_text() const override
{
const gchar* pStr = gtk_widget_get_tooltip_text(m_pWidget);
return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
}
virtual void set_cursor_data(void * /*pData*/) override {};
virtual std::unique_ptr<weld::Container> weld_parent() const override;
virtual OUString get_buildable_name() const override
{
return ::get_buildable_id(GTK_BUILDABLE(m_pWidget));
}
virtual void set_buildable_name(const OUString& rId) override
{
::set_buildable_id(GTK_BUILDABLE(m_pWidget), rId);
}
virtual void set_help_id(const OUString& rHelpId) override
{
::set_help_id(m_pWidget, rHelpId);
}
virtual OUString get_help_id() const override
{
OUString sRet = ::get_help_id(m_pWidget);
if (sRet.isEmpty())
sRet = "null";
return sRet;
}
GtkWidget* getWidget() const
{
return m_pWidget;
}
GtkWindow* getWindow() const
{
return GTK_WINDOW(widget_get_toplevel(m_pWidget));
}
#if GTK_CHECK_VERSION(4, 0, 0)
GtkEventController* get_focus_controller()
{
if (!m_pFocusController)
{
gtk_widget_set_focusable(m_pWidget, true);
m_pFocusController = gtk_event_controller_focus_new();
gtk_widget_add_controller(m_pWidget, m_pFocusController);
}
return m_pFocusController;
}
#if GTK_CHECK_VERSION(4, 0, 0)
GtkEventController* get_click_controller()
{
if (!m_pClickController)
{
GtkGesture *pClick = gtk_gesture_click_new();
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pClick), 0);
m_pClickController = GTK_EVENT_CONTROLLER(pClick);
gtk_widget_add_controller(m_pWidget, m_pClickController);
}
return m_pClickController;
}
GtkEventController* get_motion_controller()
{
if (!m_pMotionController)
{
m_pMotionController = gtk_event_controller_motion_new();
gtk_widget_add_controller(m_pWidget, m_pMotionController);
}
return m_pMotionController;
}
GtkEventController* get_drag_controller()
{
if (!m_pDragController)
{
GtkDragSource* pDrag = gtk_drag_source_new();
m_pDragController = GTK_EVENT_CONTROLLER(pDrag);
gtk_widget_add_controller(m_pWidget, m_pDragController);
}
return m_pDragController;
}
GtkEventController* get_key_controller()
{
if (!m_pKeyController)
{
m_pKeyController = gtk_event_controller_key_new();
gtk_widget_add_controller(m_pWidget, m_pKeyController);
}
return m_pKeyController;
}
#endif
#endif
virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
{
if (!m_nFocusInSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
m_nFocusInSignalId = g_signal_connect(get_focus_controller(), "enter", G_CALLBACK(signalFocusIn), this);
#else
m_nFocusInSignalId = g_signal_connect(m_pWidget, "focus-in-event", G_CALLBACK(signalFocusIn), this);
#endif
}
weld::Widget::connect_focus_in(rLink);
}
virtual void connect_mnemonic_activate(const Link<Widget&, bool>& rLink) override
{
if (!m_nMnemonicActivateSignalId)
m_nMnemonicActivateSignalId = g_signal_connect(m_pWidget, "mnemonic-activate", G_CALLBACK(signalMnemonicActivate), this);
weld::Widget::connect_mnemonic_activate(rLink);
}
virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
{
if (!m_nFocusOutSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
m_nFocusOutSignalId = g_signal_connect(get_focus_controller(), "leave", G_CALLBACK(signalFocusOut), this);
#else
m_nFocusOutSignalId = g_signal_connect(m_pWidget, "focus-out-event", G_CALLBACK(signalFocusOut), this);
#endif
}
weld::Widget::connect_focus_out(rLink);
}
virtual void connect_size_allocate(const Link<const Size&, void>& rLink) override
{
m_nSizeAllocateSignalId = g_signal_connect(m_pWidget, "size-allocate", G_CALLBACK(signalSizeAllocate), this);
weld::Widget::connect_size_allocate(rLink);
}
virtual void signal_size_allocate(guint nWidth, guint nHeight)
{
weld::Widget::signal_size_allocate(Size(nWidth, nHeight));
}
#if GTK_CHECK_VERSION(4, 0, 0)
bool signal_key_press(guint keyval, guint keycode, GdkModifierType state)
{
if (m_aKeyPressHdl.IsSet())
{
SolarMutexGuard aGuard;
return weld::Widget::signal_key_press(CreateKeyEvent(keyval, keycode, state, 0));
}
return false;
}
bool signal_key_release(guint keyval, guint keycode, GdkModifierType state)
{
if (m_aKeyReleaseHdl.IsSet())
{
SolarMutexGuard aGuard;
return weld::Widget::signal_key_release(CreateKeyEvent(keyval, keycode, state, 0));
}
return false;
}
#else
virtual bool do_signal_key_press(const GdkEventKey* pEvent)
{
if (m_aKeyPressHdl.IsSet())
{
SolarMutexGuard aGuard;
return weld::Widget::signal_key_press(GtkToVcl(*pEvent));
}
return false;
}
virtual bool do_signal_key_release(const GdkEventKey* pEvent)
{
if (m_aKeyReleaseHdl.IsSet())
{
SolarMutexGuard aGuard;
return weld::Widget::signal_key_release(GtkToVcl(*pEvent));
}
return false;
}
bool signal_key_press(const GdkEventKey* pEvent)
{
return do_signal_key_press(pEvent);
}
bool signal_key_release(const GdkEventKey* pEvent)
{
return do_signal_key_release(pEvent);
}
#endif
virtual void grab_mouse() override
{
#if !GTK_CHECK_VERSION(4, 0, 0)
// have at most one grab owned by LO application code
if (!m_bMouseGrabbed)
gtk_grab_add(m_pWidget);
#endif
m_bMouseGrabbed = true;
}
virtual bool has_mouse_grab() const override
{
#if GTK_CHECK_VERSION(4, 0, 0)
return m_bMouseGrabbed;
#else
return gtk_widget_has_grab(m_pWidget);
#endif
}
virtual void release_mouse() override
{
#if !GTK_CHECK_VERSION(4, 0, 0)
if (m_bMouseGrabbed)
gtk_grab_remove(m_pWidget);
#endif
m_bMouseGrabbed = false;
}
virtual bool get_direction() const override
{
return gtk_widget_get_direction(m_pWidget) == GTK_TEXT_DIR_RTL;
}
virtual void set_direction(bool bRTL) override
{
gtk_widget_set_direction(m_pWidget, bRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
}
virtual void freeze() override
{
++m_nFreezeCount;
#if !GTK_CHECK_VERSION(4, 0, 0)
gtk_widget_freeze_child_notify(m_pWidget);
#endif
g_object_freeze_notify(G_OBJECT(m_pWidget));
}
virtual void thaw() override
{
--m_nFreezeCount;
g_object_thaw_notify(G_OBJECT(m_pWidget));
#if !GTK_CHECK_VERSION(4, 0, 0)
gtk_widget_thaw_child_notify(m_pWidget);
#endif
}
virtual void set_busy_cursor(bool bBusy) override
{
if (bBusy)
++m_nWaitCount;
else
--m_nWaitCount;
if (m_nWaitCount == 1)
set_cursor(m_pWidget, "progress");
else if (m_nWaitCount == 0)
set_cursor(m_pWidget, nullptr);
assert (m_nWaitCount >= 0);
}
virtual void queue_resize() override
{
gtk_widget_queue_resize(m_pWidget);
}
virtual css::uno::Reference<css::datatransfer::dnd::XDropTarget> get_drop_target() override
{
if (!m_xDropTarget)
{
m_xDropTarget.set(new GtkInstDropTarget);
#if !GTK_CHECK_VERSION(4, 0, 0)
if (!gtk_drag_dest_get_track_motion(m_pWidget))
{
gtk_drag_dest_set(m_pWidget, GtkDestDefaults(0), nullptr, 0, GdkDragAction(0));
gtk_drag_dest_set_track_motion(m_pWidget, true);
}
m_nDragMotionSignalId = g_signal_connect(m_pWidget, "drag-motion", G_CALLBACK(signalDragMotion), this);
m_nDragDropSignalId = g_signal_connect(m_pWidget, "drag-drop", G_CALLBACK(signalDragDrop), this);
m_nDragDropReceivedSignalId = g_signal_connect(m_pWidget, "drag-data-received", G_CALLBACK(signalDragDropReceived), this);
m_nDragLeaveSignalId = g_signal_connect(m_pWidget, "drag-leave", G_CALLBACK(signalDragLeave), this);
#endif
}
return m_xDropTarget;
}
virtual css::uno::Reference<css::datatransfer::clipboard::XClipboard> get_clipboard() const override
{
// the gen backend can have per-frame clipboards which is (presumably) useful for LibreOffice Online
// but normal usage is the shared system clipboard
return GetSystemClipboard();
}
virtual void connect_get_property_tree(const Link<tools::JsonWriter&, void>& /*rLink*/) override
{
//not implemented for the gtk variant
}
virtual void get_property_tree(tools::JsonWriter& /*rJsonWriter*/) override
{
//not implemented for the gtk variant
}
virtual void call_attention_to() override
{
// Change the class name to restart the animation under
// its other name: https://css-tricks.com/restart-css-animation/
#if GTK_CHECK_VERSION(4, 0, 0)
if (gtk_widget_has_css_class(m_pWidget, "call_attention_1"))
{
gtk_widget_remove_css_class(m_pWidget, "call_attention_1");
gtk_widget_add_css_class(m_pWidget, "call_attention_2");
}
else
{
gtk_widget_remove_css_class(m_pWidget, "call_attention_2");
gtk_widget_add_css_class(m_pWidget, "call_attention_1");
}
#else
GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(m_pWidget);
if (gtk_style_context_has_class(pWidgetContext, "call_attention_1"))
{
gtk_style_context_remove_class(pWidgetContext, "call_attention_1");
gtk_style_context_add_class(pWidgetContext, "call_attention_2");
}
else
{
gtk_style_context_remove_class(pWidgetContext, "call_attention_2");
gtk_style_context_add_class(pWidgetContext, "call_attention_1");
}
#endif
}
virtual void set_stack_background() override
{
do_set_background(Application::GetSettings().GetStyleSettings().GetWindowColor());
}
virtual void set_title_background() override
{
do_set_background(Application::GetSettings().GetStyleSettings().GetShadowColor());
}
virtual void set_highlight_background() override
{
do_set_background(Application::GetSettings().GetStyleSettings().GetHighlightColor());
}
virtual void set_background(const Color& rColor) override
{
do_set_background(rColor);
}
virtual void set_background() override
{
do_set_background(COL_AUTO); // reset to default
}
virtual void set_toolbar_background() override
{
// no-op
}
virtual ~GtkInstanceWidget() override
{
if (m_aStyleUpdatedHdl.IsSet())
ImplGetDefaultWindow()->RemoveEventListener(LINK(this, GtkInstanceWidget, SettingsChangedHdl));
if (m_pDragCancelEvent)
Application::RemoveUserEvent(m_pDragCancelEvent);
if (m_nDragMotionSignalId)
g_signal_handler_disconnect(m_pWidget, m_nDragMotionSignalId);
if (m_nDragDropSignalId)
g_signal_handler_disconnect(m_pWidget, m_nDragDropSignalId);
if (m_nDragDropReceivedSignalId)
g_signal_handler_disconnect(m_pWidget, m_nDragDropReceivedSignalId);
if (m_nDragLeaveSignalId)
g_signal_handler_disconnect(m_pWidget, m_nDragLeaveSignalId);
if (m_nDragEndSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
g_signal_handler_disconnect(get_drag_controller(), m_nDragEndSignalId);
#else
g_signal_handler_disconnect(m_pWidget, m_nDragEndSignalId);
#endif
}
if (m_nDragBeginSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
g_signal_handler_disconnect(get_drag_controller(), m_nDragBeginSignalId);
#else
g_signal_handler_disconnect(m_pWidget, m_nDragBeginSignalId);
#endif
}
if (m_nDragFailedSignalId)
g_signal_handler_disconnect(m_pWidget, m_nDragFailedSignalId);
if (m_nDragDataDeleteignalId)
g_signal_handler_disconnect(m_pWidget, m_nDragDataDeleteignalId);
if (m_nDragGetSignalId)
g_signal_handler_disconnect(m_pWidget, m_nDragGetSignalId);
if (m_nKeyPressSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
g_signal_handler_disconnect(get_key_controller(), m_nKeyPressSignalId);
#else
g_signal_handler_disconnect(m_pWidget, m_nKeyPressSignalId);
#endif
}
if (m_nKeyReleaseSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
g_signal_handler_disconnect(get_key_controller(), m_nKeyReleaseSignalId);
#else
g_signal_handler_disconnect(m_pWidget, m_nKeyReleaseSignalId);
#endif
}
if (m_nFocusInSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
g_signal_handler_disconnect(get_focus_controller(), m_nFocusInSignalId);
#else
g_signal_handler_disconnect(m_pWidget, m_nFocusInSignalId);
#endif
}
if (m_nMnemonicActivateSignalId)
g_signal_handler_disconnect(m_pWidget, m_nMnemonicActivateSignalId);
if (m_nFocusOutSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
g_signal_handler_disconnect(get_focus_controller(), m_nFocusOutSignalId);
#else
g_signal_handler_disconnect(m_pWidget, m_nFocusOutSignalId);
#endif
}
if (m_nSizeAllocateSignalId)
g_signal_handler_disconnect(m_pWidget, m_nSizeAllocateSignalId);
do_set_background(COL_AUTO);
DisconnectMouseEvents();
if (m_bTakeOwnership)
{
#if !GTK_CHECK_VERSION(4, 0, 0)
gtk_widget_destroy(m_pWidget);
#else
gtk_window_destroy(GTK_WINDOW(m_pWidget));
#endif
}
else
g_object_unref(m_pWidget);
}
virtual void disable_notify_events()
{
if (m_nFocusInSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
g_signal_handler_block(get_focus_controller(), m_nFocusInSignalId);
#else
g_signal_handler_block(m_pWidget, m_nFocusInSignalId);
#endif
}
if (m_nMnemonicActivateSignalId)
g_signal_handler_block(m_pWidget, m_nMnemonicActivateSignalId);
if (m_nFocusOutSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
g_signal_handler_block(get_focus_controller(), m_nFocusOutSignalId);
#else
g_signal_handler_block(m_pWidget, m_nFocusOutSignalId);
#endif
}
if (m_nSizeAllocateSignalId)
g_signal_handler_block(m_pWidget, m_nSizeAllocateSignalId);
}
virtual void enable_notify_events()
{
if (m_nSizeAllocateSignalId)
g_signal_handler_unblock(m_pWidget, m_nSizeAllocateSignalId);
if (m_nFocusOutSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
g_signal_handler_unblock(get_focus_controller(), m_nFocusOutSignalId);
#else
g_signal_handler_unblock(m_pWidget, m_nFocusOutSignalId);
#endif
}
if (m_nMnemonicActivateSignalId)
g_signal_handler_unblock(m_pWidget, m_nMnemonicActivateSignalId);
if (m_nFocusInSignalId)
{
#if GTK_CHECK_VERSION(4, 0, 0)
g_signal_handler_unblock(get_focus_controller(), m_nFocusInSignalId);
#else
g_signal_handler_unblock(m_pWidget, m_nFocusInSignalId);
#endif
}
}
virtual void help_hierarchy_foreach(const std::function<bool(const OUString&)>& func) override;
virtual OUString strip_mnemonic(const OUString &rLabel) const override
{
return rLabel.replaceFirst("_", "");
}
virtual OUString escape_ui_str(const OUString &rLabel) const override
{
return rLabel.replaceAll("_", "__");
}
virtual VclPtr<VirtualDevice> create_virtual_device() const override
{
// create with no separate alpha layer like everything sane does
auto xRet = VclPtr<VirtualDevice>::Create();
xRet->SetBackground(COL_TRANSPARENT);
return xRet;
}
virtual void draw(OutputDevice& rOutput, const Point& rPos, const Size& rPixelSize) override
{
// detect if we have to manually setup its size
bool bAlreadyRealized = gtk_widget_get_realized(m_pWidget);
// has to be visible for draw to work
bool bAlreadyVisible = gtk_widget_get_visible(m_pWidget);
// has to be mapped for draw to work
bool bAlreadyMapped = gtk_widget_get_mapped(m_pWidget);
if (!bAlreadyRealized)
{
#if !GTK_CHECK_VERSION(4, 0, 0)
/*
tdf#141633 The "sample db" example (Mockup.odb) has multiline
entries used in its "Journal Entry" column. Those are painted by
taking snapshots of a never-really-shown textview widget.
Without this style_updated then the textview is always drawn
using its original default font size and changing the page zoom
has no effect on the size of text in the "Journal Entry" column.
*/
update_style(m_pWidget, nullptr);
#endif
gtk_widget_realize(m_pWidget);
}
if (!bAlreadyVisible)
gtk_widget_set_visible(m_pWidget, true);
if (!bAlreadyMapped)
gtk_widget_map(m_pWidget);
assert(gtk_widget_is_drawable(m_pWidget)); // all that should result in this holding
// turn off animations, otherwise we get a frame of an animation sequence
gboolean bAnimations;
GtkSettings* pSettings = gtk_widget_get_settings(m_pWidget);
g_object_get(pSettings, "gtk-enable-animations", &bAnimations, nullptr);
if (bAnimations)
g_object_set(pSettings, "gtk-enable-animations", false, nullptr);
Size aSize(rPixelSize);
GtkAllocation aOrigAllocation;
gtk_widget_get_allocation(m_pWidget, &aOrigAllocation);
GtkAllocation aNewAllocation {aOrigAllocation.x,
aOrigAllocation.y,
static_cast<int>(aSize.Width()),
static_cast<int>(aSize.Height()) };
#if !GTK_CHECK_VERSION(4, 0, 0)
gtk_widget_size_allocate(m_pWidget, &aNewAllocation);
#else
gtk_widget_size_allocate(m_pWidget, &aNewAllocation, 0);
#endif
#if !GTK_CHECK_VERSION(4, 0, 0)
if (GTK_IS_CONTAINER(m_pWidget))
gtk_container_resize_children(GTK_CONTAINER(m_pWidget));
#endif
VclPtr<VirtualDevice> xOutput(VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA));
xOutput->SetOutputSizePixel(aSize);
switch (rOutput.GetOutDevType())
{
case OUTDEV_WINDOW:
case OUTDEV_VIRDEV:
xOutput->DrawOutDev(Point(), aSize, rPos, aSize, rOutput);
break;
case OUTDEV_PRINTER:
case OUTDEV_PDF:
xOutput->SetBackground(rOutput.GetBackground());
xOutput->Erase();
break;
}
cairo_surface_t* pSurface = get_underlying_cairo_surface(*xOutput);
cairo_t* cr = cairo_create(pSurface);
#if !GTK_CHECK_VERSION(4, 0, 0)
gtk_widget_draw(m_pWidget, cr);
#else
GtkSnapshot* pSnapshot = gtk_snapshot_new();
GtkWidgetClass* pWidgetClass = GTK_WIDGET_GET_CLASS(m_pWidget);
pWidgetClass->snapshot(m_pWidget, pSnapshot);
GskRenderNode* pNode = gtk_snapshot_free_to_node(pSnapshot);
gsk_render_node_draw(pNode, cr);
gsk_render_node_unref(pNode);
#endif
cairo_destroy(cr);
#if !GTK_CHECK_VERSION(4, 0, 0)
gtk_widget_set_allocation(m_pWidget, &aOrigAllocation);
gtk_widget_size_allocate(m_pWidget, &aOrigAllocation);
#else
gtk_widget_size_allocate(m_pWidget, &aOrigAllocation, 0);
#endif
switch (rOutput.GetOutDevType())
{
case OUTDEV_WINDOW:
case OUTDEV_VIRDEV:
rOutput.DrawOutDev(rPos, aSize, Point(), aSize, *xOutput);
break;
case OUTDEV_PRINTER:
case OUTDEV_PDF:
rOutput.DrawBitmapEx(rPos, xOutput->GetBitmapEx(Point(), aSize));
break;
}
if (bAnimations)
g_object_set(pSettings, "gtk-enable-animations", true, nullptr);
if (!bAlreadyMapped)
gtk_widget_unmap(m_pWidget);
if (!bAlreadyVisible)
gtk_widget_set_visible(m_pWidget, false);
if (!bAlreadyRealized)
gtk_widget_unrealize(m_pWidget);
}
};
}
IMPL_LINK(GtkInstanceWidget, SettingsChangedHdl, VclWindowEvent&, rEvent, void)
{
if (rEvent.GetId() != VclEventId::WindowDataChanged)
return;
DataChangedEvent* pData = static_cast<DataChangedEvent*>(rEvent.GetData());
if (pData->GetType() == DataChangedEventType::SETTINGS)
signal_style_updated();
}
#if !GTK_CHECK_VERSION(4, 0, 0)
IMPL_LINK(GtkInstanceWidget, async_drag_cancel, void*, arg, void)
{
m_pDragCancelEvent = nullptr;
GdkDragContext* context = static_cast<GdkDragContext*>(arg);
// tdf#132477 simply calling gtk_drag_cancel on the treeview dnd under X
// doesn't seem to work as hoped for (though under wayland all is well).
// Under X the next (allowed) drag effort doesn't work to drop anything,
// but a then repeated attempt does.
// emitting cancel to get gtk to cancel the drag for us does work as hoped for.
g_signal_emit_by_name(context, "cancel", 0, GDK_DRAG_CANCEL_USER_CANCELLED);
g_object_unref(context);
}
#endif
namespace
{
OString MapToGtkAccelerator(const OUString &rStr)
{
return OUStringToOString(rStr.replaceFirst("~", "_"), RTL_TEXTENCODING_UTF8);
}
OUString get_label(GtkLabel* pLabel)
{
const gchar* pStr = gtk_label_get_label(pLabel);
return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
}
void set_label(GtkLabel* pLabel, const OUString& rText)
{
gtk_label_set_label(pLabel, MapToGtkAccelerator(rText).getStr());
}
#if GTK_CHECK_VERSION(4, 0, 0)
GtkWidget* find_label_widget(GtkWidget* pContainer)
{
GtkWidget* pLabel = nullptr;
for (GtkWidget* pChild = gtk_widget_get_first_child(pContainer);
pChild; pChild = gtk_widget_get_next_sibling(pChild))
{
if (GTK_IS_LABEL(pChild))
{
pLabel = pChild;
break;
}
else
{
pLabel = find_label_widget(pChild);
if (pLabel)
break;
}
}
return pLabel;
}
GtkWidget* find_image_widget(GtkWidget* pContainer)
{
GtkWidget* pImage = nullptr;
for (GtkWidget* pChild = gtk_widget_get_first_child(pContainer);
pChild; pChild = gtk_widget_get_next_sibling(pChild))
{
if (GTK_IS_IMAGE(pChild))
{
pImage = pChild;
break;
}
else
{
pImage = find_image_widget(pChild);
if (pImage)
break;
}
}
return pImage;
}
#else
GtkWidget* find_label_widget(GtkContainer* pContainer)
{
GList* pChildren = gtk_container_get_children(pContainer);
GtkWidget* pChild = nullptr;
for (GList* pCandidate = pChildren; pCandidate; pCandidate = pCandidate->next)
{
if (GTK_IS_LABEL(pCandidate->data))
{
pChild = GTK_WIDGET(pCandidate->data);
break;
}
else if (GTK_IS_CONTAINER(pCandidate->data))
{
pChild = find_label_widget(GTK_CONTAINER(pCandidate->data));
if (pChild)
break;
}
}
g_list_free(pChildren);
return pChild;
}
GtkWidget* find_image_widget(GtkContainer* pContainer)
{
GList* pChildren = gtk_container_get_children(pContainer);
GtkWidget* pChild = nullptr;
for (GList* pCandidate = pChildren; pCandidate; pCandidate = pCandidate->next)
{
if (GTK_IS_IMAGE(pCandidate->data))
{
pChild = GTK_WIDGET(pCandidate->data);
break;
}
else if (GTK_IS_CONTAINER(pCandidate->data))
{
pChild = find_image_widget(GTK_CONTAINER(pCandidate->data));
if (pChild)
break;
}
}
g_list_free(pChildren);
return pChild;
}
#endif
GtkLabel* get_label_widget(GtkWidget* pButton)
{
#if !GTK_CHECK_VERSION(4, 0, 0)
GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pButton));
if (GTK_IS_CONTAINER(pChild))
pChild = find_label_widget(GTK_CONTAINER(pChild));
else if (!GTK_IS_LABEL(pChild))
pChild = nullptr;
return GTK_LABEL(pChild);
#else
return GTK_LABEL(find_label_widget(pButton));
#endif
}
GtkImage* get_image_widget(GtkWidget *pButton)
{
#if !GTK_CHECK_VERSION(4, 0, 0)
GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pButton));
if (GTK_IS_CONTAINER(pChild))
pChild = find_image_widget(GTK_CONTAINER(pChild));
else if (!GTK_IS_IMAGE(pChild))
pChild = nullptr;
return GTK_IMAGE(pChild);
#else
return GTK_IMAGE(find_image_widget(pButton));
#endif
}
OUString button_get_label(GtkButton* pButton)
{
if (GtkLabel* pLabel = get_label_widget(GTK_WIDGET(pButton)))
return ::get_label(pLabel);
const gchar* pStr = gtk_button_get_label(pButton);
return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
}
void button_set_label(GtkButton* pButton, const OUString& rText)
{
if (GtkLabel* pLabel = get_label_widget(GTK_WIDGET(pButton)))
{
::set_label(pLabel, rText);
gtk_widget_set_visible(GTK_WIDGET(pLabel), true);
return;
}
gtk_button_set_label(pButton, MapToGtkAccelerator(rText).getStr());
}
#if GTK_CHECK_VERSION(4, 0, 0)
OUString get_label(GtkCheckButton* pButton)
{
const gchar* pStr = gtk_check_button_get_label(pButton);
return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
}
void set_label(GtkCheckButton* pButton, const OUString& rText)
{
gtk_check_button_set_label(pButton, MapToGtkAccelerator(rText).getStr());
}
#endif
OUString get_title(GtkWindow* pWindow)
{
const gchar* pStr = gtk_window_get_title(pWindow);
return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
}
void set_title(GtkWindow* pWindow, std::u16string_view rTitle)
{
gtk_window_set_title(pWindow, OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
}
OUString get_primary_text(GtkMessageDialog* pMessageDialog)
{
g_autofree char* pText = nullptr;
g_object_get(G_OBJECT(pMessageDialog), "text", &pText, nullptr);
return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
}
void set_primary_text(GtkMessageDialog* pMessageDialog, std::u16string_view rText)
{
g_object_set(G_OBJECT(pMessageDialog), "text",
OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
nullptr);
}
void set_secondary_text(GtkMessageDialog* pMessageDialog, std::u16string_view rText)
{
g_object_set(G_OBJECT(pMessageDialog), "secondary-text",
OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
nullptr);
}
OUString get_secondary_text(GtkMessageDialog* pMessageDialog)
{
g_autofree char* pText = nullptr;
g_object_get(G_OBJECT(pMessageDialog), "secondary-text", &pText, nullptr);
return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
}
}
namespace
{
GdkPixbuf* load_icon_from_stream(SvMemoryStream& rStream)
{
auto nLength = rStream.TellEnd();
if (!nLength)
return nullptr;
const guchar* pData = static_cast<const guchar*>(rStream.GetData());
assert((*pData == 137 || *pData == '<') && "if we want to support more than png or svg this function must change");
// if we know the image type, it's a little faster to hand the type over and skip the type detection.
GdkPixbufLoader *pixbuf_loader = gdk_pixbuf_loader_new_with_type(*pData == 137 ? "png" : "svg", nullptr);
gdk_pixbuf_loader_write(pixbuf_loader, pData, nLength, nullptr);
gdk_pixbuf_loader_close(pixbuf_loader, nullptr);
GdkPixbuf* pixbuf = gdk_pixbuf_loader_get_pixbuf(pixbuf_loader);
if (pixbuf)
g_object_ref(pixbuf);
g_object_unref(pixbuf_loader);
return pixbuf;
}
std::shared_ptr<SvMemoryStream> get_icon_stream_by_name_theme_lang(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
{
return ImageTree::get().getImageStream(rIconName, rIconTheme, rUILang);
}
GdkPixbuf* load_icon_by_name_theme_lang(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
{
auto xMemStm = get_icon_stream_by_name_theme_lang(rIconName, rIconTheme, rUILang);
if (!xMemStm)
return nullptr;
return load_icon_from_stream(*xMemStm);
}
std::unique_ptr<utl::TempFileNamed> get_icon_stream_as_file_by_name_theme_lang(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
{
uno::Reference<io::XInputStream> xInputStream = ImageTree::get().getImageXInputStream(rIconName, rIconTheme, rUILang);
if (!xInputStream)
return nullptr;
std::unique_ptr<utl::TempFileNamed> xRet(new utl::TempFileNamed);
xRet->EnableKillingFile(true);
SvStream* pStream = xRet->GetStream(StreamMode::WRITE);
for (;;)
{
const sal_Int32 nSize(2048);
uno::Sequence<sal_Int8> aData(nSize);
sal_Int32 nRead = xInputStream->readBytes(aData, nSize);
pStream->WriteBytes(aData.getConstArray(), nRead);
if (nRead < nSize)
break;
}
xRet->CloseStream();
return xRet;
}
std::unique_ptr<utl::TempFileNamed> get_icon_stream_as_file(const OUString& rIconName)
{
OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
OUString sUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
return get_icon_stream_as_file_by_name_theme_lang(rIconName, sIconTheme, sUILang);
}
}
GdkPixbuf* load_icon_by_name(const OUString& rIconName)
{
OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
OUString sUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
return load_icon_by_name_theme_lang(rIconName, sIconTheme, sUILang);
}
namespace
{
Image mirrorImage(const Image& rImage)
{
BitmapEx aMirrBitmapEx(rImage.GetBitmapEx());
aMirrBitmapEx.Mirror(BmpMirrorFlags::Horizontal);
return Image(aMirrBitmapEx);
}
GdkPixbuf* getPixbuf(const css::uno::Reference<css::graphic::XGraphic>& rImage)
{
Image aImage(rImage);
const OUString& sStock(aImage.GetStock());
if (!sStock.isEmpty())
return load_icon_by_name(sStock);
SvMemoryStream aMemStm;
// We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed.
css::uno::Sequence<css::beans::PropertyValue> aFilterData{ comphelper::makePropertyValue(
u"Compression"_ustr, sal_Int32(1)) };
auto aBitmapEx = aImage.GetBitmapEx();
vcl::PngImageWriter aWriter(aMemStm);
aWriter.setParameters(aFilterData);
aWriter.write(aBitmapEx);
return load_icon_from_stream(aMemStm);
}
// tdf#151898 as far as I can see only gtk_image_new_from_file (or gtk_image_new_from_resource) can support the use of a
// scalable input format to create a hidpi GtkImage, rather than an upscaled lodpi one so forced to go via a file here
std::unique_ptr<utl::TempFileNamed> getImageFile(const css::uno::Reference<css::graphic::XGraphic>& rImage, bool bMirror = false)
{
Image aImage(rImage);
if (bMirror)
aImage = mirrorImage(aImage);
OUString sStock(aImage.GetStock());
if (!sStock.isEmpty())
return get_icon_stream_as_file(sStock);
std::unique_ptr<utl::TempFileNamed> xRet(new utl::TempFileNamed);
xRet->EnableKillingFile(true);
SvStream* pStream = xRet->GetStream(StreamMode::WRITE);
// We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed.
css::uno::Sequence<css::beans::PropertyValue> aFilterData{ comphelper::makePropertyValue(
u"Compression"_ustr, sal_Int32(1)) };
auto aBitmapEx = aImage.GetBitmapEx();
vcl::PngImageWriter aWriter(*pStream);
aWriter.setParameters(aFilterData);
aWriter.write(aBitmapEx);
xRet->CloseStream();
return xRet;
}
GdkPixbuf* getPixbuf(const VirtualDevice& rDevice)
{
Size aSize(rDevice.GetOutputSizePixel());
cairo_surface_t* orig_surface = get_underlying_cairo_surface(rDevice);
double fXScale, fYScale;
dl_cairo_surface_get_device_scale(orig_surface, &fXScale, &fYScale);
cairo_surface_t* surface;
if (fXScale != 1.0 || fYScale != -1)
{
surface = cairo_surface_create_similar_image(orig_surface,
CAIRO_FORMAT_ARGB32,
aSize.Width(),
aSize.Height());
cairo_t* cr = cairo_create(surface);
cairo_set_source_surface(cr, orig_surface, 0, 0);
cairo_paint(cr);
cairo_destroy(cr);
}
else
surface = orig_surface;
GdkPixbuf* pRet = gdk_pixbuf_get_from_surface(surface, 0, 0, aSize.Width(), aSize.Height());
if (surface != orig_surface)
cairo_surface_destroy(surface);
return pRet;
}
GdkPixbuf* getPixbuf(const BitmapEx& rBitmap)
{
ScopedVclPtr<VirtualDevice> pVDevice(VclPtr<VirtualDevice>::Create());
pVDevice->SetOutputSizePixel(rBitmap.GetSizePixel());
pVDevice->DrawBitmapEx(Point(0,0), rBitmap);
return getPixbuf(*pVDevice);
}
#if GTK_CHECK_VERSION(4, 0, 0)
cairo_surface_t* render_paintable_to_surface(GdkPaintable *paintable, int nWidth, int nHeight)
{
cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nWidth, nHeight);
GtkSnapshot* snapshot = gtk_snapshot_new();
gdk_paintable_snapshot(paintable, snapshot, nWidth, nHeight);
GskRenderNode* node = gtk_snapshot_free_to_node(snapshot);
cairo_t* cr = cairo_create(surface);
gsk_render_node_draw(node, cr);
cairo_destroy(cr);
gsk_render_node_unref(node);
return surface;
}
#endif
GdkPixbuf* getPixbuf(const OUString& rIconName)
{
if (rIconName.isEmpty())
return nullptr;
GdkPixbuf* pixbuf = nullptr;
if (rIconName.lastIndexOf('.') != rIconName.getLength() - 4)
{
assert((rIconName== "dialog-warning" || rIconName== "dialog-error" || rIconName== "dialog-information") &&
"unknown stock image");
#if GTK_CHECK_VERSION(4, 0, 0)
GtkIconTheme *icon_theme = gtk_icon_theme_get_for_display(gdk_display_get_default());
GtkIconPaintable *icon = gtk_icon_theme_lookup_icon(icon_theme,
OUStringToOString(rIconName, RTL_TEXTENCODING_UTF8).getStr(),
nullptr,
16,
1,
AllSettings::GetLayoutRTL() ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR,
static_cast<GtkIconLookupFlags>(0));
GdkPaintable* paintable = GDK_PAINTABLE(icon);
int nWidth = gdk_paintable_get_intrinsic_width(paintable);
int nHeight = gdk_paintable_get_intrinsic_height(paintable);
cairo_surface_t* surface = render_paintable_to_surface(paintable, nWidth, nHeight);
pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, nWidth, nHeight);
cairo_surface_destroy(surface);
#else
GtkIconTheme *icon_theme = gtk_icon_theme_get_default();
GError *error = nullptr;
pixbuf = gtk_icon_theme_load_icon(icon_theme, OUStringToOString(rIconName, RTL_TEXTENCODING_UTF8).getStr(),
16, GTK_ICON_LOOKUP_USE_BUILTIN, &error);
#endif
}
else
{
const AllSettings& rSettings = Application::GetSettings();
pixbuf = load_icon_by_name_theme_lang(rIconName,
rSettings.GetStyleSettings().DetermineIconTheme(),
rSettings.GetUILanguageTag().getBcp47());
}
return pixbuf;
}
}
namespace
{
#if GTK_CHECK_VERSION(4, 0, 0)
SurfacePaintable* paintable_new_from_virtual_device(const VirtualDevice& rImageSurface)
{
cairo_surface_t* surface = get_underlying_cairo_surface(rImageSurface);
Size aSize(rImageSurface.GetOutputSizePixel());
cairo_surface_t* target = cairo_surface_create_similar(surface,
cairo_surface_get_content(surface),
aSize.Width(),
aSize.Height());
cairo_t* cr = cairo_create(target);
cairo_set_source_surface(cr, surface, 0, 0);
cairo_paint(cr);
cairo_destroy(cr);
SurfacePaintable* pPaintable = SURFACE_PAINTABLE(g_object_new(surface_paintable_get_type(), nullptr));
surface_paintable_set_source(pPaintable, target, aSize.Width(), aSize.Height());
return pPaintable;
}
GtkWidget* image_new_from_virtual_device(const VirtualDevice& rImageSurface)
{
SurfacePaintable* paintable = paintable_new_from_virtual_device(rImageSurface);
return gtk_image_new_from_paintable(GDK_PAINTABLE(paintable));
}
GtkWidget* picture_new_from_virtual_device(const VirtualDevice& rImageSurface)
{
SurfacePaintable* paintable = paintable_new_from_virtual_device(rImageSurface);
return gtk_picture_new_for_paintable(GDK_PAINTABLE(paintable));
}
#else
GtkWidget* image_new_from_virtual_device(const VirtualDevice& rImageSurface)
{
GtkWidget* pImage = nullptr;
cairo_surface_t* surface = get_underlying_cairo_surface(rImageSurface);
Size aSize(rImageSurface.GetOutputSizePixel());
cairo_surface_t* target = cairo_surface_create_similar(surface,
cairo_surface_get_content(surface),
aSize.Width(),
aSize.Height());
cairo_t* cr = cairo_create(target);
cairo_set_source_surface(cr, surface, 0, 0);
cairo_paint(cr);
cairo_destroy(cr);
pImage = gtk_image_new_from_surface(target);
cairo_surface_destroy(target);
return pImage;
}
#endif
GtkWidget* image_new_from_xgraphic(const css::uno::Reference<css::graphic::XGraphic>& rIcon, bool bMirror)
{
GtkWidget* pImage = nullptr;
if (auto xTempFile = getImageFile(rIcon, bMirror))
pImage = gtk_image_new_from_file(OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
return pImage;
}
GtkWidget* image_new_from_icon_name(const OUString& rIconName)
{
GtkWidget* pImage = nullptr;
if (auto xTempFile = get_icon_stream_as_file(rIconName))
pImage = gtk_image_new_from_file(OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
return pImage;
}
GtkWidget* image_new_from_icon_name_theme_lang(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
{
GtkWidget* pImage = nullptr;
if (auto xTempFile = get_icon_stream_as_file_by_name_theme_lang(rIconName, rIconTheme, rUILang))
pImage = gtk_image_new_from_file(OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
return pImage;
}
void image_set_from_icon_name(GtkImage* pImage, const OUString& rIconName)
{
if (auto xTempFile = get_icon_stream_as_file(rIconName))
gtk_image_set_from_file(pImage, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
else
gtk_image_set_from_pixbuf(pImage, nullptr);
}
void image_set_from_icon_name_theme_lang(GtkImage* pImage, const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
{
if (auto xTempFile = get_icon_stream_as_file_by_name_theme_lang(rIconName, rIconTheme, rUILang))
gtk_image_set_from_file(pImage, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
else
gtk_image_set_from_pixbuf(pImage, nullptr);
}
void image_set_from_virtual_device(GtkImage* pImage, const VirtualDevice* pDevice)
{
#if GTK_CHECK_VERSION(4, 0, 0)
gtk_image_set_from_paintable(pImage, pDevice ? GDK_PAINTABLE(paintable_new_from_virtual_device(*pDevice)) : nullptr);
#else
gtk_image_set_from_surface(pImage, pDevice ? get_underlying_cairo_surface(*pDevice) : nullptr);
#endif
}
void image_set_from_xgraphic(GtkImage* pImage, const css::uno::Reference<css::graphic::XGraphic>& rImage)
{
if (auto xTempFile = getImageFile(rImage, false))
gtk_image_set_from_file(pImage, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
else
gtk_image_set_from_pixbuf(pImage, nullptr);
}
#if GTK_CHECK_VERSION(4, 0, 0)
void picture_set_from_icon_name(GtkPicture* pPicture, const OUString& rIconName)
{
if (auto xTempFile = get_icon_stream_as_file(rIconName))
gtk_picture_set_filename(pPicture, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
else
gtk_picture_set_pixbuf(pPicture, nullptr);
}
void picture_set_from_icon_name_theme_lang(GtkPicture* pPicture, const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
{
if (auto xTempFile = get_icon_stream_as_file_by_name_theme_lang(rIconName, rIconTheme, rUILang))
gtk_picture_set_filename(pPicture, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
else
gtk_picture_set_pixbuf(pPicture, nullptr);
}
void picture_set_from_virtual_device(GtkPicture* pPicture, const VirtualDevice* pDevice)
{
if (!pDevice)
gtk_picture_set_paintable(pPicture, nullptr);
else
gtk_picture_set_paintable(pPicture, GDK_PAINTABLE(paintable_new_from_virtual_device(*pDevice)));
}
void picture_set_from_xgraphic(GtkPicture* pPicture, const css::uno::Reference<css::graphic::XGraphic>& rPicture)
{
if (auto xTempFile = getImageFile(rPicture, false))
gtk_picture_set_filename(pPicture, OUStringToOString(xTempFile->GetFileName(), osl_getThreadTextEncoding()).getStr());
else
gtk_picture_set_pixbuf(pPicture, nullptr);
}
#endif
void button_set_from_icon_name(GtkButton* pButton, const OUString& rIconName)
{
if (GtkImage* pImage = get_image_widget(GTK_WIDGET(pButton)))
{
::image_set_from_icon_name(pImage, rIconName);
gtk_widget_set_visible(GTK_WIDGET(pImage), true);
return;
}
GtkWidget* pImage = image_new_from_icon_name(rIconName);
#if GTK_CHECK_VERSION(4, 0, 0)
gtk_button_set_child(pButton, pImage);
#else
gtk_button_set_image(pButton, pImage);
#endif
}
void button_set_image(GtkButton* pButton, const VirtualDevice* pDevice)
{
#if !GTK_CHECK_VERSION(4, 0, 0)
gtk_button_set_always_show_image(pButton, true);
gtk_button_set_image_position(pButton, GTK_POS_LEFT);
#endif
GtkWidget* pImage = pDevice ? image_new_from_virtual_device(*pDevice) : nullptr;
#if GTK_CHECK_VERSION(4, 0, 0)
gtk_button_set_child(pButton, pImage);
#else
gtk_button_set_image(pButton, pImage);
#endif
}
void button_set_image(GtkButton* pButton, const css::uno::Reference<css::graphic::XGraphic>& rImage)
{
if (GtkImage* pImage = get_image_widget(GTK_WIDGET(pButton)))
{
::image_set_from_xgraphic(pImage, rImage);
gtk_widget_set_visible(GTK_WIDGET(pImage), true);
return;
}
GtkWidget* pImage = image_new_from_xgraphic(rImage, false);
#if GTK_CHECK_VERSION(4, 0, 0)
gtk_button_set_child(pButton, pImage);
#else
gtk_button_set_image(pButton, pImage);
#endif
}
class MenuHelper
{
protected:
#if !GTK_CHECK_VERSION(4, 0, 0)
GtkMenu* m_pMenu;
std::map<OUString, GtkMenuItem*> m_aMap;
#else
GtkPopoverMenu* m_pMenu;
o3tl::sorted_vector<OString> m_aInsertedActions; // must outlive m_aActionEntries
std::map<OUString, OString> m_aIdToAction;
std::set<OUString> m_aHiddenIds;
std::vector<GActionEntry> m_aActionEntries;
GActionGroup* m_pActionGroup;
// move 'invisible' entries to m_pHiddenActionGroup
GActionGroup* m_pHiddenActionGroup;
#endif
bool m_bTakeOwnership;
private:
virtual void signal_item_activate(const OUString& rIdent) = 0;
#if !GTK_CHECK_VERSION(4, 0, 0)
static void collect(GtkWidget* pItem, gpointer widget)
{
GtkMenuItem* pMenuItem = GTK_MENU_ITEM(pItem);
if (GtkWidget* pSubMenu = gtk_menu_item_get_submenu(pMenuItem))
gtk_container_foreach(GTK_CONTAINER(pSubMenu), collect, widget);
MenuHelper* pThis = static_cast<MenuHelper*>(widget);
pThis->add_to_map(pMenuItem);
}
static void signalActivate(GtkMenuItem* pItem, gpointer widget)
{
// ignore "activate" signal for non-leaf items resulting in the submenu to be
// shown, contrary to a "normal" menu item getting activated to trigger its action
if (gtk_menu_item_get_submenu(pItem))
return;
MenuHelper* pThis = static_cast<MenuHelper*>(widget);
SolarMutexGuard aGuard;
pThis->signal_item_activate(::get_buildable_id(GTK_BUILDABLE(pItem)));
}
#else
static std::pair<GMenuModel*, int> find_id(GMenuModel* pMenuModel, const OUString& rId)
{
for (int i = 0, nCount = g_menu_model_get_n_items(pMenuModel); i < nCount; ++i)
{
OUString sTarget;
char *id;
if (g_menu_model_get_item_attribute(pMenuModel, i, "target", "s", &id))
{
sTarget = OStringToOUString(id, RTL_TEXTENCODING_UTF8);
g_free(id);
}
if (sTarget == rId)
return std::make_pair(pMenuModel, i);
if (GMenuModel* pSectionModel = g_menu_model_get_item_link(pMenuModel, i, G_MENU_LINK_SECTION))
{
std::pair<GMenuModel*, int> aRet = find_id(pSectionModel, rId);
if (aRet.first)
return aRet;
}
if (GMenuModel* pSubMenuModel = g_menu_model_get_item_link(pMenuModel, i, G_MENU_LINK_SUBMENU))
{
std::pair<GMenuModel*, int> aRet = find_id(pSubMenuModel, rId);
if (aRet.first)
return aRet;
}
}
return std::make_pair(nullptr, -1);
}
void clear_actions()
{
for (const auto& rAction : m_aActionEntries)
{
g_action_map_remove_action(G_ACTION_MAP(m_pActionGroup), rAction.name);
g_action_map_remove_action(G_ACTION_MAP(m_pHiddenActionGroup), rAction.name);
}
m_aActionEntries.clear();
m_aInsertedActions.clear();
m_aIdToAction.clear();
}
static void action_activated(GSimpleAction*, GVariant* pParameter, gpointer widget)
{
gsize nLength(0);
const gchar* pStr = g_variant_get_string(pParameter, &nLength);
OUString aStr(pStr, nLength, RTL_TEXTENCODING_UTF8);
MenuHelper* pThis = static_cast<MenuHelper*>(widget);
SolarMutexGuard aGuard;
pThis->signal_item_activate(aStr);
}
#endif
public:
#if !GTK_CHECK_VERSION(4, 0, 0)
MenuHelper(GtkMenu* pMenu, bool bTakeOwnership)
#else
MenuHelper(GtkPopoverMenu* pMenu, bool bTakeOwnership)
#endif
: m_pMenu(pMenu)
, m_bTakeOwnership(bTakeOwnership)
{
#if !GTK_CHECK_VERSION(4, 0, 0)
if (!m_pMenu)
return;
gtk_container_foreach(GTK_CONTAINER(m_pMenu), collect, this);
#else
m_pActionGroup = G_ACTION_GROUP(g_simple_action_group_new());
m_pHiddenActionGroup = G_ACTION_GROUP(g_simple_action_group_new());
#endif
}
#if !GTK_CHECK_VERSION(4, 0, 0)
void add_to_map(GtkMenuItem* pMenuItem)
{
OUString id = ::get_buildable_id(GTK_BUILDABLE(pMenuItem));
m_aMap[id] = pMenuItem;
g_signal_connect(pMenuItem, "activate", G_CALLBACK(signalActivate), this);
}
void remove_from_map(GtkMenuItem* pMenuItem)
{
OUString id = ::get_buildable_id(GTK_BUILDABLE(pMenuItem));
auto iter = m_aMap.find(id);
g_signal_handlers_disconnect_by_data(pMenuItem, this);
m_aMap.erase(iter);
}
void disable_item_notify_events()
{
for (auto& a : m_aMap)
g_signal_handlers_block_by_func(a.second, reinterpret_cast<void*>(signalActivate), this);
}
void enable_item_notify_events()
{
for (auto& a : m_aMap)
g_signal_handlers_unblock_by_func(a.second, reinterpret_cast<void*>(signalActivate), this);
}
#endif
#if GTK_CHECK_VERSION(4, 0, 0)
/* LibreOffice likes to think of separators between menu entries, while gtk likes
to think of sections of menus with separators drawn between sections. We always
arrange to have a section in a menu so toplevel menumodels comprise of
sections and we move entries between sections on pretending to insert separators */
static std::pair<GMenuModel*, int> get_section_and_pos_for(GMenuModel* pMenuModel, int pos)
{
int nSectionCount = g_menu_model_get_n_items(pMenuModel);
assert(nSectionCount);
GMenuModel* pSectionModel = nullptr;
int nIndexWithinSection = 0;
int nExternalPos = 0;
for (int nSection = 0; nSection < nSectionCount; ++nSection)
{
pSectionModel = g_menu_model_get_item_link(pMenuModel, nSection, G_MENU_LINK_SECTION);
assert(pSectionModel);
int nCount = g_menu_model_get_n_items(pSectionModel);
for (nIndexWithinSection = 0; nIndexWithinSection < nCount; ++nIndexWithinSection)
{
if (pos == nExternalPos)
break;
++nExternalPos;
}
++nExternalPos;
}
return std::make_pair(pSectionModel, nIndexWithinSection);
}
static int count_immediate_children(GMenuModel* pMenuModel)
{
int nSectionCount = g_menu_model_get_n_items(pMenuModel);
assert(nSectionCount);
int nExternalPos = 0;
for (int nSection = 0; nSection < nSectionCount; ++nSection)
{
GMenuModel* pSectionModel = g_menu_model_get_item_link(pMenuModel, nSection, G_MENU_LINK_SECTION);
assert(pSectionModel);
int nCount = g_menu_model_get_n_items(pSectionModel);
for (int nIndexWithinSection = 0; nIndexWithinSection < nCount; ++nIndexWithinSection)
{
++nExternalPos;
}
++nExternalPos;
}
return nExternalPos - 1;
}
#endif
#if GTK_CHECK_VERSION(4, 0, 0)
void process_menu_model(GMenuModel* pMenuModel)
{
for (int i = 0, nCount = g_menu_model_get_n_items(pMenuModel); i < nCount; ++i)
{
OString sAction;
OUString sTarget;
char *id;
if (g_menu_model_get_item_attribute(pMenuModel, i, "action", "s", &id))
{
assert(o3tl::starts_with(id, "menu."));
sAction = OString(id + 5);
auto res = m_aInsertedActions.insert(sAction);
if (res.second)
{
// the const char* arg isn't copied by anything so it must continue to exist for the life time of
// the action group
if (sAction.startsWith("radio."))
m_aActionEntries.push_back({res.first->getStr(), action_activated, "s", "'none'", nullptr, {}});
else
m_aActionEntries.push_back({res.first->getStr(), action_activated, "s", nullptr, nullptr, {}});
}
g_free(id);
}
if (g_menu_model_get_item_attribute(pMenuModel, i, "target", "s", &id))
{
sTarget = OStringToOUString(id, RTL_TEXTENCODING_UTF8);
g_free(id);
}
m_aIdToAction[sTarget] = sAction;
if (GMenuModel* pSectionModel = g_menu_model_get_item_link(pMenuModel, i, G_MENU_LINK_SECTION))
process_menu_model(pSectionModel);
if (GMenuModel* pSubMenuModel = g_menu_model_get_item_link(pMenuModel, i, G_MENU_LINK_SUBMENU))
process_menu_model(pSubMenuModel);
}
}
// build an action group for the menu, "action" is the normal menu entry case
// the others are radiogroups
void update_action_group_from_popover_model()
{
clear_actions();
if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
{
process_menu_model(pMenuModel);
}
// move hidden entries to m_pHiddenActionGroup
g_action_map_add_action_entries(G_ACTION_MAP(m_pActionGroup), m_aActionEntries.data(), m_aActionEntries.size(), this);
for (const auto& id : m_aHiddenIds)
{
GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(m_pActionGroup), m_aIdToAction[id].getStr());
g_action_map_add_action(G_ACTION_MAP(m_pHiddenActionGroup), pAction);
g_action_map_remove_action(G_ACTION_MAP(m_pActionGroup), m_aIdToAction[id].getStr());
}
}
#endif
void insert_item(int pos, const OUString& rId, const OUString& rStr,
const OUString* pIconName, const VirtualDevice* pImageSurface,
TriState eCheckRadioFalse)
{
#if !GTK_CHECK_VERSION(4, 0, 0)
GtkWidget* pImage = nullptr;
if (pIconName && !pIconName->isEmpty())
pImage = image_new_from_icon_name(*pIconName);
else if (pImageSurface)
pImage = image_new_from_virtual_device(*pImageSurface);
GtkWidget *pItem;
if (pImage)
{
GtkBox *pBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
GtkWidget *pLabel = gtk_label_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
gtk_label_set_xalign(GTK_LABEL(pLabel), 0.0);
pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new() : gtk_menu_item_new();
gtk_box_pack_start(pBox, pImage, false, true, 0);
gtk_box_pack_start(pBox, pLabel, true, true, 0);
gtk_container_add(GTK_CONTAINER(pItem), GTK_WIDGET(pBox));
gtk_widget_show_all(pItem);
}
else
{
pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr())
: gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(rStr).getStr());
}
if (eCheckRadioFalse == TRISTATE_FALSE)
gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(pItem), true);
::set_buildable_id(GTK_BUILDABLE(pItem), rId);
gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
gtk_widget_set_visible(pItem, true);
add_to_map(GTK_MENU_ITEM(pItem));
if (pos != -1)
gtk_menu_reorder_child(m_pMenu, pItem, pos);
#else
(void)pIconName; (void)pImageSurface;
if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
{
auto aSectionAndPos = get_section_and_pos_for(pMenuModel, pos);
GMenu* pMenu = G_MENU(aSectionAndPos.first);
// action with a target value ... the action name and target value are separated by a double
// colon ... For example: "app.action::target"
OUString sActionAndTarget;
if (eCheckRadioFalse == TRISTATE_INDET)
sActionAndTarget = "menu.normal." + rId + "::" + rId;
else
sActionAndTarget = "menu.radio." + rId + "::" + rId;
g_menu_insert(pMenu, aSectionAndPos.second, MapToGtkAccelerator(rStr).getStr(), sActionAndTarget.toUtf8().getStr());
assert(eCheckRadioFalse == TRISTATE_INDET); // come back to this later
// TODO not redo entire group
update_action_group_from_popover_model();
}
#endif
}
void insert_separator(int pos, const OUString& rId)
{
#if !GTK_CHECK_VERSION(4, 0, 0)
GtkWidget* pItem = gtk_separator_menu_item_new();
::set_buildable_id(GTK_BUILDABLE(pItem), rId);
gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
gtk_widget_set_visible(pItem, true);
add_to_map(GTK_MENU_ITEM(pItem));
if (pos != -1)
gtk_menu_reorder_child(m_pMenu, pItem, pos);
#else
if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
{
auto aSectionAndPos = get_section_and_pos_for(pMenuModel, pos);
for (int nSection = 0, nSectionCount = g_menu_model_get_n_items(pMenuModel); nSection < nSectionCount; ++nSection)
{
GMenuModel* pSectionModel = g_menu_model_get_item_link(pMenuModel, nSection, G_MENU_LINK_SECTION);
assert(pSectionModel);
if (aSectionAndPos.first == pSectionModel)
{
GMenu* pNewSection = g_menu_new();
GMenuItem* pSectionItem = g_menu_item_new_section(nullptr, G_MENU_MODEL(pNewSection));
OUString sActionAndTarget = "menu.separator." + rId + "::" + rId;
g_menu_item_set_detailed_action(pSectionItem, sActionAndTarget.toUtf8().getStr());
g_menu_insert_item(G_MENU(pMenuModel), nSection + 1, pSectionItem);
int nOldSectionCount = g_menu_model_get_n_items(pSectionModel);
for (int i = nOldSectionCount - 1; i >= aSectionAndPos.second; --i)
{
GMenuItem* pMenuItem = g_menu_item_new_from_model(pSectionModel, i);
g_menu_prepend_item(pNewSection, pMenuItem);
g_menu_remove(G_MENU(pSectionModel), i);
g_object_unref(pMenuItem);
}
g_object_unref(pSectionItem);
g_object_unref(pNewSection);
}
}
}
#endif
}
void remove_item(const OUString& rIdent)
{
#if !GTK_CHECK_VERSION(4, 0, 0)
GtkMenuItem* pMenuItem = m_aMap[rIdent];
remove_from_map(pMenuItem);
gtk_widget_destroy(GTK_WIDGET(pMenuItem));
#else
if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
{
std::pair<GMenuModel*, int> aRes = find_id(pMenuModel, rIdent);
if (!aRes.first)
return;
g_menu_remove(G_MENU(aRes.first), aRes.second);
}
#endif
}
void set_item_sensitive(const OUString& rIdent, bool bSensitive)
{
#if GTK_CHECK_VERSION(4, 0, 0)
GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup; | |