summaryrefslogtreecommitdiffstats
path: root/vcl/unx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/unx
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/unx')
-rw-r--r--vcl/unx/generic/app/gendata.cxx54
-rw-r--r--vcl/unx/generic/app/gendisp.cxx69
-rw-r--r--vcl/unx/generic/app/geninst.cxx99
-rw-r--r--vcl/unx/generic/app/gensys.cxx116
-rw-r--r--vcl/unx/generic/app/i18n_cb.cxx494
-rw-r--r--vcl/unx/generic/app/i18n_ic.cxx604
-rw-r--r--vcl/unx/generic/app/i18n_im.cxx410
-rw-r--r--vcl/unx/generic/app/i18n_keysym.cxx358
-rw-r--r--vcl/unx/generic/app/i18n_xkb.cxx107
-rw-r--r--vcl/unx/generic/app/keysymnames.cxx509
-rw-r--r--vcl/unx/generic/app/randrwrapper.cxx181
-rw-r--r--vcl/unx/generic/app/saldata.cxx778
-rw-r--r--vcl/unx/generic/app/saldisp.cxx2865
-rw-r--r--vcl/unx/generic/app/salinst.cxx254
-rw-r--r--vcl/unx/generic/app/saltimer.cxx68
-rw-r--r--vcl/unx/generic/app/sm.cxx856
-rw-r--r--vcl/unx/generic/app/wmadaptor.cxx2215
-rw-r--r--vcl/unx/generic/desktopdetect/desktopdetector.cxx253
-rw-r--r--vcl/unx/generic/dtrans/X11_clipboard.cxx231
-rw-r--r--vcl/unx/generic/dtrans/X11_clipboard.hxx111
-rw-r--r--vcl/unx/generic/dtrans/X11_dndcontext.cxx118
-rw-r--r--vcl/unx/generic/dtrans/X11_dndcontext.hxx80
-rw-r--r--vcl/unx/generic/dtrans/X11_droptarget.cxx177
-rw-r--r--vcl/unx/generic/dtrans/X11_selection.cxx4156
-rw-r--r--vcl/unx/generic/dtrans/X11_selection.hxx496
-rw-r--r--vcl/unx/generic/dtrans/X11_service.cxx88
-rw-r--r--vcl/unx/generic/dtrans/X11_transferable.cxx101
-rw-r--r--vcl/unx/generic/dtrans/X11_transferable.hxx50
-rw-r--r--vcl/unx/generic/dtrans/bmp.cxx786
-rw-r--r--vcl/unx/generic/dtrans/bmp.hxx75
-rw-r--r--vcl/unx/generic/dtrans/config.cxx123
-rw-r--r--vcl/unx/generic/dtrans/copydata_curs.h36
-rw-r--r--vcl/unx/generic/dtrans/copydata_mask.h36
-rw-r--r--vcl/unx/generic/dtrans/linkdata_curs.h36
-rw-r--r--vcl/unx/generic/dtrans/linkdata_mask.h36
-rw-r--r--vcl/unx/generic/dtrans/movedata_curs.h36
-rw-r--r--vcl/unx/generic/dtrans/movedata_mask.h36
-rw-r--r--vcl/unx/generic/dtrans/nodrop_curs.h36
-rw-r--r--vcl/unx/generic/dtrans/nodrop_mask.h36
-rw-r--r--vcl/unx/generic/fontmanager/fontconfig.cxx1316
-rw-r--r--vcl/unx/generic/fontmanager/fontmanager.cxx1147
-rw-r--r--vcl/unx/generic/fontmanager/fontsubst.cxx223
-rw-r--r--vcl/unx/generic/fontmanager/helper.cxx262
-rw-r--r--vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.cxx185
-rw-r--r--vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.hxx97
-rw-r--r--vcl/unx/generic/gdi/cairo_xlib_cairo.cxx307
-rw-r--r--vcl/unx/generic/gdi/cairo_xlib_cairo.hxx96
-rw-r--r--vcl/unx/generic/gdi/cairotextrender.cxx382
-rw-r--r--vcl/unx/generic/gdi/font.cxx116
-rw-r--r--vcl/unx/generic/gdi/freetypetextrender.cxx216
-rw-r--r--vcl/unx/generic/gdi/gdiimpl.cxx2015
-rw-r--r--vcl/unx/generic/gdi/gdiimpl.hxx297
-rw-r--r--vcl/unx/generic/gdi/salbmp.cxx1001
-rw-r--r--vcl/unx/generic/gdi/salgdi.cxx482
-rw-r--r--vcl/unx/generic/gdi/salgdi2.cxx89
-rw-r--r--vcl/unx/generic/gdi/salvd.cxx222
-rw-r--r--vcl/unx/generic/gdi/x11cairotextrender.cxx66
-rw-r--r--vcl/unx/generic/gdi/xrender_peer.cxx48
-rw-r--r--vcl/unx/generic/glyphs/freetype_glyphcache.cxx975
-rw-r--r--vcl/unx/generic/glyphs/glyphcache.cxx121
-rw-r--r--vcl/unx/generic/print/GenPspGfxBackend.cxx412
-rw-r--r--vcl/unx/generic/print/bitmap_gfx.cxx674
-rw-r--r--vcl/unx/generic/print/common_gfx.cxx1152
-rw-r--r--vcl/unx/generic/print/genprnpsp.cxx1298
-rw-r--r--vcl/unx/generic/print/genpspgraphics.cxx521
-rw-r--r--vcl/unx/generic/print/glyphset.cxx301
-rw-r--r--vcl/unx/generic/print/glyphset.hxx81
-rw-r--r--vcl/unx/generic/print/printerjob.cxx973
-rw-r--r--vcl/unx/generic/print/prtsetup.cxx516
-rw-r--r--vcl/unx/generic/print/prtsetup.hxx138
-rw-r--r--vcl/unx/generic/print/psheader.ps363
-rw-r--r--vcl/unx/generic/print/psputil.cxx184
-rw-r--r--vcl/unx/generic/print/psputil.hxx55
-rw-r--r--vcl/unx/generic/print/text_gfx.cxx158
-rw-r--r--vcl/unx/generic/printer/configuration/README5
-rw-r--r--vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS582
-rw-r--r--vcl/unx/generic/printer/configuration/psprint.conf99
-rw-r--r--vcl/unx/generic/printer/cpdmgr.cxx757
-rw-r--r--vcl/unx/generic/printer/cupsmgr.cxx964
-rw-r--r--vcl/unx/generic/printer/jobdata.cxx316
-rw-r--r--vcl/unx/generic/printer/ppdparser.cxx1991
-rw-r--r--vcl/unx/generic/printer/printerinfomanager.cxx892
-rw-r--r--vcl/unx/generic/window/salframe.cxx4093
-rw-r--r--vcl/unx/generic/window/salobj.cxx506
-rw-r--r--vcl/unx/generic/window/screensaverinhibitor.cxx370
-rw-r--r--vcl/unx/gtk3/a11y/TODO49
-rw-r--r--vcl/unx/gtk3/a11y/atkaction.cxx269
-rw-r--r--vcl/unx/gtk3/a11y/atkbridge.cxx32
-rw-r--r--vcl/unx/gtk3/a11y/atkcomponent.cxx431
-rw-r--r--vcl/unx/gtk3/a11y/atkeditabletext.cxx193
-rw-r--r--vcl/unx/gtk3/a11y/atkfactory.cxx186
-rw-r--r--vcl/unx/gtk3/a11y/atkfactory.hxx30
-rw-r--r--vcl/unx/gtk3/a11y/atkhypertext.cxx277
-rw-r--r--vcl/unx/gtk3/a11y/atkimage.cxx135
-rw-r--r--vcl/unx/gtk3/a11y/atklistener.cxx736
-rw-r--r--vcl/unx/gtk3/a11y/atklistener.hxx68
-rw-r--r--vcl/unx/gtk3/a11y/atkregistry.cxx66
-rw-r--r--vcl/unx/gtk3/a11y/atkregistry.hxx34
-rw-r--r--vcl/unx/gtk3/a11y/atkselection.cxx196
-rw-r--r--vcl/unx/gtk3/a11y/atktable.cxx584
-rw-r--r--vcl/unx/gtk3/a11y/atktext.cxx879
-rw-r--r--vcl/unx/gtk3/a11y/atktextattributes.cxx1388
-rw-r--r--vcl/unx/gtk3/a11y/atktextattributes.hxx45
-rw-r--r--vcl/unx/gtk3/a11y/atkutil.cxx637
-rw-r--r--vcl/unx/gtk3/a11y/atkutil.hxx26
-rw-r--r--vcl/unx/gtk3/a11y/atkvalue.cxx138
-rw-r--r--vcl/unx/gtk3/a11y/atkwrapper.cxx1014
-rw-r--r--vcl/unx/gtk3/a11y/atkwrapper.hxx128
-rw-r--r--vcl/unx/gtk3/customcellrenderer.cxx308
-rw-r--r--vcl/unx/gtk3/customcellrenderer.hxx49
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx2087
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx239
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx211
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFolderPicker.hxx66
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkPicker.cxx300
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkPicker.hxx144
-rw-r--r--vcl/unx/gtk3/fpicker/eventnotification.hxx41
-rw-r--r--vcl/unx/gtk3/fpicker/resourceprovider.cxx80
-rw-r--r--vcl/unx/gtk3/gloactiongroup.cxx405
-rw-r--r--vcl/unx/gtk3/glomenu.cxx694
-rw-r--r--vcl/unx/gtk3/gtkcairo.cxx128
-rw-r--r--vcl/unx/gtk3/gtkcairo.hxx46
-rw-r--r--vcl/unx/gtk3/gtkdata.cxx984
-rw-r--r--vcl/unx/gtk3/gtkframe.cxx6081
-rw-r--r--vcl/unx/gtk3/gtkinst.cxx24113
-rw-r--r--vcl/unx/gtk3/gtkobject.cxx617
-rw-r--r--vcl/unx/gtk3/gtksalmenu.cxx1643
-rw-r--r--vcl/unx/gtk3/gtksys.cxx339
-rw-r--r--vcl/unx/gtk3/hudawareness.cxx110
-rw-r--r--vcl/unx/gtk3/salnativewidgets-gtk.cxx3060
-rw-r--r--vcl/unx/gtk3_kde5/FPServiceInfo.hxx28
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkaction.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkbridge.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkcomponent.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkeditabletext.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkfactory.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkhypertext.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkimage.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atklistener.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkregistry.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkselection.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktable.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktext.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktextattributes.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkutil.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkvalue.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwrapper.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/filepicker_ipc_commands.hxx173
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_a11y.cxx38
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_cairo.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_customcellrenderer.cxx12
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.cxx455
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.hxx131
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.cxx277
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.hxx135
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker.cxx85
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker.hxx59
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_gloactiongroup.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_glomenu.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_gtkdata.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_gtkframe.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_gtkinst.cxx57
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_gtkobject.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_gtksalmenu.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_gtksys.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_hudawareness.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_salnativewidgets-gtk.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/kde5_filepicker.cxx287
-rw-r--r--vcl/unx/gtk3_kde5/kde5_filepicker.hxx110
-rw-r--r--vcl/unx/gtk3_kde5/kde5_filepicker_ipc.cxx374
-rw-r--r--vcl/unx/gtk3_kde5/kde5_filepicker_ipc.hxx51
-rw-r--r--vcl/unx/gtk3_kde5/kde5_lo_filepicker_main.cxx53
-rw-r--r--vcl/unx/gtk4/convert3to4.cxx1576
-rw-r--r--vcl/unx/gtk4/convert3to4.hxx16
-rw-r--r--vcl/unx/gtk4/customcellrenderer.cxx12
-rw-r--r--vcl/unx/gtk4/fpicker/SalGtkFilePicker.cxx12
-rw-r--r--vcl/unx/gtk4/fpicker/SalGtkFilePicker.hxx12
-rw-r--r--vcl/unx/gtk4/fpicker/SalGtkFolderPicker.cxx12
-rw-r--r--vcl/unx/gtk4/fpicker/SalGtkFolderPicker.hxx12
-rw-r--r--vcl/unx/gtk4/fpicker/SalGtkPicker.cxx12
-rw-r--r--vcl/unx/gtk4/fpicker/SalGtkPicker.hxx12
-rw-r--r--vcl/unx/gtk4/fpicker/eventnotification.hxx12
-rw-r--r--vcl/unx/gtk4/fpicker/resourceprovider.cxx12
-rw-r--r--vcl/unx/gtk4/gloactiongroup.cxx12
-rw-r--r--vcl/unx/gtk4/glomenu.cxx12
-rw-r--r--vcl/unx/gtk4/gtkcairo.cxx12
-rw-r--r--vcl/unx/gtk4/gtkcairo.hxx12
-rw-r--r--vcl/unx/gtk4/gtkdata.cxx12
-rw-r--r--vcl/unx/gtk4/gtkframe.cxx14
-rw-r--r--vcl/unx/gtk4/gtkinst.cxx21
-rw-r--r--vcl/unx/gtk4/gtkobject.cxx12
-rw-r--r--vcl/unx/gtk4/gtksalmenu.cxx12
-rw-r--r--vcl/unx/gtk4/gtksys.cxx12
-rw-r--r--vcl/unx/gtk4/hudawareness.cxx12
-rw-r--r--vcl/unx/gtk4/notifyinglayout.cxx88
-rw-r--r--vcl/unx/gtk4/notifyinglayout.hxx36
-rw-r--r--vcl/unx/gtk4/salnativewidgets-gtk.cxx12
-rw-r--r--vcl/unx/gtk4/surfacecellrenderer.cxx241
-rw-r--r--vcl/unx/gtk4/surfacecellrenderer.hxx42
-rw-r--r--vcl/unx/gtk4/surfacepaintable.cxx100
-rw-r--r--vcl/unx/gtk4/surfacepaintable.hxx32
-rw-r--r--vcl/unx/gtk4/transferableprovider.cxx118
-rw-r--r--vcl/unx/gtk4/transferableprovider.hxx51
-rw-r--r--vcl/unx/kf5/KF5FilePicker.cxx180
-rw-r--r--vcl/unx/kf5/KF5FilePicker.hxx63
-rw-r--r--vcl/unx/kf5/KF5SalInstance.cxx92
-rw-r--r--vcl/unx/kf5/KF5SalInstance.hxx35
-rw-r--r--vcl/unx/x11/x11sys.cxx105
-rw-r--r--vcl/unx/x11/xlimits.cxx29
209 files changed, 99324 insertions, 0 deletions
diff --git a/vcl/unx/generic/app/gendata.cxx b/vcl/unx/generic/app/gendata.cxx
new file mode 100644
index 000000000..444d65302
--- /dev/null
+++ b/vcl/unx/generic/app/gendata.cxx
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unx/gendata.hxx>
+
+#include <unx/fontmanager.hxx>
+#include <unx/glyphcache.hxx>
+#include <printerinfomanager.hxx>
+
+SalData::SalData() { SetSalData(this); }
+
+SalData::~SalData() {}
+
+GenericUnixSalData::GenericUnixSalData()
+ : m_pDisplay(nullptr)
+{
+}
+
+GenericUnixSalData::~GenericUnixSalData()
+{
+ // at least for InitPrintFontManager the sequence is important
+ m_pPrintFontManager.reset();
+ m_pFreetypeManager.reset();
+ m_pPrinterInfoManager.reset();
+}
+
+void GenericUnixSalData::Dispose() {}
+
+void GenericUnixSalData::InitFreetypeManager() { m_pFreetypeManager.reset(new FreetypeManager); }
+
+void GenericUnixSalData::InitPrintFontManager()
+{
+ GetFreetypeManager();
+ m_pPrintFontManager.reset(new psp::PrintFontManager);
+ m_pPrintFontManager->initialize();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/gendisp.cxx b/vcl/unx/generic/app/gendisp.cxx
new file mode 100644
index 000000000..b1dbef3f5
--- /dev/null
+++ b/vcl/unx/generic/app/gendisp.cxx
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <salframe.hxx>
+#include <unx/gendisp.hxx>
+
+SalGenericDisplay::SalGenericDisplay()
+{
+ m_pCapture = nullptr;
+}
+
+SalGenericDisplay::~SalGenericDisplay()
+{
+}
+
+void SalGenericDisplay::registerFrame( SalFrame* pFrame )
+{
+ insertFrame( pFrame );
+}
+
+void SalGenericDisplay::deregisterFrame( SalFrame* pFrame )
+{
+ eraseFrame( pFrame );
+}
+
+void SalGenericDisplay::emitDisplayChanged()
+{
+ SalFrame *pAnyFrame = anyFrame();
+ if( pAnyFrame )
+ pAnyFrame->CallCallback( SalEvent::DisplayChanged, nullptr );
+}
+
+bool SalGenericDisplay::DispatchInternalEvent( bool bHandleAllCurrentEvent )
+{
+ return DispatchUserEvents( bHandleAllCurrentEvent );
+}
+
+void SalGenericDisplay::SendInternalEvent( SalFrame* pFrame, void* pData, SalEvent nEvent )
+{
+ PostEvent( pFrame, pData, nEvent );
+}
+
+void SalGenericDisplay::CancelInternalEvent( SalFrame* pFrame, void* pData, SalEvent nEvent )
+{
+ RemoveEvent( pFrame, pData, nEvent );
+}
+
+void SalGenericDisplay::ProcessEvent( SalUserEvent aEvent )
+{
+ aEvent.m_pFrame->CallCallback( aEvent.m_nEvent, aEvent.m_pData );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/geninst.cxx b/vcl/unx/generic/app/geninst.cxx
new file mode 100644
index 000000000..191d87ca7
--- /dev/null
+++ b/vcl/unx/generic/app/geninst.cxx
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#if defined(LINUX)
+# include <stdio.h>
+#endif
+#if defined(__FreeBSD__)
+# include <sys/utsname.h>
+#endif
+
+#include <config_features.h>
+#if HAVE_FEATURE_OPENGL
+#include <vcl/opengl/OpenGLContext.hxx>
+#endif
+#include <unx/geninst.h>
+#include <o3tl/string_view.hxx>
+
+// SalYieldMutex
+
+SalYieldMutex::SalYieldMutex()
+{
+#if HAVE_FEATURE_OPENGL
+ SetBeforeReleaseHandler( &OpenGLContext::prepareForYield );
+#endif
+}
+
+SalYieldMutex::~SalYieldMutex()
+{
+}
+
+SalGenericInstance::~SalGenericInstance()
+{
+}
+
+OUString SalGenericInstance::getOSVersion()
+{
+ OUString aKernelVer = "unknown";
+#if defined(LINUX)
+ FILE* pVersion = fopen( "/proc/version", "r" );
+ if ( pVersion )
+ {
+ char aVerBuffer[512];
+ if ( fgets ( aVerBuffer, 511, pVersion ) )
+ {
+ aKernelVer = OUString::createFromAscii( aVerBuffer );
+ // "Linux version 3.16.7-29-desktop ..."
+ std::u16string_view aVers = o3tl::getToken(aKernelVer, 2, ' ' );
+ // "3.16.7-29-desktop ..."
+ size_t nTooDetailed = aVers.find( '.', 2);
+ if (nTooDetailed < 1 || nTooDetailed > 8)
+ aKernelVer = "Linux (misparsed version)";
+ else // "3.16.7-29-desktop ..."
+ aKernelVer = OUString::Concat("Linux ") + aVers.substr(0, nTooDetailed);
+ }
+ fclose( pVersion );
+ }
+#elif defined(__FreeBSD__)
+ struct utsname stName;
+ if ( uname( &stName ) != 0 )
+ return aKernelVer;
+
+ sal_Int32 nDots = 0;
+ sal_Int32 nIndex = 0;
+ aKernelVer = OUString::createFromAscii( stName.release );
+ while ( nIndex++ < aKernelVer.getLength() )
+ {
+ const char c = stName.release[ nIndex ];
+ if ( c == ' ' || c == '-' || ( c == '.' && nDots++ > 0 ) )
+ break;
+ }
+ aKernelVer = OUString::createFromAscii(stName.sysname) + " " + aKernelVer.copy(0, nIndex);
+#elif defined(EMSCRIPTEN)
+#define str(s) #s
+#define xstr(s) str(s)
+ aKernelVer = "Emscripten " xstr(__EMSCRIPTEN_major__)
+ "." xstr(__EMSCRIPTEN_minor__) "." xstr(__EMSCRIPTEN_tiny__);
+#endif
+ return aKernelVer;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/gensys.cxx b/vcl/unx/generic/app/gensys.cxx
new file mode 100644
index 000000000..98371c548
--- /dev/null
+++ b/vcl/unx/generic/app/gensys.cxx
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_folders.h>
+
+#include <unx/gensys.h>
+
+#include <svdata.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <rtl/bootstrap.hxx>
+#include <osl/process.h>
+#include <osl/thread.h>
+#include <unotools/configmgr.hxx>
+
+using namespace com::sun::star;
+
+SalGenericSystem::SalGenericSystem()
+{
+}
+
+SalGenericSystem::~SalGenericSystem()
+{
+}
+
+int SalGenericSystem::ShowNativeMessageBox( const OUString& rTitle, const OUString& rMessage )
+{
+ std::vector< OUString > aButtons;
+ int nButtonIds[5] = {0}, nBut = 0;
+
+ ImplHideSplash();
+
+ aButtons.push_back( "OK" );
+ nButtonIds[nBut++] = SALSYSTEM_SHOWNATIVEMSGBOX_BTN_OK;
+ int nResult = ShowNativeDialog( rTitle, rMessage, aButtons );
+
+ return nResult != -1 ? nButtonIds[ nResult ] : 0;
+}
+
+#if !defined(ANDROID) && !defined(IOS)
+
+// X11-specific
+
+const char* SalGenericSystem::getFrameResName()
+{
+ /* according to ICCCM:
+ * first search command line for -name parameter
+ * then try RESOURCE_NAME environment variable
+ * then use argv[0] stripped by directories
+ */
+ static OStringBuffer aResName;
+ if( aResName.isEmpty() )
+ {
+ int nArgs = osl_getCommandArgCount();
+ for( int n = 0; n < nArgs-1; n++ )
+ {
+ OUString aArg;
+ osl_getCommandArg( n, &aArg.pData );
+ if( aArg.equalsIgnoreAsciiCase("-name") )
+ {
+ osl_getCommandArg( n+1, &aArg.pData );
+ aResName.append( OUStringToOString( aArg, osl_getThreadTextEncoding() ) );
+ break;
+ }
+ }
+ if( aResName.isEmpty() )
+ {
+ const char* pEnv = getenv( "RESOURCE_NAME" );
+ if( pEnv && *pEnv )
+ aResName.append( pEnv );
+ }
+ if( aResName.isEmpty() )
+ aResName.append( OUStringToOString( utl::ConfigManager::getProductName().toAsciiLowerCase(),
+ osl_getThreadTextEncoding()));
+ }
+ return aResName.getStr();
+}
+
+const char* SalGenericSystem::getFrameClassName()
+{
+ static OStringBuffer aClassName;
+ if( aClassName.isEmpty() )
+ {
+ OUString aIni, aProduct;
+ rtl::Bootstrap::get( "BRAND_BASE_DIR", aIni );
+ aIni += "/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap" );
+ rtl::Bootstrap aBootstrap( aIni );
+ aBootstrap.getFrom( "ProductKey", aProduct );
+
+ if( !aProduct.isEmpty() )
+ aClassName.append( OUStringToOString( aProduct, osl_getThreadTextEncoding() ) );
+ else
+ aClassName.append( OUStringToOString( utl::ConfigManager::getProductName(), osl_getThreadTextEncoding()));
+ }
+ return aClassName.getStr();
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/i18n_cb.cxx b/vcl/unx/generic/app/i18n_cb.cxx
new file mode 100644
index 000000000..c17c01a4d
--- /dev/null
+++ b/vcl/unx/generic/app/i18n_cb.cxx
@@ -0,0 +1,494 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <o3tl/safeint.hxx>
+#include <osl/thread.h>
+#include <sal/log.hxx>
+
+#include <X11/Xlib.h>
+
+#include <vcl/commandevent.hxx>
+#include <unx/i18n_cb.hxx>
+#include <unx/i18n_ic.hxx>
+#include <unx/i18n_im.hxx>
+#include <salframe.hxx>
+
+// i. preedit start callback
+
+int
+PreeditStartCallback ( XIC, XPointer client_data, XPointer )
+{
+ preedit_data_t* pPreeditData = reinterpret_cast<preedit_data_t*>(client_data);
+ if ( pPreeditData->eState == PreeditStatus::ActivationRequired )
+ {
+ pPreeditData->eState = PreeditStatus::Active;
+ pPreeditData->aText.nLength = 0;
+ }
+
+ return -1;
+}
+
+// ii. preedit done callback
+
+void
+PreeditDoneCallback ( XIC, XPointer client_data, XPointer )
+{
+ preedit_data_t* pPreeditData = reinterpret_cast<preedit_data_t*>(client_data);
+ if (pPreeditData->eState == PreeditStatus::Active )
+ {
+ if( pPreeditData->pFrame )
+ pPreeditData->pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+ }
+ pPreeditData->eState = PreeditStatus::StartPending;
+}
+
+// iii. preedit draw callback
+
+// Handle deletion of text in a preedit_draw_callback
+// from and howmuch are guaranteed to be nonnegative
+
+static void
+Preedit_DeleteText(preedit_text_t *ptext, int from, int howmuch)
+{
+ // If we've been asked to delete no text then just set
+ // nLength correctly and return
+ if (ptext->nLength == 0)
+ {
+ ptext->nLength = from;
+ return;
+ }
+
+ int to = from + howmuch;
+
+ if (to == static_cast<int>(ptext->nLength))
+ {
+ // delete from the end of the text
+ ptext->nLength = from;
+ }
+ else if (to < static_cast<int>(ptext->nLength))
+ {
+ // cut out of the middle of the text
+ memmove( static_cast<void*>(ptext->pUnicodeBuffer + from),
+ static_cast<void*>(ptext->pUnicodeBuffer + to),
+ (ptext->nLength - to) * sizeof(sal_Unicode));
+ memmove( static_cast<void*>(ptext->pCharStyle + from),
+ static_cast<void*>(ptext->pCharStyle + to),
+ (ptext->nLength - to) * sizeof(XIMFeedback));
+ ptext->nLength -= howmuch;
+ }
+ else
+ {
+ // XXX this indicates an error, are we out of sync ?
+ SAL_INFO("vcl.app", "Preedit_DeleteText( from=" << from
+ << " to=" << to
+ << " length=" << ptext->nLength
+ << " ).");
+ fprintf (stderr, "\t XXX internal error, out of sync XXX\n");
+
+ ptext->nLength = from;
+ }
+
+ // NULL-terminate the string
+ ptext->pUnicodeBuffer[ptext->nLength] = u'\0';
+}
+
+// reallocate the textbuffer with sufficiently large size 2^x
+// nnewlimit is presupposed to be larger than ptext->size
+static void
+enlarge_buffer ( preedit_text_t *ptext, int nnewlimit )
+{
+ size_t nnewsize = ptext->nSize;
+
+ while ( nnewsize <= o3tl::make_unsigned(nnewlimit) )
+ nnewsize *= 2;
+
+ ptext->nSize = nnewsize;
+ ptext->pUnicodeBuffer = static_cast<sal_Unicode*>(realloc(static_cast<void*>(ptext->pUnicodeBuffer),
+ nnewsize * sizeof(sal_Unicode)));
+ ptext->pCharStyle = static_cast<XIMFeedback*>(realloc(static_cast<void*>(ptext->pCharStyle),
+ nnewsize * sizeof(XIMFeedback)));
+}
+
+// Handle insertion of text in a preedit_draw_callback
+// string field of XIMText struct is guaranteed to be != NULL
+
+static void
+Preedit_InsertText(preedit_text_t *pText, XIMText *pInsertText, int where)
+{
+ sal_Unicode *pInsertTextString;
+ int nInsertTextLength = 0;
+ XIMFeedback *pInsertTextCharStyle = pInsertText->feedback;
+
+ nInsertTextLength = pInsertText->length;
+
+ // can't handle wchar_t strings, so convert to multibyte chars first
+ char *pMBString;
+ size_t nMBLength;
+ if (pInsertText->encoding_is_wchar)
+ {
+ wchar_t *pWCString = pInsertText->string.wide_char;
+ size_t nBytes = wcstombs ( nullptr, pWCString, 0 /* don't care */);
+ pMBString = static_cast<char*>(alloca( nBytes + 1 ));
+ nMBLength = wcstombs ( pMBString, pWCString, nBytes + 1);
+ }
+ else
+ {
+ pMBString = pInsertText->string.multi_byte;
+ nMBLength = strlen(pMBString); // xxx
+ }
+
+ // convert multibyte chars to unicode
+ rtl_TextEncoding nEncoding = osl_getThreadTextEncoding();
+
+ if (nEncoding != RTL_TEXTENCODING_UNICODE)
+ {
+ rtl_TextToUnicodeConverter aConverter =
+ rtl_createTextToUnicodeConverter( nEncoding );
+ rtl_TextToUnicodeContext aContext =
+ rtl_createTextToUnicodeContext(aConverter);
+
+ sal_Size nBufferSize = nInsertTextLength * 2;
+
+ pInsertTextString = static_cast<sal_Unicode*>(alloca(nBufferSize));
+
+ sal_uInt32 nConversionInfo;
+ sal_Size nConvertedChars;
+
+ rtl_convertTextToUnicode( aConverter, aContext,
+ pMBString, nMBLength,
+ pInsertTextString, nBufferSize,
+ RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_IGNORE
+ | RTL_TEXTTOUNICODE_FLAGS_INVALID_IGNORE,
+ &nConversionInfo, &nConvertedChars );
+
+ rtl_destroyTextToUnicodeContext(aConverter, aContext);
+ rtl_destroyTextToUnicodeConverter(aConverter);
+
+ }
+ else
+ {
+ pInsertTextString = reinterpret_cast<sal_Unicode*>(pMBString);
+ }
+
+ // enlarge target text-buffer if necessary
+ if (pText->nSize <= (pText->nLength + nInsertTextLength))
+ enlarge_buffer(pText, pText->nLength + nInsertTextLength);
+
+ // insert text: displace old mem and put new bytes in
+ int from = where;
+ int to = where + nInsertTextLength;
+ int howmany = pText->nLength - where;
+
+ memmove(static_cast<void*>(pText->pUnicodeBuffer + to),
+ static_cast<void*>(pText->pUnicodeBuffer + from),
+ howmany * sizeof(sal_Unicode));
+ memmove(static_cast<void*>(pText->pCharStyle + to),
+ static_cast<void*>(pText->pCharStyle + from),
+ howmany * sizeof(XIMFeedback));
+
+ to = from;
+ howmany = nInsertTextLength;
+
+ memcpy(static_cast<void*>(pText->pUnicodeBuffer + to), static_cast<void*>(pInsertTextString),
+ howmany * sizeof(sal_Unicode));
+ memcpy(static_cast<void*>(pText->pCharStyle + to), static_cast<void*>(pInsertTextCharStyle),
+ howmany * sizeof(XIMFeedback));
+
+ pText->nLength += howmany;
+
+ // NULL-terminate the string
+ pText->pUnicodeBuffer[pText->nLength] = u'\0';
+}
+
+// Handle the change of attributes in a preedit_draw_callback
+
+static void
+Preedit_UpdateAttributes ( preedit_text_t* ptext, XIMFeedback const * feedback,
+ int from, int amount )
+{
+ if ( (from + amount) > static_cast<int>(ptext->nLength) )
+ {
+ // XXX this indicates an error, are we out of sync ?
+ SAL_INFO("vcl.app", "Preedit_UpdateAttributes( "
+ << from << " + " << amount << " > " << ptext->nLength
+ << " ).");
+ fprintf (stderr, "\t XXX internal error, out of sync XXX\n");
+
+ return;
+ }
+
+ memcpy ( ptext->pCharStyle + from,
+ feedback, amount * sizeof(XIMFeedback) );
+}
+
+// Convert the XIM feedback values into appropriate VCL
+// EXTTEXTINPUT_ATTR values
+// returns an allocate list of attributes, which must be freed by caller
+static ExtTextInputAttr*
+Preedit_FeedbackToSAL ( const XIMFeedback* pfeedback, int nlength, std::vector<ExtTextInputAttr>& rSalAttr )
+{
+ ExtTextInputAttr *psalattr;
+ ExtTextInputAttr nval;
+ ExtTextInputAttr noldval = ExtTextInputAttr::NONE;
+ XIMFeedback nfeedback;
+
+ // only work with reasonable length
+ if (nlength > 0 && nlength > sal::static_int_cast<int>(rSalAttr.size()) )
+ {
+ rSalAttr.reserve( nlength );
+ psalattr = rSalAttr.data();
+ }
+ else
+ return nullptr;
+
+ for (int npos = 0; npos < nlength; npos++)
+ {
+ nval = ExtTextInputAttr::NONE;
+ nfeedback = pfeedback[npos];
+
+ // means to use the feedback of the previous char
+ if (nfeedback == 0)
+ {
+ nval = noldval;
+ }
+ // convert feedback to attributes
+ else
+ {
+ if (nfeedback & XIMReverse)
+ nval |= ExtTextInputAttr::Highlight;
+ if (nfeedback & XIMUnderline)
+ nval |= ExtTextInputAttr::Underline;
+ if (nfeedback & XIMHighlight)
+ nval |= ExtTextInputAttr::Highlight;
+ if (nfeedback & XIMPrimary)
+ nval |= ExtTextInputAttr::DottedUnderline;
+ if (nfeedback & XIMSecondary)
+ nval |= ExtTextInputAttr::DashDotUnderline;
+ if (nfeedback & XIMTertiary) // same as 2ery
+ nval |= ExtTextInputAttr::DashDotUnderline;
+
+ }
+ // copy in list
+ psalattr[npos] = nval;
+ noldval = nval;
+ }
+ // return list of sal attributes
+ return psalattr;
+}
+
+void
+PreeditDrawCallback(XIC ic, XPointer client_data,
+ XIMPreeditDrawCallbackStruct *call_data)
+{
+ preedit_data_t* pPreeditData = reinterpret_cast<preedit_data_t*>(client_data);
+
+ // if there's nothing to change then change nothing
+ if ( ( (call_data->text == nullptr) && (call_data->chg_length == 0) )
+ || pPreeditData->pFrame == nullptr )
+ return;
+
+ // Solaris 7 deletes the preedit buffer after commit
+ // since the next call to preeditstart will have the same effect just skip this.
+ // if (pPreeditData->eState == ePreeditStatusStartPending && call_data->text == NULL)
+ // return;
+
+ if ( pPreeditData->eState == PreeditStatus::StartPending )
+ pPreeditData->eState = PreeditStatus::ActivationRequired;
+ PreeditStartCallback( ic, client_data, nullptr );
+
+ // Edit the internal textbuffer as indicated by the call_data,
+ // chg_first and chg_length are guaranteed to be nonnegative
+
+ // handle text deletion
+ if (call_data->text == nullptr)
+ {
+ Preedit_DeleteText(&(pPreeditData->aText),
+ call_data->chg_first, call_data->chg_length );
+ }
+ else
+ {
+ // handle text insertion
+ if ( (call_data->chg_length == 0)
+ && (call_data->text->string.wide_char != nullptr))
+ {
+ Preedit_InsertText(&(pPreeditData->aText), call_data->text,
+ call_data->chg_first);
+ }
+ else if ( (call_data->chg_length != 0)
+ && (call_data->text->string.wide_char != nullptr))
+ {
+ // handle text replacement by deletion and insertion of text,
+ // not smart, just good enough
+
+ Preedit_DeleteText(&(pPreeditData->aText),
+ call_data->chg_first, call_data->chg_length);
+ Preedit_InsertText(&(pPreeditData->aText), call_data->text,
+ call_data->chg_first);
+ }
+ else if ( (call_data->chg_length != 0)
+ && (call_data->text->string.wide_char == nullptr))
+ {
+ // not really a text update, only attributes are concerned
+ Preedit_UpdateAttributes(&(pPreeditData->aText),
+ call_data->text->feedback,
+ call_data->chg_first, call_data->chg_length);
+ }
+ }
+
+ // build the SalExtTextInputEvent and send it up
+
+ pPreeditData->aInputEv.mpTextAttr = Preedit_FeedbackToSAL(
+ pPreeditData->aText.pCharStyle, pPreeditData->aText.nLength, pPreeditData->aInputFlags);
+ pPreeditData->aInputEv.mnCursorPos = call_data->caret;
+ pPreeditData->aInputEv.maText = OUString(pPreeditData->aText.pUnicodeBuffer,
+ pPreeditData->aText.nLength);
+ pPreeditData->aInputEv.mnCursorFlags = 0; // default: make cursor visible
+
+ if ( pPreeditData->eState == PreeditStatus::Active && pPreeditData->pFrame )
+ pPreeditData->pFrame->CallCallback(SalEvent::ExtTextInput, static_cast<void*>(&pPreeditData->aInputEv));
+ if (pPreeditData->aText.nLength == 0 && pPreeditData->pFrame )
+ pPreeditData->pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+
+ if (pPreeditData->aText.nLength == 0)
+ pPreeditData->eState = PreeditStatus::StartPending;
+
+ GetPreeditSpotLocation(ic, reinterpret_cast<XPointer>(pPreeditData));
+}
+
+void
+GetPreeditSpotLocation(XIC ic, XPointer client_data)
+{
+
+ // Send SalEventExtTextInputPos event to get spotlocation
+
+ SalExtTextInputPosEvent aPosEvent;
+ preedit_data_t* pPreeditData = reinterpret_cast<preedit_data_t*>(client_data);
+
+ if( pPreeditData->pFrame )
+ pPreeditData->pFrame->CallCallback(SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent));
+
+ XPoint point;
+ point.x = aPosEvent.mnX + aPosEvent.mnWidth;
+ point.y = aPosEvent.mnY + aPosEvent.mnHeight;
+
+ XVaNestedList preedit_attr;
+ preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &point, nullptr);
+ XSetICValues(ic, XNPreeditAttributes, preedit_attr, nullptr);
+ XFree(preedit_attr);
+}
+
+// iv. preedit caret callback
+
+#if OSL_DEBUG_LEVEL > 1
+void
+PreeditCaretCallback ( XIC ic, XPointer client_data,
+ XIMPreeditCaretCallbackStruct *call_data )
+{
+ // XXX PreeditCaretCallback is pure debug code for now
+ const char *direction = "?";
+ const char *style = "?";
+
+ switch ( call_data->style )
+ {
+ case XIMIsInvisible: style = "Invisible"; break;
+ case XIMIsPrimary: style = "Primary"; break;
+ case XIMIsSecondary: style = "Secondary"; break;
+ }
+ switch ( call_data->direction )
+ {
+ case XIMForwardChar: direction = "Forward char"; break;
+ case XIMBackwardChar: direction = "Backward char"; break;
+ case XIMForwardWord: direction = "Forward word"; break;
+ case XIMBackwardWord: direction = "Backward word"; break;
+ case XIMCaretUp: direction = "Caret up"; break;
+ case XIMCaretDown: direction = "Caret down"; break;
+ case XIMNextLine: direction = "Next line"; break;
+ case XIMPreviousLine: direction = "Previous line"; break;
+ case XIMLineStart: direction = "Line start"; break;
+ case XIMLineEnd: direction = "Line end"; break;
+ case XIMAbsolutePosition: direction = "Absolute"; break;
+ case XIMDontChange: direction = "Don't change"; break;
+ }
+
+ SAL_INFO("vcl.app", "PreeditCaretCallback( ic=" << ic
+ << ", client=" << client_data
+ << ",");
+ SAL_INFO("vcl.app", "\t position=" << call_data->position
+ << ", direction=\"" << direction
+ << "\", style=\"" << style
+ << "\" ).");
+}
+#else
+void
+PreeditCaretCallback ( XIC, XPointer, XIMPreeditCaretCallbackStruct* )
+{
+}
+#endif
+
+// v. commit string callback: convert an extended text input (iiimp ... )
+// into an ordinary key-event
+
+Bool
+IsControlCode(sal_Unicode nChar)
+{
+ if ( nChar <= 0x1F /* C0 controls */ )
+ return True;
+ else
+ return False;
+}
+
+// vi. status callbacks: for now these are empty, they are just needed for turbo linux
+
+void
+StatusStartCallback (XIC, XPointer, XPointer)
+{
+}
+
+void
+StatusDoneCallback (XIC, XPointer, XPointer)
+{
+}
+
+void
+StatusDrawCallback (XIC, XPointer, XIMStatusDrawCallbackStruct *)
+{
+}
+
+// vii. destroy callbacks: internally disable all IC/IM calls
+
+void
+IC_IMDestroyCallback (XIM, XPointer client_data, XPointer)
+{
+ SalI18N_InputContext *pContext = reinterpret_cast<SalI18N_InputContext*>(client_data);
+ if (pContext != nullptr)
+ pContext->HandleDestroyIM();
+}
+
+void
+IM_IMDestroyCallback (XIM, XPointer client_data, XPointer)
+{
+ SalI18N_InputMethod *pMethod = reinterpret_cast<SalI18N_InputMethod*>(client_data);
+ if (pMethod != nullptr)
+ pMethod->HandleDestroyIM();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/i18n_ic.cxx b/vcl/unx/generic/app/i18n_ic.cxx
new file mode 100644
index 000000000..32390a888
--- /dev/null
+++ b/vcl/unx/generic/app/i18n_ic.cxx
@@ -0,0 +1,604 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <X11/Xlib.h>
+
+#include <unx/i18n_ic.hxx>
+#include <unx/i18n_im.hxx>
+
+#include <unx/salframe.h>
+#include <unx/saldisp.hxx>
+
+#include <sal/log.hxx>
+
+using namespace vcl;
+
+static void sendEmptyCommit( SalFrame* pFrame )
+{
+ vcl::DeletionListener aDel( pFrame );
+
+ SalExtTextInputEvent aEmptyEv;
+ aEmptyEv.mpTextAttr = nullptr;
+ aEmptyEv.maText.clear();
+ aEmptyEv.mnCursorPos = 0;
+ aEmptyEv.mnCursorFlags = 0;
+ pFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void*>(&aEmptyEv) );
+ if( ! aDel.isDeleted() )
+ pFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+}
+
+// Constructor / Destructor, the InputContext is bound to the SalFrame, as it
+// needs the shell window as a focus window
+
+SalI18N_InputContext::~SalI18N_InputContext()
+{
+ if ( maContext != nullptr )
+ XDestroyIC( maContext );
+ if ( mpAttributes != nullptr )
+ XFree( mpAttributes );
+ if ( mpStatusAttributes != nullptr )
+ XFree( mpStatusAttributes );
+ if ( mpPreeditAttributes != nullptr )
+ XFree( mpPreeditAttributes );
+
+ if (maClientData.aText.pUnicodeBuffer != nullptr)
+ free(maClientData.aText.pUnicodeBuffer);
+ if (maClientData.aText.pCharStyle != nullptr)
+ free(maClientData.aText.pCharStyle);
+}
+
+// convenience routine to add items to a XVaNestedList
+
+static XVaNestedList
+XVaAddToNestedList( XVaNestedList a_srclist, char* name, XPointer value )
+{
+ XVaNestedList a_dstlist;
+
+ // if ( value == NULL )
+ // return a_srclist;
+
+ if ( a_srclist == nullptr )
+ {
+ a_dstlist = XVaCreateNestedList(
+ 0,
+ name, value,
+ nullptr );
+ }
+ else
+ {
+ a_dstlist = XVaCreateNestedList(
+ 0,
+ XNVaNestedList, a_srclist,
+ name, value,
+ nullptr );
+ }
+
+ return a_dstlist != nullptr ? a_dstlist : a_srclist ;
+}
+
+// convenience routine to create a fontset
+
+static XFontSet
+get_font_set( Display *p_display )
+{
+ static XFontSet p_font_set = nullptr;
+
+ if (p_font_set == nullptr)
+ {
+ char **pp_missing_list;
+ int n_missing_count;
+ char *p_default_string;
+
+ p_font_set = XCreateFontSet(p_display, "-*",
+ &pp_missing_list, &n_missing_count, &p_default_string);
+ }
+
+ return p_font_set;
+}
+
+const XIMStyle g_nSupportedStatusStyle(
+ XIMStatusCallbacks |
+ XIMStatusNothing |
+ XIMStatusNone
+ );
+
+// Constructor for an InputContext (IC)
+
+SalI18N_InputContext::SalI18N_InputContext ( SalFrame *pFrame ) :
+ mbUseable( True ),
+ maContext( nullptr ),
+ mnSupportedPreeditStyle(
+ XIMPreeditCallbacks |
+ XIMPreeditNothing |
+ XIMPreeditNone
+ ),
+ mnStatusStyle( 0 ),
+ mnPreeditStyle( 0 ),
+ mpAttributes( nullptr ),
+ mpStatusAttributes( nullptr ),
+ mpPreeditAttributes( nullptr )
+{
+#ifdef __sun
+ static const char* pIIIMPEnable = getenv( "SAL_DISABLE_OWN_IM_STATUS" );
+ if( pIIIMPEnable && *pIIIMPEnable )
+ mnSupportedStatusStyle &= ~XIMStatusCallbacks;
+#endif
+
+ memset(&maPreeditStartCallback, 0, sizeof(maPreeditStartCallback));
+ memset(&maPreeditDoneCallback, 0, sizeof(maPreeditDoneCallback));
+ memset(&maPreeditDrawCallback, 0, sizeof(maPreeditDrawCallback));
+ memset(&maPreeditCaretCallback, 0, sizeof(maPreeditCaretCallback));
+ memset(&maCommitStringCallback, 0, sizeof(maCommitStringCallback));
+ memset(&maSwitchIMCallback, 0, sizeof(maSwitchIMCallback));
+ memset(&maDestroyCallback, 0, sizeof(maDestroyCallback));
+
+ maClientData.aText.pUnicodeBuffer = nullptr;
+ maClientData.aText.pCharStyle = nullptr;
+ maClientData.aInputEv.mpTextAttr = nullptr;
+ maClientData.aInputEv.mnCursorPos = 0;
+ maClientData.aInputEv.mnCursorFlags = 0;
+
+ SalI18N_InputMethod *pInputMethod;
+ pInputMethod = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetInputMethod();
+
+ mnSupportedPreeditStyle = XIMPreeditCallbacks | XIMPreeditPosition
+ | XIMPreeditNothing | XIMPreeditNone;
+ if (pInputMethod->UseMethod()
+ && SupportInputMethodStyle( pInputMethod->GetSupportedStyles() ) )
+ {
+ const SystemEnvData* pEnv = pFrame->GetSystemData();
+ ::Window aClientWindow = pEnv->aShellWindow;
+ ::Window aFocusWindow = pEnv->GetWindowHandle(pFrame);
+
+ // for status callbacks and commit string callbacks
+#define PREEDIT_BUFSZ 16
+ maClientData.eState = PreeditStatus::StartPending;
+ maClientData.pFrame = pFrame;
+ maClientData.aText.pUnicodeBuffer =
+ static_cast<sal_Unicode*>(malloc(PREEDIT_BUFSZ * sizeof(sal_Unicode)));
+ maClientData.aText.pCharStyle =
+ static_cast<XIMFeedback*>(malloc(PREEDIT_BUFSZ * sizeof(XIMFeedback)));
+ maClientData.aText.nSize = PREEDIT_BUFSZ;
+ maClientData.aText.nLength = 0;
+
+ // Status attributes
+
+ switch ( mnStatusStyle )
+ {
+ case XIMStatusCallbacks:
+ {
+ static XIMCallback aStatusStartCallback;
+ static XIMCallback aStatusDoneCallback;
+ static XIMCallback aStatusDrawCallback;
+
+ aStatusStartCallback.callback = reinterpret_cast<XIMProc>(StatusStartCallback);
+ aStatusStartCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
+ aStatusDoneCallback.callback = reinterpret_cast<XIMProc>(StatusDoneCallback);
+ aStatusDoneCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
+ aStatusDrawCallback.callback = reinterpret_cast<XIMProc>(StatusDrawCallback);
+ aStatusDrawCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
+
+ mpStatusAttributes = XVaCreateNestedList (
+ 0,
+ XNStatusStartCallback, &aStatusStartCallback,
+ XNStatusDoneCallback, &aStatusDoneCallback,
+ XNStatusDrawCallback, &aStatusDrawCallback,
+ nullptr );
+
+ break;
+ }
+
+ case XIMStatusArea:
+ /* not supported */
+ break;
+
+ case XIMStatusNone:
+ case XIMStatusNothing:
+ default:
+ /* no arguments needed */
+ break;
+ }
+
+ // set preedit attributes
+
+ switch ( mnPreeditStyle )
+ {
+ case XIMPreeditCallbacks:
+
+ maPreeditCaretCallback.callback = reinterpret_cast<XIMProc>(PreeditCaretCallback);
+ maPreeditStartCallback.callback = reinterpret_cast<XIMProc>(PreeditStartCallback);
+ maPreeditDoneCallback.callback = reinterpret_cast<XIMProc>(PreeditDoneCallback);
+ maPreeditDrawCallback.callback = reinterpret_cast<XIMProc>(PreeditDrawCallback);
+ maPreeditCaretCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
+ maPreeditStartCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
+ maPreeditDoneCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
+ maPreeditDrawCallback.client_data = reinterpret_cast<XPointer>(&maClientData);
+
+ mpPreeditAttributes = XVaCreateNestedList (
+ 0,
+ XNPreeditStartCallback, &maPreeditStartCallback,
+ XNPreeditDoneCallback, &maPreeditDoneCallback,
+ XNPreeditDrawCallback, &maPreeditDrawCallback,
+ XNPreeditCaretCallback, &maPreeditCaretCallback,
+ nullptr );
+
+ break;
+
+ case XIMPreeditArea:
+ /* not supported */
+ break;
+
+ case XIMPreeditPosition:
+ {
+ // spot location
+ SalExtTextInputPosEvent aPosEvent;
+ pFrame->CallCallback(SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent));
+
+ static XPoint aSpot;
+ aSpot.x = aPosEvent.mnX + aPosEvent.mnWidth;
+ aSpot.y = aPosEvent.mnY + aPosEvent.mnHeight;
+
+ // create attributes for preedit position style
+ mpPreeditAttributes = XVaCreateNestedList (
+ 0,
+ XNSpotLocation, &aSpot,
+ nullptr );
+
+ // XCreateIC() fails on Redflag Linux 2.0 if there is no
+ // fontset though the data itself is not evaluated nor is
+ // it required according to the X specs.
+ Display* pDisplay = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay();
+ XFontSet pFontSet = get_font_set(pDisplay);
+
+ if (pFontSet != nullptr)
+ {
+ mpPreeditAttributes = XVaAddToNestedList( mpPreeditAttributes,
+ const_cast<char*>(XNFontSet), reinterpret_cast<XPointer>(pFontSet));
+ }
+
+ break;
+ }
+
+ case XIMPreeditNone:
+ case XIMPreeditNothing:
+ default:
+ /* no arguments needed */
+ break;
+ }
+
+ // Create the InputContext by giving it exactly the information it
+ // deserves, because inappropriate attributes
+ // let XCreateIC fail on Solaris (eg. for C locale)
+
+ mpAttributes = XVaCreateNestedList(
+ 0,
+ XNFocusWindow, aFocusWindow,
+ XNClientWindow, aClientWindow,
+ XNInputStyle, mnPreeditStyle | mnStatusStyle,
+ nullptr );
+
+ if ( mnPreeditStyle != XIMPreeditNone )
+ {
+#if defined LINUX || defined FREEBSD || defined NETBSD || defined OPENBSD || defined DRAGONFLY
+ if ( mpPreeditAttributes != nullptr )
+#endif
+ mpAttributes = XVaAddToNestedList( mpAttributes,
+ const_cast<char*>(XNPreeditAttributes), static_cast<XPointer>(mpPreeditAttributes) );
+ }
+ if ( mnStatusStyle != XIMStatusNone )
+ {
+#if defined LINUX || defined FREEBSD || defined NETBSD || defined OPENBSD || defined DRAGONFLY
+ if ( mpStatusAttributes != nullptr )
+#endif
+ mpAttributes = XVaAddToNestedList( mpAttributes,
+ const_cast<char*>(XNStatusAttributes), static_cast<XPointer>(mpStatusAttributes) );
+ }
+ maContext = XCreateIC( pInputMethod->GetMethod(),
+ XNVaNestedList, mpAttributes,
+ nullptr );
+ }
+
+ if ( maContext == nullptr )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.app", "input context creation failed.");
+#endif
+
+ mbUseable = False;
+
+ if ( mpAttributes != nullptr )
+ XFree( mpAttributes );
+ if ( mpStatusAttributes != nullptr )
+ XFree( mpStatusAttributes );
+ if ( mpPreeditAttributes != nullptr )
+ XFree( mpPreeditAttributes );
+ if ( maClientData.aText.pUnicodeBuffer != nullptr )
+ free ( maClientData.aText.pUnicodeBuffer );
+ if ( maClientData.aText.pCharStyle != nullptr )
+ free ( maClientData.aText.pCharStyle );
+
+ mpAttributes = nullptr;
+ mpStatusAttributes = nullptr;
+ mpPreeditAttributes = nullptr;
+ maClientData.aText.pUnicodeBuffer = nullptr;
+ maClientData.aText.pCharStyle = nullptr;
+ }
+
+ if ( maContext != nullptr)
+ {
+ maDestroyCallback.callback = IC_IMDestroyCallback;
+ maDestroyCallback.client_data = reinterpret_cast<XPointer>(this);
+ XSetICValues( maContext,
+ XNDestroyCallback, &maDestroyCallback,
+ nullptr );
+ }
+}
+
+// In Solaris 8 the status window does not unmap if the frame unmapps, so
+// unmap it the hard way
+
+void
+SalI18N_InputContext::Unmap()
+{
+ UnsetICFocus();
+ maClientData.pFrame = nullptr;
+}
+
+void
+SalI18N_InputContext::Map( SalFrame *pFrame )
+{
+ if( !mbUseable )
+ return;
+
+ if( !pFrame )
+ return;
+
+ if ( maContext == nullptr )
+ {
+ SalI18N_InputMethod *pInputMethod;
+ pInputMethod = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetInputMethod();
+
+ maContext = XCreateIC( pInputMethod->GetMethod(),
+ XNVaNestedList, mpAttributes,
+ nullptr );
+ }
+ if( maClientData.pFrame != pFrame )
+ SetICFocus( pFrame );
+}
+
+// Handle DestroyCallbacks
+// in fact this is a callback called from the XNDestroyCallback
+
+void
+SalI18N_InputContext::HandleDestroyIM()
+{
+ maContext = nullptr; // don't change
+ mbUseable = False;
+}
+
+// make sure, the input method gets all the X-Events it needs, this is only
+// called once on each frame, it relies on a valid maContext
+
+void
+SalI18N_InputContext::ExtendEventMask( ::Window aFocusWindow )
+{
+ unsigned long nIMEventMask;
+ XWindowAttributes aWindowAttributes;
+
+ if ( mbUseable )
+ {
+ Display *pDisplay = XDisplayOfIM( XIMOfIC(maContext) );
+
+ XGetWindowAttributes( pDisplay, aFocusWindow,
+ &aWindowAttributes );
+ XGetICValues ( maContext,
+ XNFilterEvents, &nIMEventMask,
+ nullptr);
+ nIMEventMask |= aWindowAttributes.your_event_mask;
+ XSelectInput ( pDisplay, aFocusWindow, nIMEventMask );
+ }
+}
+
+// tune the styles provided by the input method with the supported one
+
+unsigned int
+SalI18N_InputContext::GetWeightingOfIMStyle( XIMStyle nStyle )
+{
+ struct StyleWeightingT {
+ const XIMStyle nStyle;
+ const unsigned int nWeight;
+ };
+
+ StyleWeightingT const *pWeightPtr;
+ static const StyleWeightingT pWeight[] = {
+ { XIMPreeditCallbacks, 0x10000000 },
+ { XIMPreeditPosition, 0x02000000 },
+ { XIMPreeditArea, 0x01000000 },
+ { XIMPreeditNothing, 0x00100000 },
+ { XIMPreeditNone, 0x00010000 },
+ { XIMStatusCallbacks, 0x1000 },
+ { XIMStatusArea, 0x0100 },
+ { XIMStatusNothing, 0x0010 },
+ { XIMStatusNone, 0x0001 },
+ { 0, 0x0 }
+ };
+
+ int nWeight = 0;
+ for ( pWeightPtr = pWeight; pWeightPtr->nStyle != 0; pWeightPtr++ )
+ {
+ if ( (pWeightPtr->nStyle & nStyle) != 0 )
+ nWeight += pWeightPtr->nWeight;
+ }
+ return nWeight;
+}
+
+bool
+SalI18N_InputContext::IsSupportedIMStyle( XIMStyle nStyle ) const
+{
+ return (nStyle & mnSupportedPreeditStyle)
+ && (nStyle & g_nSupportedStatusStyle);
+}
+
+bool
+SalI18N_InputContext::SupportInputMethodStyle( XIMStyles const *pIMStyles )
+{
+ mnPreeditStyle = 0;
+ mnStatusStyle = 0;
+
+ if ( pIMStyles != nullptr )
+ {
+ int nBestScore = 0;
+ int nActualScore = 0;
+
+ // check whether the XIM supports one of the desired styles
+ // only a single preedit and a single status style must occur
+ // in an input method style. Hideki said so, so i trust him
+ for ( int nStyle = 0; nStyle < pIMStyles->count_styles; nStyle++ )
+ {
+ XIMStyle nProvidedStyle = pIMStyles->supported_styles[ nStyle ];
+ if ( IsSupportedIMStyle(nProvidedStyle) )
+ {
+ nActualScore = GetWeightingOfIMStyle( nProvidedStyle );
+ if ( nActualScore >= nBestScore )
+ {
+ nBestScore = nActualScore;
+ mnPreeditStyle = nProvidedStyle & mnSupportedPreeditStyle;
+ mnStatusStyle = nProvidedStyle & g_nSupportedStatusStyle;
+ }
+ }
+ }
+ }
+
+ return (mnPreeditStyle != 0) && (mnStatusStyle != 0) ;
+}
+
+// handle extended and normal key input
+
+void
+SalI18N_InputContext::CommitKeyEvent(sal_Unicode const * pText, std::size_t nLength)
+{
+ if (nLength == 1 && IsControlCode(pText[0]))
+ return;
+
+ if( maClientData.pFrame )
+ {
+ SalExtTextInputEvent aTextEvent;
+
+ aTextEvent.mpTextAttr = nullptr;
+ aTextEvent.mnCursorPos = nLength;
+ aTextEvent.maText = OUString(pText, nLength);
+ aTextEvent.mnCursorFlags = 0;
+
+ maClientData.pFrame->CallCallback(SalEvent::ExtTextInput, static_cast<void*>(&aTextEvent));
+ maClientData.pFrame->CallCallback(SalEvent::EndExtTextInput, nullptr);
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ SAL_WARN("vcl.app", "CommitKeyEvent without frame.");
+#endif
+}
+
+int
+SalI18N_InputContext::UpdateSpotLocation()
+{
+ if (maContext == nullptr || maClientData.pFrame == nullptr)
+ return -1;
+
+ SalExtTextInputPosEvent aPosEvent;
+ maClientData.pFrame->CallCallback(SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent));
+
+ XPoint aSpot;
+ aSpot.x = aPosEvent.mnX + aPosEvent.mnWidth;
+ aSpot.y = aPosEvent.mnY + aPosEvent.mnHeight;
+
+ XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &aSpot, nullptr);
+ XSetICValues(maContext, XNPreeditAttributes, preedit_attr, nullptr);
+ XFree(preedit_attr);
+
+ return 0;
+}
+
+// set and unset the focus for the Input Context
+// the context may be NULL despite it is usable if the framewindow is
+// in unmapped state
+
+void
+SalI18N_InputContext::SetICFocus( SalFrame* pFocusFrame )
+{
+ if ( !(mbUseable && (maContext != nullptr)) )
+ return;
+
+ maClientData.pFrame = pFocusFrame;
+
+ const SystemEnvData* pEnv = pFocusFrame->GetSystemData();
+ ::Window aClientWindow = pEnv->aShellWindow;
+ ::Window aFocusWindow = pEnv->GetWindowHandle(pFocusFrame);
+
+ XSetICValues( maContext,
+ XNFocusWindow, aFocusWindow,
+ XNClientWindow, aClientWindow,
+ nullptr );
+
+ if( maClientData.aInputEv.mpTextAttr )
+ {
+ sendEmptyCommit(pFocusFrame);
+ // begin preedit again
+ vcl_sal::getSalDisplay(GetGenericUnixSalData())->SendInternalEvent( pFocusFrame, &maClientData.aInputEv, SalEvent::ExtTextInput );
+ }
+
+ XSetICFocus( maContext );
+}
+
+void
+SalI18N_InputContext::UnsetICFocus()
+{
+
+ if ( mbUseable && (maContext != nullptr) )
+ {
+ // cancel an eventual event posted to begin preedit again
+ vcl_sal::getSalDisplay(GetGenericUnixSalData())->CancelInternalEvent( maClientData.pFrame, &maClientData.aInputEv, SalEvent::ExtTextInput );
+ maClientData.pFrame = nullptr;
+ XUnsetICFocus( maContext );
+ }
+}
+
+// multi byte input method only
+
+void
+SalI18N_InputContext::EndExtTextInput()
+{
+ if ( !mbUseable || (maContext == nullptr) || !maClientData.pFrame )
+ return;
+
+ vcl::DeletionListener aDel( maClientData.pFrame );
+ // delete preedit in sal (commit an empty string)
+ sendEmptyCommit( maClientData.pFrame );
+ if( ! aDel.isDeleted() )
+ {
+ // mark previous preedit state again (will e.g. be sent at focus gain)
+ maClientData.aInputEv.mpTextAttr = maClientData.aInputFlags.data();
+ if( static_cast<X11SalFrame*>(maClientData.pFrame)->hasFocus() )
+ {
+ // begin preedit again
+ vcl_sal::getSalDisplay(GetGenericUnixSalData())->SendInternalEvent( maClientData.pFrame, &maClientData.aInputEv, SalEvent::ExtTextInput );
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/i18n_im.cxx b/vcl/unx/generic/app/i18n_im.cxx
new file mode 100644
index 000000000..6a655ca39
--- /dev/null
+++ b/vcl/unx/generic/app/i18n_im.cxx
@@ -0,0 +1,410 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <iostream>
+
+#ifdef LINUX
+# ifndef __USE_XOPEN
+# define __USE_XOPEN
+# endif
+#endif
+
+#include <X11/Xlib.h>
+
+#include <unx/i18n_im.hxx>
+
+#include <osl/thread.h>
+#include <osl/process.h>
+#include <sal/log.hxx>
+
+#include <unx/i18n_cb.hxx>
+
+using namespace vcl;
+
+// kinput2 IME needs special key handling since key release events are filtered in
+// preeditmode and XmbResetIC does not work
+
+namespace {
+
+class XKeyEventOp : public XKeyEvent
+{
+ private:
+ void init();
+
+ public:
+ XKeyEventOp();
+
+ XKeyEventOp& operator= (const XKeyEvent &rEvent);
+ void erase ();
+ bool match (const XKeyEvent &rEvent) const;
+};
+
+}
+
+void
+XKeyEventOp::init()
+{
+ type = 0; /* serial = 0; */
+ send_event = 0; display = nullptr;
+ window = 0; root = 0;
+ subwindow = 0; /* time = 0; */
+ /* x = 0; y = 0; */
+ /* x_root = 0; y_root = 0; */
+ state = 0; keycode = 0;
+ same_screen = 0;
+}
+
+XKeyEventOp::XKeyEventOp()
+{
+ init();
+}
+
+XKeyEventOp&
+XKeyEventOp::operator= (const XKeyEvent &rEvent)
+{
+ type = rEvent.type; /* serial = rEvent.serial; */
+ send_event = rEvent.send_event; display = rEvent.display;
+ window = rEvent.window; root = rEvent.root;
+ subwindow = rEvent.subwindow;/* time = rEvent.time; */
+ /* x = rEvent.x, y = rEvent.y; */
+ /* x_root = rEvent.x_root, y_root = rEvent.y_root; */
+ state = rEvent.state; keycode = rEvent.keycode;
+ same_screen = rEvent.same_screen;
+
+ return *this;
+}
+
+void
+XKeyEventOp::erase ()
+{
+ init();
+}
+
+bool
+XKeyEventOp::match (const XKeyEvent &rEvent) const
+{
+ return ( (type == KeyPress && rEvent.type == KeyRelease)
+ || (type == KeyRelease && rEvent.type == KeyPress ))
+ /* && serial == rEvent.serial */
+ && send_event == rEvent.send_event
+ && display == rEvent.display
+ && window == rEvent.window
+ && root == rEvent.root
+ && subwindow == rEvent.subwindow
+ /* && time == rEvent.time
+ && x == rEvent.x
+ && y == rEvent.y
+ && x_root == rEvent.x_root
+ && y_root == rEvent.y_root */
+ && state == rEvent.state
+ && keycode == rEvent.keycode
+ && same_screen == rEvent.same_screen;
+}
+
+// locale handling
+
+// Locale handling of the operating system layer
+
+static char*
+SetSystemLocale( const char* p_inlocale )
+{
+ char *p_outlocale = setlocale(LC_ALL, p_inlocale);
+
+ SAL_WARN_IF(p_outlocale == nullptr, "vcl.app",
+ "I18N: Operating system doesn't support locale \""
+ << p_inlocale << "\".");
+
+ return p_outlocale;
+}
+
+#ifdef __sun
+static void
+SetSystemEnvironment( const OUString& rLocale )
+{
+ OUString LC_ALL_Var("LC_ALL");
+ osl_setEnvironment(LC_ALL_Var.pData, rLocale.pData);
+
+ OUString LANG_Var("LANG");
+ osl_setEnvironment(LANG_Var.pData, rLocale.pData);
+}
+#endif
+
+static Bool
+IsPosixLocale( const char* p_locale )
+{
+ if ( p_locale == nullptr )
+ return False;
+ if ( (p_locale[ 0 ] == 'C') && (p_locale[ 1 ] == '\0') )
+ return True;
+ if ( strncmp(p_locale, "POSIX", sizeof("POSIX")) == 0 )
+ return True;
+
+ return False;
+}
+
+// Locale handling of the X Window System layer
+
+static Bool
+IsXWindowCompatibleLocale( const char* p_locale )
+{
+ if ( p_locale == nullptr )
+ return False;
+
+ if ( !XSupportsLocale() )
+ {
+ SAL_WARN("vcl.app",
+ "I18N: X Window System doesn't support locale \""
+ << p_locale << "\".");
+ return False;
+ }
+ return True;
+}
+
+// Set the operating system locale prior to trying to open an
+// XIM InputMethod.
+// Handle the cases where the current locale is either not supported by the
+// operating system (LANG=gaga) or by the XWindow system (LANG=aa_ER@saaho)
+// by providing a fallback.
+// Upgrade "C" or "POSIX" to "en_US" locale to allow umlauts and accents
+// see i8988, i9188, i8930, i16318
+// on Solaris the environment needs to be set equivalent to the locale (#i37047#)
+
+void
+SalI18N_InputMethod::SetLocale()
+{
+ // check whether we want an Input Method engine, if we don't we
+ // do not need to set the locale
+ if ( !mbUseable )
+ return;
+
+ char *locale = SetSystemLocale( "" );
+ if ( (!IsXWindowCompatibleLocale(locale)) || IsPosixLocale(locale) )
+ {
+ osl_setThreadTextEncoding (RTL_TEXTENCODING_ISO_8859_1);
+ locale = SetSystemLocale( "en_US" );
+#ifdef __sun
+ SetSystemEnvironment( "en_US" );
+#endif
+ if (! IsXWindowCompatibleLocale(locale))
+ {
+ locale = SetSystemLocale( "C" );
+#ifdef __sun
+ SetSystemEnvironment( "C" );
+#endif
+ if (! IsXWindowCompatibleLocale(locale))
+ mbUseable = False;
+ }
+ }
+
+ // must not fail if mbUseable since XSupportsLocale() asserts success
+ if ( mbUseable && XSetLocaleModifiers("") == nullptr )
+ {
+ SAL_WARN("vcl.app",
+ "I18N: Can't set X modifiers for locale \""
+ << locale << "\".");
+ mbUseable = False;
+ }
+}
+
+Bool
+SalI18N_InputMethod::PosixLocale()
+{
+ if (maMethod)
+ return IsPosixLocale (XLocaleOfIM (maMethod));
+ return False;
+}
+
+// Constructor / Destructor / Initialisation
+
+SalI18N_InputMethod::SalI18N_InputMethod( )
+ : mbUseable( bUseInputMethodDefault )
+ , maMethod( nullptr )
+ , mpStyles( nullptr )
+{
+ maDestroyCallback.callback = nullptr;
+ maDestroyCallback.client_data = nullptr;
+ const char *pUseInputMethod = getenv( "SAL_USEINPUTMETHOD" );
+ if ( pUseInputMethod != nullptr )
+ mbUseable = pUseInputMethod[0] != '\0' ;
+}
+
+SalI18N_InputMethod::~SalI18N_InputMethod()
+{
+ if ( mpStyles != nullptr )
+ XFree( mpStyles );
+ if ( maMethod != nullptr )
+ XCloseIM ( maMethod );
+}
+
+// XXX
+// debug routine: lets have a look at the provided method styles
+
+#if OSL_DEBUG_LEVEL > 1
+
+extern "C" char*
+GetMethodName( XIMStyle nStyle, char *pBuf, int nBufSize)
+{
+ struct StyleName {
+ const XIMStyle nStyle;
+ const char *pName;
+ const int nNameLen;
+ };
+
+ StyleName *pDescPtr;
+ static const StyleName pDescription[] = {
+ { XIMPreeditArea, "PreeditArea ", sizeof("PreeditArea ") },
+ { XIMPreeditCallbacks, "PreeditCallbacks ",sizeof("PreeditCallbacks ")},
+ { XIMPreeditPosition, "PreeditPosition ", sizeof("PreeditPosition ") },
+ { XIMPreeditNothing, "PreeditNothing ", sizeof("PreeditNothing ") },
+ { XIMPreeditNone, "PreeditNone ", sizeof("PreeditNone ") },
+ { XIMStatusArea, "StatusArea ", sizeof("StatusArea ") },
+ { XIMStatusCallbacks, "StatusCallbacks ", sizeof("StatusCallbacks ") },
+ { XIMStatusNothing, "StatusNothing ", sizeof("StatusNothing ") },
+ { XIMStatusNone, "StatusNone ", sizeof("StatusNone ") },
+ { 0, "NULL", 0 }
+ };
+
+ if ( nBufSize > 0 )
+ pBuf[0] = '\0';
+
+ char *pBufPtr = pBuf;
+ for ( pDescPtr = const_cast<StyleName*>(pDescription); pDescPtr->nStyle != 0; pDescPtr++ )
+ {
+ int nSize = pDescPtr->nNameLen - 1;
+ if ( (nStyle & pDescPtr->nStyle) && (nBufSize > nSize) )
+ {
+ strncpy( pBufPtr, pDescPtr->pName, nSize + 1);
+ pBufPtr += nSize;
+ nBufSize -= nSize;
+ }
+ }
+
+ return pBuf;
+}
+
+extern "C" void
+PrintInputStyle( XIMStyles *pStyle )
+{
+ char pBuf[ 128 ];
+ int nBuf = sizeof( pBuf );
+
+ if ( pStyle == NULL )
+ SAL_INFO("vcl.app", "no input method styles.");
+ else
+ for ( int nStyle = 0; nStyle < pStyle->count_styles; nStyle++ )
+ {
+ SAL_INFO("vcl.app", "style #"
+ << nStyle
+ << " = "
+ << GetMethodName(pStyle->supported_styles[nStyle], pBuf, nBuf));
+ }
+}
+
+#endif
+
+// this is the real constructing routine, since locale setting has to be done
+// prior to xopendisplay, the xopenim call has to be delayed
+
+void
+SalI18N_InputMethod::CreateMethod ( Display *pDisplay )
+{
+ if ( mbUseable )
+ {
+ maMethod = XOpenIM(pDisplay, nullptr, nullptr, nullptr);
+
+ if ((maMethod == nullptr) && (getenv("XMODIFIERS") != nullptr))
+ {
+ OUString envVar("XMODIFIERS");
+ osl_clearEnvironment(envVar.pData);
+ XSetLocaleModifiers("");
+ maMethod = XOpenIM(pDisplay, nullptr, nullptr, nullptr);
+ }
+
+ if ( maMethod != nullptr )
+ {
+ if ( XGetIMValues(maMethod, XNQueryInputStyle, &mpStyles, nullptr)
+ != nullptr)
+ mbUseable = False;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "Creating Mono-Lingual InputMethod.");
+ PrintInputStyle( mpStyles );
+#endif
+ }
+ else
+ {
+ mbUseable = False;
+ }
+ }
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN_IF(!mbUseable, "vcl.app", "input method creation failed.");
+#endif
+
+ maDestroyCallback.callback = IM_IMDestroyCallback;
+ maDestroyCallback.client_data = reinterpret_cast<XPointer>(this);
+ if (mbUseable && maMethod != nullptr)
+ XSetIMValues(maMethod, XNDestroyCallback, &maDestroyCallback, nullptr);
+}
+
+// give IM the opportunity to look at the event, and possibly hide it
+
+bool
+SalI18N_InputMethod::FilterEvent( XEvent *pEvent, ::Window window )
+{
+ if (!mbUseable)
+ return False;
+
+ bool bFilterEvent = XFilterEvent (pEvent, window);
+
+ if (pEvent->type != KeyPress && pEvent->type != KeyRelease)
+ return bFilterEvent;
+
+ /*
+ * fix broken key release handling of some IMs
+ */
+ XKeyEvent* pKeyEvent = &(pEvent->xkey);
+ static XKeyEventOp s_aLastKeyPress;
+
+ if (bFilterEvent)
+ {
+ if (pKeyEvent->type == KeyRelease)
+ bFilterEvent = !s_aLastKeyPress.match (*pKeyEvent);
+ s_aLastKeyPress.erase();
+ }
+ else /* (!bFilterEvent) */
+ {
+ if (pKeyEvent->type == KeyPress)
+ s_aLastKeyPress = *pKeyEvent;
+ else
+ s_aLastKeyPress.erase();
+ }
+
+ return bFilterEvent;
+}
+
+void
+SalI18N_InputMethod::HandleDestroyIM()
+{
+ mbUseable = False;
+ maMethod = nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/i18n_keysym.cxx b/vcl/unx/generic/app/i18n_keysym.cxx
new file mode 100644
index 000000000..a77632a3e
--- /dev/null
+++ b/vcl/unx/generic/app/i18n_keysym.cxx
@@ -0,0 +1,358 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <X11/X.h>
+#include <sal/types.h>
+
+#include <unx/i18n_keysym.hxx>
+
+// convert keysyms to unicode
+// for all keysyms with byte1 and byte2 equal zero, and of course only for
+// keysyms that have a unicode counterpart
+
+namespace {
+
+struct keymap_t {
+ const int first; const int last;
+ const sal_Unicode *map;
+};
+
+}
+
+// Latin-1 Byte 3 = 0x00
+const sal_Unicode keymap00_map[] = {
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+ 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+ 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
+ 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
+ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
+ 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
+ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7,
+ 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
+ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7,
+ 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
+ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
+ 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
+ 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff };
+const keymap_t keymap00 = { 32, 255, keymap00_map };
+
+// Latin-2 Byte 3 = 0x01
+const sal_Unicode keymap01_map[] = {
+ 0x0104, 0x02d8, 0x0141, 0x0000, 0x013d, 0x015a, 0x0000, 0x0000,
+ 0x0160, 0x015e, 0x0164, 0x0179, 0x0000, 0x017d, 0x017b, 0x0000,
+ 0x0105, 0x02db, 0x0142, 0x0000, 0x013e, 0x015b, 0x02c7, 0x0000,
+ 0x0161, 0x015f, 0x0165, 0x017a, 0x02dd, 0x017e, 0x017c, 0x0154,
+ 0x0000, 0x0000, 0x0102, 0x0000, 0x0139, 0x0106, 0x0000, 0x010c,
+ 0x0000, 0x0118, 0x0000, 0x011a, 0x0000, 0x0000, 0x010e, 0x0110,
+ 0x0143, 0x0147, 0x0000, 0x0000, 0x0150, 0x0000, 0x0000, 0x0158,
+ 0x016e, 0x0000, 0x0170, 0x0000, 0x0000, 0x0162, 0x0000, 0x0155,
+ 0x0000, 0x0000, 0x0103, 0x0000, 0x013a, 0x0107, 0x0000, 0x010d,
+ 0x0000, 0x0119, 0x0000, 0x011b, 0x0000, 0x0000, 0x010f, 0x0111,
+ 0x0144, 0x0148, 0x0000, 0x0000, 0x0151, 0x0000, 0x0000, 0x0159,
+ 0x016f, 0x0000, 0x0171, 0x0000, 0x0000, 0x0163, 0x02d9 };
+const keymap_t keymap01 = { 161, 255, keymap01_map };
+
+// Latin-3 Byte 3 = 0x02
+const sal_Unicode keymap02_map[] = {
+ 0x0126, 0x0000, 0x0000, 0x0000, 0x0000, 0x0124, 0x0000, 0x0000,
+ 0x0130, 0x0000, 0x011e, 0x0134, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0127, 0x0000, 0x0000, 0x0000, 0x0000, 0x0125, 0x0000, 0x0000,
+ 0x0131, 0x0000, 0x011f, 0x0135, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x010a, 0x0108, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0120, 0x0000, 0x0000, 0x011c,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x016c, 0x015c, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x010b, 0x0109, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0121, 0x0000, 0x0000, 0x011d,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x016d, 0x015d };
+const keymap_t keymap02 = { 161, 254, keymap02_map };
+
+// Latin-4 Byte 3 = 0x03
+const sal_Unicode keymap03_map[] = {
+ 0x0138, 0x0156, 0x0000, 0x0128, 0x013b, 0x0000, 0x0000, 0x0000,
+ 0x0112, 0x0122, 0x0166, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0157, 0x0000, 0x0129, 0x013c, 0x0000, 0x0000, 0x0000,
+ 0x0113, 0x0123, 0x0167, 0x014a, 0x0000, 0x014b, 0x0100, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x012e, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0116, 0x0000, 0x0000, 0x012a, 0x0000, 0x0145,
+ 0x014c, 0x0136, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0172,
+ 0x0000, 0x0000, 0x0000, 0x0168, 0x016a, 0x0000, 0x0101, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x012f, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0117, 0x0000, 0x0000, 0x012b, 0x0000, 0x0146,
+ 0x014d, 0x0137, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0173,
+ 0x0000, 0x0000, 0x0000, 0x0169, 0x016b };
+const keymap_t keymap03 = { 162, 254, keymap03_map };
+
+// Kana Byte 3 = 0x04
+const sal_Unicode keymap04_map[] = {
+ 0x203e, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x3002, 0x300c, 0x300d, 0x3001, 0x30fb,
+ 0x30f2, 0x30a1, 0x30a3, 0x30a5, 0x30a7, 0x30a9, 0x30e3, 0x30e5,
+ 0x30e7, 0x30c3, 0x30fc, 0x30a2, 0x30a4, 0x30a6, 0x30a8, 0x30aa,
+ 0x30ab, 0x30ad, 0x30af, 0x30b1, 0x30b3, 0x30b5, 0x30b7, 0x30b9,
+ 0x30bb, 0x30bd, 0x30bf, 0x30c1, 0x30c4, 0x30c6, 0x30c8, 0x30ca,
+ 0x30cb, 0x30cc, 0x30cd, 0x30ce, 0x30cf, 0x30d2, 0x30d5, 0x30d8,
+ 0x30db, 0x30de, 0x30df, 0x30e0, 0x30e1, 0x30e2, 0x30e4, 0x30e6,
+ 0x30e8, 0x30e9, 0x30ea, 0x30eb, 0x30ec, 0x30ed, 0x30ef, 0x30f3,
+ 0x309b, 0x309c };
+const keymap_t keymap04 = { 126, 223, keymap04_map };
+
+// Arabic Byte 3 = 0x05
+const sal_Unicode keymap05_map[] = {
+ 0x060c, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x061b,
+ 0x0000, 0x0000, 0x0000, 0x061f, 0x0000, 0x0621, 0x0622, 0x0623,
+ 0x0624, 0x0625, 0x0626, 0x0627, 0x0628, 0x0629, 0x062a, 0x062b,
+ 0x062c, 0x062d, 0x062e, 0x062f, 0x0630, 0x0631, 0x0632, 0x0633,
+ 0x0634, 0x0635, 0x0636, 0x0637, 0x0638, 0x0639, 0x063a, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0640, 0x0641, 0x0642, 0x0643,
+ 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064a, 0x064b,
+ 0x064c, 0x064d, 0x064e, 0x064f, 0x0650, 0x0651, 0x0652 };
+const keymap_t keymap05 = { 172, 242, keymap05_map };
+
+// Cyrillic Byte 3 = 0x06
+const sal_Unicode keymap06_map[] = {
+ 0x0452, 0x0453, 0x0451, 0x0454, 0x0455, 0x0456, 0x0457, 0x0458,
+ 0x0459, 0x045a, 0x045b, 0x045c, 0x0000, 0x045e, 0x045f, 0x2116,
+ 0x0402, 0x0403, 0x0401, 0x0404, 0x0405, 0x0406, 0x0407, 0x0408,
+ 0x0409, 0x040a, 0x040b, 0x040c, 0x0000, 0x040e, 0x040f, 0x044e,
+ 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, 0x0445,
+ 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f,
+ 0x044f, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, 0x044c,
+ 0x044b, 0x0437, 0x0448, 0x044d, 0x0449, 0x0447, 0x044a, 0x042e,
+ 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, 0x0425,
+ 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f,
+ 0x042f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, 0x042c,
+ 0x042b, 0x0417, 0x0428, 0x042d, 0x0429, 0x0427, 0x042a };
+const keymap_t keymap06 = { 161, 255, keymap06_map };
+
+// Greek Byte 3 = 0x07
+const sal_Unicode keymap07_map[] = {
+ 0x0386, 0x0388, 0x0389, 0x038a, 0x03aa, 0x0000, 0x038c, 0x038e,
+ 0x03ab, 0x0000, 0x038f, 0x0000, 0x0000, 0x0385, 0x2015, 0x0000,
+ 0x03ac, 0x03ad, 0x03ae, 0x03af, 0x03ca, 0x0390, 0x03cc, 0x03cd,
+ 0x03cb, 0x03b0, 0x03ce, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398,
+ 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, 0x03a0,
+ 0x03a1, 0x03a3, 0x0000, 0x03a4, 0x03a5, 0x03a6, 0x03a7, 0x03a8,
+ 0x03a9, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, 0x03b8,
+ 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, 0x03c0,
+ 0x03c1, 0x03c3, 0x03c2, 0x03c4, 0x03c5, 0x03c6, 0x03c7, 0x03c8,
+ 0x03c9 };
+const keymap_t keymap07 = { 161, 249, keymap07_map };
+
+// Technical Byte 3 = 0x08
+const sal_Unicode keymap08_map[] = {
+ 0x23b7, 0x250c, 0x2500, 0x2320, 0x2321, 0x2502, 0x23a1, 0x23a3,
+ 0x23a4, 0x23a6, 0x239b, 0x239d, 0x239e, 0x23a0, 0x23a8, 0x23ac,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x2264, 0x2260, 0x2265, 0x222b, 0x2234,
+ 0x221d, 0x221e, 0x0000, 0x0000, 0x2207, 0x0000, 0x0000, 0x223c,
+ 0x2243, 0x0000, 0x0000, 0x0000, 0x21d4, 0x21d2, 0x2261, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x221a, 0x0000, 0x0000,
+ 0x0000, 0x2282, 0x2283, 0x2229, 0x222a, 0x2227, 0x2228, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2202, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0192, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x2190, 0x2191, 0x2192, 0x2193 };
+const keymap_t keymap08 = { 161, 254, keymap08_map };
+
+// Special Byte 3 = 0x09
+const sal_Unicode keymap09_map[] = {
+ 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x0000, 0x0000,
+ 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba,
+ 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c,
+ 0x2502 };
+const keymap_t keymap09 = { 224, 248, keymap09_map };
+
+// Publishing Byte 3 = 0x0a = 10
+const sal_Unicode keymap10_map[] = {
+ 0x2003, 0x2002, 0x2004, 0x2005, 0x2007, 0x2008, 0x2009, 0x200a,
+ 0x2014, 0x2013, 0x0000, 0x0000, 0x0000, 0x2026, 0x2025, 0x2153,
+ 0x2154, 0x2155, 0x2156, 0x2157, 0x2158, 0x2159, 0x215a, 0x2105,
+ 0x0000, 0x0000, 0x2012, 0x2329, 0x0000, 0x232a, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x215b, 0x215c, 0x215d, 0x215e, 0x0000, 0x0000,
+ 0x2122, 0x2613, 0x0000, 0x25c1, 0x25b7, 0x25cb, 0x25af, 0x2018,
+ 0x2019, 0x201c, 0x201d, 0x211e, 0x0000, 0x2032, 0x2033, 0x0000,
+ 0x271d, 0x0000, 0x25ac, 0x25c0, 0x25b6, 0x25cf, 0x25ae, 0x25e6,
+ 0x25ab, 0x25ad, 0x25b3, 0x25bd, 0x2606, 0x2022, 0x25aa, 0x25b2,
+ 0x25bc, 0x261c, 0x261e, 0x2663, 0x2666, 0x2665, 0x0000, 0x2720,
+ 0x2020, 0x2021, 0x2713, 0x2717, 0x266f, 0x266d, 0x2642, 0x2640,
+ 0x260e, 0x2315, 0x2117, 0x2038, 0x201a, 0x201e };
+const keymap_t keymap10 = { 161, 254, keymap10_map };
+
+// APL Byte 3 = 0x0b = 11
+const sal_Unicode keymap11_map[] = {
+ 0x003c, 0x0000, 0x0000, 0x003e, 0x0000, 0x2228, 0x2227, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x00af, 0x0000, 0x22a5,
+ 0x2229, 0x230a, 0x0000, 0x005f, 0x0000, 0x0000, 0x0000, 0x2218,
+ 0x0000, 0x2395, 0x0000, 0x22a4, 0x25cb, 0x0000, 0x0000, 0x0000,
+ 0x2308, 0x0000, 0x0000, 0x222a, 0x0000, 0x2283, 0x0000, 0x2282,
+ 0x0000, 0x22a2, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x22a3 };
+const keymap_t keymap11 = { 163, 252, keymap11_map };
+
+// Hebrew Byte 3 = 0x0c = 12
+const sal_Unicode keymap12_map[] = {
+ 0x2017, 0x05d0, 0x05d1, 0x05d2, 0x05d3, 0x05d4, 0x05d5, 0x05d6,
+ 0x05d7, 0x05d8, 0x05d9, 0x05da, 0x05db, 0x05dc, 0x05dd, 0x05de,
+ 0x05df, 0x05e0, 0x05e1, 0x05e2, 0x05e3, 0x05e4, 0x05e5, 0x05e6,
+ 0x05e7, 0x05e8, 0x05e9, 0x05ea };
+const keymap_t keymap12 = { 223, 250, keymap12_map };
+
+// Thai Byte 3 = 0x0d = 13
+const sal_Unicode keymap13_map[] = {
+ 0x0e01, 0x0e02, 0x0e03, 0x0e04, 0x0e05, 0x0e06, 0x0e07, 0x0e08,
+ 0x0e09, 0x0e0a, 0x0e0b, 0x0e0c, 0x0e0d, 0x0e0e, 0x0e0f, 0x0e10,
+ 0x0e11, 0x0e12, 0x0e13, 0x0e14, 0x0e15, 0x0e16, 0x0e17, 0x0e18,
+ 0x0e19, 0x0e1a, 0x0e1b, 0x0e1c, 0x0e1d, 0x0e1e, 0x0e1f, 0x0e20,
+ 0x0e21, 0x0e22, 0x0e23, 0x0e24, 0x0e25, 0x0e26, 0x0e27, 0x0e28,
+ 0x0e29, 0x0e2a, 0x0e2b, 0x0e2c, 0x0e2d, 0x0e2e, 0x0e2f, 0x0e30,
+ 0x0e31, 0x0e32, 0x0e33, 0x0e34, 0x0e35, 0x0e36, 0x0e37, 0x0e38,
+ 0x0e39, 0x0e3a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0e3f, 0x0e40,
+ 0x0e41, 0x0e42, 0x0e43, 0x0e44, 0x0e45, 0x0e46, 0x0e47, 0x0e48,
+ 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x0e4d, 0x0000, 0x0000, 0x0e50,
+ 0x0e51, 0x0e52, 0x0e53, 0x0e54, 0x0e55, 0x0e56, 0x0e57, 0x0e58,
+ 0x0e59 };
+const keymap_t keymap13 = { 161, 249, keymap13_map };
+
+// Korean Byte 3 = 0x0e = 14
+const sal_Unicode keymap14_map[] = {
+ 0x3131, 0x3132, 0x3133, 0x3134, 0x3135, 0x3136, 0x3137, 0x3138,
+ 0x3139, 0x313a, 0x313b, 0x313c, 0x313d, 0x313e, 0x313f, 0x3140,
+ 0x3141, 0x3142, 0x3143, 0x3144, 0x3145, 0x3146, 0x3147, 0x3148,
+ 0x3149, 0x314a, 0x314b, 0x314c, 0x314d, 0x314e, 0x314f, 0x3150,
+ 0x3151, 0x3152, 0x3153, 0x3154, 0x3155, 0x3156, 0x3157, 0x3158,
+ 0x3159, 0x315a, 0x315b, 0x315c, 0x315d, 0x315e, 0x315f, 0x3160,
+ 0x3161, 0x3162, 0x3163, 0x11a8, 0x11a9, 0x11aa, 0x11ab, 0x11ac,
+ 0x11ad, 0x11ae, 0x11af, 0x11b0, 0x11b1, 0x11b2, 0x11b3, 0x11b4,
+ 0x11b5, 0x11b6, 0x11b7, 0x11b8, 0x11b9, 0x11ba, 0x11bb, 0x11bc,
+ 0x11bd, 0x11be, 0x11bf, 0x11c0, 0x11c1, 0x11c2, 0x316d, 0x3171,
+ 0x3178, 0x317f, 0x3181, 0x3184, 0x3186, 0x318d, 0x318e, 0x11eb,
+ 0x11f0, 0x11f9, 0x0000, 0x0000, 0x0000, 0x0000, 0x20a9 };
+const keymap_t keymap14 = { 161, 255, keymap14_map };
+
+// missing:
+// Latin-8 Byte 3 = 0x12 = 18
+
+// Latin-9 Byte 3 = 0x13 = 19
+const sal_Unicode keymap19_map[] = {
+ 0x0152, 0x0153, 0x0178 };
+const keymap_t keymap19 = { 188, 190, keymap19_map };
+
+// missing:
+// Armenian Byte 3 = 0x14 = 20
+// Georgian Byte 3 = 0x15 = 21
+// Azeri Byte 3 = 0x16 = 22
+// Vietnamese Byte 3 = 0x1e = 30
+
+// Currency Byte 3 = 0x20 = 32
+const sal_Unicode keymap32_map[] = {
+ 0x20a0, 0x20a1, 0x20a2, 0x20a3, 0x20a4, 0x20a5, 0x20a6, 0x20a7,
+ 0x20a8, 0x0000, 0x20aa, 0x20ab, 0x20ac };
+const keymap_t keymap32 = { 160, 172, keymap32_map };
+
+// Keyboard (Keypad mappings) Byte 3 = 0xff = 255
+const sal_Unicode keymap255_map[] = {
+ 0x0020, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x0000, 0x0000, 0x0000, 0x003d };
+const keymap_t keymap255 = { 128, 189, keymap255_map };
+
+#define INITIAL_KEYMAPS 33
+const keymap_t* const p_keymap[INITIAL_KEYMAPS] = {
+ &keymap00, &keymap01, &keymap02, &keymap03, /* 00 -- 03 */
+ &keymap04, &keymap05, &keymap06, &keymap07, /* 04 -- 07 */
+ &keymap08, &keymap09, &keymap10, &keymap11, /* 08 -- 11 */
+ &keymap12, &keymap13, &keymap14, nullptr, /* 12 -- 15 */
+ nullptr, nullptr, nullptr, &keymap19, /* 16 -- 19 */
+ nullptr, nullptr, nullptr, nullptr, /* 20 -- 23 */
+ nullptr, nullptr, nullptr, nullptr, /* 24 -- 27 */
+ nullptr, nullptr, nullptr, nullptr, /* 28 -- 31 */
+ &keymap32 /* 32 */
+};
+
+sal_Unicode
+KeysymToUnicode (KeySym nKeySym)
+{
+ // keysym is already unicode
+ if ((nKeySym & 0xff000000) == 0x01000000)
+ {
+ // strip off group indicator and iso10646 plane
+ // FIXME can't handle chars from surrogate area.
+ if (! (nKeySym & 0x00ff0000) )
+ return static_cast<sal_Unicode>(nKeySym & 0x0000ffff);
+ }
+ // legacy keysyms, switch to appropriate codeset
+ else
+ {
+ unsigned char n_byte1 = (nKeySym & 0xff000000) >> 24;
+ unsigned char n_byte2 = (nKeySym & 0x00ff0000) >> 16;
+ unsigned char n_byte3 = (nKeySym & 0x0000ff00) >> 8;
+ unsigned char n_byte4 = (nKeySym & 0x000000ff);
+
+ if (n_byte1 != 0)
+ return 0;
+ if (n_byte2 != 0)
+ return 0;
+
+ keymap_t const* p_map = nullptr;
+ if (n_byte3 < INITIAL_KEYMAPS)
+ p_map = p_keymap[n_byte3];
+ else if (n_byte3 == 255)
+ p_map = &keymap255;
+
+ if ((p_map != nullptr) && (n_byte4 >= p_map->first) && (n_byte4 <= p_map->last) )
+ return p_map->map[n_byte4 - p_map->first];
+ }
+
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/i18n_xkb.cxx b/vcl/unx/generic/app/i18n_xkb.cxx
new file mode 100644
index 000000000..0fc4d7933
--- /dev/null
+++ b/vcl/unx/generic/app/i18n_xkb.cxx
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <iostream>
+
+#include <sal/log.hxx>
+
+#include <X11/Xlib.h>
+#include <X11/XKBlib.h>
+
+#include <unx/i18n_xkb.hxx>
+
+SalI18N_KeyboardExtension::SalI18N_KeyboardExtension( Display* pDisplay )
+ : mbUseExtension(true)
+ , mnEventBase(0)
+{
+ // allow user to set the default keyboard group idx or to disable the usage
+ // of x keyboard extension at all:
+ // setenv SAL_XKEYBOARDGROUP disables keyboard extension
+ // setenv SAL_XKEYBOARDGROUP 2 sets the keyboard group index to 2
+ // keyboard group index must be in [1,4], may be specified in hex or decimal
+ static char *pUseKeyboardExtension = getenv( "SAL_XKEYBOARDGROUP" );
+ if ( pUseKeyboardExtension != nullptr )
+ {
+ mbUseExtension = pUseKeyboardExtension[0] != '\0' ;
+ }
+
+ // query XServer support for XKB Extension,
+ // do not call XQueryExtension() / XInitExtension() due to possible version
+ // clashes !
+ if ( mbUseExtension )
+ {
+ int nMajorExtOpcode;
+ int nExtMajorVersion = XkbMajorVersion;
+ int nExtMinorVersion = XkbMinorVersion;
+ int nErrorBase = 0;
+
+ mbUseExtension = XkbQueryExtension( pDisplay,
+ &nMajorExtOpcode, &mnEventBase, &nErrorBase,
+ &nExtMajorVersion, &nExtMinorVersion ) != 0;
+ }
+
+ // query notification for changes of the keyboard group
+ if ( mbUseExtension )
+ {
+ constexpr auto XkbGroupMask = XkbGroupStateMask | XkbGroupBaseMask
+ | XkbGroupLatchMask | XkbGroupLockMask;
+
+ mbUseExtension = XkbSelectEventDetails( pDisplay,
+ XkbUseCoreKbd, XkbStateNotify, XkbGroupMask, XkbGroupMask );
+ }
+
+ // query initial keyboard group
+ if ( mbUseExtension )
+ {
+ XkbStateRec aStateRecord;
+ XkbGetState( pDisplay, XkbUseCoreKbd, &aStateRecord );
+ }
+}
+
+void
+SalI18N_KeyboardExtension::Dispatch( XEvent* pEvent )
+{
+ // must the event be handled?
+ if ( !mbUseExtension
+ || (pEvent->type != mnEventBase) )
+ return;
+
+ // only handle state notify events for now, and only interested
+ // in group details
+ sal_uInt32 nXKBType = reinterpret_cast<XkbAnyEvent*>(pEvent)->xkb_type;
+ switch ( nXKBType )
+ {
+ case XkbStateNotify:
+ break;
+
+ default:
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.app", "Got unrequested XkbAnyEvent "
+ << std::hex << std::showbase
+ << static_cast<unsigned int>(nXKBType)
+ << "/" << std::dec
+ << static_cast<int>(nXKBType));
+#endif
+ break;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/keysymnames.cxx b/vcl/unx/generic/app/keysymnames.cxx
new file mode 100644
index 000000000..16ffaa4b9
--- /dev/null
+++ b/vcl/unx/generic/app/keysymnames.cxx
@@ -0,0 +1,509 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <o3tl/string_view.hxx>
+#include <unx/saldisp.hxx>
+#include <X11/keysym.h>
+#include <sal/macros.h>
+
+#if !defined (SunXK_Undo)
+#define SunXK_Undo 0x0000FF65 // XK_Undo
+#define SunXK_Again 0x0000FF66 // XK_Redo
+#define SunXK_Find 0x0000FF68 // XK_Find
+#define SunXK_Stop 0x0000FF69 // XK_Cancel
+#define SunXK_Props 0x1005FF70
+#define SunXK_Front 0x1005FF71
+#define SunXK_Copy 0x1005FF72
+#define SunXK_Open 0x1005FF73
+#define SunXK_Paste 0x1005FF74
+#define SunXK_Cut 0x1005FF75
+#endif
+
+#include <string.h>
+#include <rtl/ustring.hxx>
+
+namespace vcl_sal {
+
+ namespace {
+
+ struct KeysymNameReplacement
+ {
+ KeySym aSymbol;
+ const char* pName;
+ };
+
+ struct KeyboardReplacements
+ {
+ const char* pLangName;
+ const KeysymNameReplacement* pReplacements;
+ int nReplacements;
+ };
+
+ }
+
+ // CAUTION CAUTION CAUTION
+ // every string value in the replacements tables must be in UTF8
+ // be careful with your editor !
+
+ const struct KeysymNameReplacement aImplReplacements_English[] =
+ {
+ { XK_Control_L, "Ctrl" },
+ { XK_Control_R, "Ctrl" },
+ { XK_Escape, "Esc" },
+ { XK_space, "Space" },
+ { XK_Page_Up, "PgUp"},
+ { XK_Page_Down, "PgDn"},
+ { XK_grave, "`"}
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Turkish[] =
+ {
+ { XK_Control_L, "Ctrl" },
+ { XK_Control_R, "Ctrl" },
+ { XK_Right, "Sa\304\237" },
+ { XK_Left, "Sol" },
+ { XK_Up, "Yukar\304\261" },
+ { XK_Down, "A\305\237a\304\237\304\261" },
+ { XK_space, "Bo\305\237luk" }
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Russian[] =
+ {
+ { XK_Right, "\320\222\320\277\321\200\320\260\320\262\320\276" },
+ { XK_Left, "\320\222\320\273\320\265\320\262\320\276" },
+ { XK_Up, "\320\222\320\262\320\265\321\200\321\205" },
+ { XK_Down, "\320\222\320\275\320\270\320\267" },
+ { XK_space, "\320\237\321\200\320\276\320\261\320\265\320\273" }
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_German[] =
+ {
+ { XK_Control_L, "Strg" },
+ { XK_Control_R, "Strg" },
+ { XK_Shift_L, "Umschalt" },
+ { XK_Shift_R, "Umschalt" },
+ { XK_Alt_L, "Alt" },
+ { XK_Alt_R, "Alt Gr" },
+ { XK_Page_Up, "Bild auf" },
+ { XK_Page_Down, "Bild ab" },
+ { XK_End, "Ende" },
+ { XK_Home, "Pos 1" },
+ { XK_Insert, "Einfg" },
+ { XK_Delete, "Entf" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "Rechts" },
+ { XK_Left, "Links" },
+ { XK_Up, "Oben" },
+ { XK_Down, "Unten" },
+ { XK_BackSpace, "R\303\274ckschritt" },
+ { XK_Return, "Eingabe" },
+ { XK_slash, "Schr\303\244gstrich" },
+ { XK_space, "Leertaste" },
+ { SunXK_Stop, "Stop" },
+ { SunXK_Again, "Wiederholen" },
+ { SunXK_Props, "Eigenschaften" },
+ { SunXK_Undo, "Zur\303\274cknehmen" },
+ { SunXK_Front, "Vordergrund" },
+ { SunXK_Copy, "Kopieren" },
+ { SunXK_Open, "\303\226ffnen" },
+ { SunXK_Paste, "Einsetzen" },
+ { SunXK_Find, "Suchen" },
+ { SunXK_Cut, "Ausschneiden" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_French[] =
+ {
+ { XK_Shift_L, "Maj" },
+ { XK_Shift_R, "Maj" },
+ { XK_Page_Up, "Pg. Pr\303\251c" },
+ { XK_Page_Down, "Pg. Suiv" },
+ { XK_End, "Fin" },
+ { XK_Home, "Origine" },
+ { XK_Insert, "Ins\303\251rer" },
+ { XK_Delete, "Suppr" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "Droite" },
+ { XK_Left, "Gauche" },
+ { XK_Up, "Haut" },
+ { XK_Down, "Bas" },
+ { XK_BackSpace, "Ret. Arr" },
+ { XK_Return, "Retour" },
+ { XK_space, "Espace" },
+ { XK_KP_Enter, "Entr\303\251e" },
+ { SunXK_Stop, "Stop" },
+ { SunXK_Again, "Encore" },
+ { SunXK_Props, "Props" },
+ { SunXK_Undo, "Annuler" },
+ { SunXK_Front, "Devant" },
+ { SunXK_Copy, "Copy" },
+ { SunXK_Open, "Ouvrir" },
+ { SunXK_Paste, "Coller" },
+ { SunXK_Find, "Cher." },
+ { SunXK_Cut, "Couper" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Italian[] =
+ {
+ { XK_Shift_L, "Maiusc" },
+ { XK_Shift_R, "Maiusc" },
+ { XK_Page_Up, "PgSu" },
+ { XK_Page_Down, "PgGiu" },
+ { XK_End, "Fine" },
+ { XK_Insert, "Ins" },
+ { XK_Delete, "Canc" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "A destra" },
+ { XK_Left, "A sinistra" },
+ { XK_Up, "Sposta verso l'alto" },
+ { XK_Down, "Sposta verso il basso" },
+ { XK_BackSpace, "Backspace" },
+ { XK_Return, "Invio" },
+ { XK_space, "Spazio" },
+ { SunXK_Stop, "Stop" },
+ { SunXK_Again, "Ancora" },
+ { SunXK_Props, "Propriet\303\240" },
+ { SunXK_Undo, "Annulla" },
+ { SunXK_Front, "Davanti" },
+ { SunXK_Copy, "Copia" },
+ { SunXK_Open, "Apri" },
+ { SunXK_Paste, "Incolla" },
+ { SunXK_Find, "Trova" },
+ { SunXK_Cut, "Taglia" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Dutch[] =
+ {
+ { XK_Page_Up, "PageUp" },
+ { XK_Page_Down, "PageDown" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "Rechts" },
+ { XK_Left, "Links" },
+ { XK_Up, "Boven" },
+ { XK_Down, "Onder" },
+ { XK_BackSpace, "Backspace" },
+ { XK_Return, "Return" },
+ { XK_space, "Spatiebalk" },
+ { SunXK_Stop, "Stop" },
+ { SunXK_Again, "Again" },
+ { SunXK_Props, "Props" },
+ { SunXK_Undo, "Undo" },
+ { SunXK_Front, "Front" },
+ { SunXK_Copy, "Copy" },
+ { SunXK_Open, "Open" },
+ { SunXK_Paste, "Paste" },
+ { SunXK_Find, "Find" },
+ { SunXK_Cut, "Cut" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Norwegian[] =
+ {
+ { XK_Shift_L, "Skift" },
+ { XK_Shift_R, "Skift" },
+ { XK_Page_Up, "PageUp" },
+ { XK_Page_Down, "PageDown" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "H\303\270yre" },
+ { XK_Left, "Venstre" },
+ { XK_Up, "Opp" },
+ { XK_Down, "Ned" },
+ { XK_BackSpace, "Tilbake" },
+ { XK_Return, "Enter" },
+ { SunXK_Stop, "Avbryt" },
+ { SunXK_Again, "Gjenta" },
+ { SunXK_Props, "Egenskaper" },
+ { SunXK_Undo, "Angre" },
+ { SunXK_Front, "Front" },
+ { SunXK_Copy, "Kopi" },
+ { SunXK_Open, "\303\205pne" },
+ { SunXK_Paste, "Lim" },
+ { SunXK_Find, "S\303\270k" },
+ { SunXK_Cut, "Klipp" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Swedish[] =
+ {
+ { XK_Shift_L, "Skift" },
+ { XK_Shift_R, "Skift" },
+ { XK_Page_Up, "PageUp" },
+ { XK_Page_Down, "PageDown" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "H\303\266ger" },
+ { XK_Left, "V\303\244nster" },
+ { XK_Up, "Up" },
+ { XK_Down, "Ned" },
+ { XK_BackSpace, "Backsteg" },
+ { XK_Return, "Retur" },
+ { XK_space, "Blank" },
+ { SunXK_Stop, "Avbryt" },
+ { SunXK_Again, "Upprepa" },
+ { SunXK_Props, "Egenskaper" },
+ { SunXK_Undo, "\303\205ngra" },
+ { SunXK_Front, "Fram" },
+ { SunXK_Copy, "Kopiera" },
+ { SunXK_Open, "\303\226ppna" },
+ { SunXK_Paste, "Klistra in" },
+ { SunXK_Find, "S\303\266k" },
+ { SunXK_Cut, "Klipp ut" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Portuguese[] =
+ {
+ { XK_Page_Up, "PageUp" },
+ { XK_Page_Down, "PageDown" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "Direita" },
+ { XK_Left, "Esquerda" },
+ { XK_Up, "Acima" },
+ { XK_Down, "Abaixo" },
+ { XK_BackSpace, "Backspace" },
+ { XK_Return, "Enter" },
+ { XK_slash, "Barra" },
+ { SunXK_Stop, "Stop" },
+ { SunXK_Again, "Again" },
+ { SunXK_Props, "Props" },
+ { SunXK_Undo, "Undo" },
+ { SunXK_Front, "Front" },
+ { SunXK_Copy, "Copy" },
+ { SunXK_Open, "Open" },
+ { SunXK_Paste, "Paste" },
+ { SunXK_Find, "Find" },
+ { SunXK_Cut, "Cut" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Slovenian[] =
+ {
+ { XK_Control_L, "Krmilka" },
+ { XK_Control_R, "Krmilka" },
+ { XK_Shift_L, "Dvigalka" },
+ { XK_Shift_R, "Dvigalka" },
+ { XK_Alt_L, "Izmenjalka" },
+ { XK_Alt_R, "Desna izmenjalka" },
+ { XK_Page_Up, "Prej\305\241nja stranf" },
+ { XK_Page_Down, "Naslednja stran" },
+ { XK_End, "Konec" },
+ { XK_Home, "Za\304\215etek" },
+ { XK_Insert, "Vstavljalka" },
+ { XK_Delete, "Brisalka" },
+ { XK_Escape, "Ube\305\276nica" },
+ { XK_Right, "Desno" },
+ { XK_Left, "Levo" },
+ { XK_Up, "Navzgor" },
+ { XK_Down, "Navzdol" },
+ { XK_BackSpace, "Vra\304\215alka" },
+ { XK_Return, "Vna\305\241alka" },
+ { XK_slash, "Po\305\241evnica" },
+ { XK_space, "Preslednica" },
+ { SunXK_Stop, "Ustavi" },
+ { SunXK_Again, "Ponovi" },
+ { SunXK_Props, "Lastnosti" },
+ { SunXK_Undo, "Razveljavi" },
+ { SunXK_Front, "Ospredje" },
+ { SunXK_Copy, "Kopiraj" },
+ { SunXK_Open, "Odpri" },
+ { SunXK_Paste, "Prilepi" },
+ { SunXK_Find, "Najdi" },
+ { SunXK_Cut, "Izre\305\276i" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Spanish[] =
+ {
+ { XK_Shift_L, "May\303\272s" },
+ { XK_Shift_R, "May\303\272s" },
+ { XK_Page_Up, "ReP\303\241g" },
+ { XK_Page_Down, "AvP\303\241g" },
+ { XK_End, "Fin" },
+ { XK_Home, "Inicio" },
+ { XK_Delete, "Supr" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "Derecha" },
+ { XK_Left, "Izquierda" },
+ { XK_Up, "Arriba" },
+ { XK_Down, "Abajo" },
+ { XK_BackSpace, "Ret" },
+ { XK_Return, "Entrada" },
+ { XK_space, "Espacio" },
+ { XK_KP_Enter, "Intro" },
+ { SunXK_Stop, "Detener" },
+ { SunXK_Again, "Repetir" },
+ { SunXK_Props, "Props" },
+ { SunXK_Undo, "Anular" },
+ { SunXK_Front, "Delante" },
+ { SunXK_Copy, "Copiar" },
+ { SunXK_Open, "Abrir" },
+ { SunXK_Paste, "Pegar" },
+ { SunXK_Find, "Buscar" },
+ { SunXK_Cut, "Cortar" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Estonian[] =
+ {
+ { XK_Page_Up, "PgUp" },
+ { XK_Page_Down, "PgDown" },
+ { XK_End, "End" },
+ { XK_Home, "Home" },
+ { XK_Insert, "Ins" },
+ { XK_Delete, "Del" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "Nool paremale" },
+ { XK_Left, "Nool vasakule" },
+ { XK_Up, "Nool \303\274les" },
+ { XK_Down, "Nool alla" },
+ { XK_BackSpace, "Tagasil\303\274ke" },
+ { XK_Return, "Enter" },
+ { XK_slash, "Kaldkriips" },
+ { XK_space, "T\303\274hik" },
+ { XK_asterisk, "T\303\244rn" },
+ { SunXK_Stop, "Peata" },
+ { SunXK_Again, "Korda" },
+ { SunXK_Props, "Omadused" },
+ { SunXK_Undo, "V\303\265ta tagasi" },
+ { SunXK_Front, "Esiplaanile" },
+ { SunXK_Copy, "Kopeeri" },
+ { SunXK_Open, "Ava" },
+ { SunXK_Paste, "Aseta" },
+ { SunXK_Find, "Otsi" },
+ { SunXK_Cut, "L\303\265ika" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Catalan[] =
+ {
+ { XK_Shift_L, "Maj" },
+ { XK_Shift_R, "Maj" },
+ { XK_Page_Up, "Re P\303\240g" },
+ { XK_Page_Down, "Av P\303\240g" },
+ { XK_End, "Fi" },
+ { XK_Home, "Inici" },
+ { XK_Delete, "Supr" },
+ { XK_Escape, "Esc" },
+ { XK_Right, "Dreta" },
+ { XK_Left, "Esquerra" },
+ { XK_Up, "Amunt" },
+ { XK_Down, "Avall" },
+ { XK_BackSpace, "Retroc\303\251s" },
+ { XK_Return, "Retorn" },
+ { XK_space, "Espai" },
+ { XK_KP_Enter, "Retorn" },
+ { SunXK_Stop, "Atura" },
+ { SunXK_Again, "Repeteix" },
+ { SunXK_Props, "Props" },
+ { SunXK_Undo, "Desf\303\251s" },
+ { SunXK_Front, "Davant" },
+ { SunXK_Copy, "C\303\262pia" },
+ { SunXK_Open, "Obre" },
+ { SunXK_Paste, "Enganxa" },
+ { SunXK_Find, "Cerca" },
+ { SunXK_Cut, "Retalla" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Lithuanian[] =
+ {
+ { XK_Control_L, "Vald" },
+ { XK_Control_R, "Vald" },
+ { XK_Shift_L, "Lyg2" },
+ { XK_Shift_R, "Lyg2" },
+ { XK_Alt_L, "Alt" },
+ { XK_Alt_R, "Lyg3" },
+ { XK_Page_Up, "Psl\342\206\221" },
+ { XK_Page_Down, "Psl\342\206\223" },
+ { XK_End, "Pab" },
+ { XK_Home, "Prad" },
+ { XK_Insert, "\304\256terpti" },
+ { XK_Delete, "\305\240al" },
+ { XK_Escape, "Gr" },
+ { XK_Right, "De\305\241in\304\227n" },
+ { XK_Left, "Kair\304\227n" },
+ { XK_Up, "Auk\305\241tyn" },
+ { XK_Down, "\305\275emyn" },
+ { XK_BackSpace, "Naikinti" },
+ { XK_Return, "\304\256vesti" },
+ { XK_asterisk, "\305\275vaig\305\276dut\304\227" },
+ { XK_slash, "De\305\241ininis br\305\253k\305\241nys" },
+ { XK_space, "Tarpas" },
+ { SunXK_Stop, "Stabdyti" },
+ { SunXK_Again, "Kartoti" },
+ { SunXK_Props, "Savyb\304\227s" },
+ { SunXK_Undo, "At\305\241aukti" },
+ { SunXK_Front, "Priekinis planas" },
+ { SunXK_Copy, "Kopijuoti" },
+ { SunXK_Open, "Atverti" },
+ { SunXK_Paste, "\304\256d\304\227ti" },
+ { SunXK_Find, "Ie\305\241koti" },
+ { SunXK_Cut, "I\305\241kirpti" },
+ };
+
+ const struct KeysymNameReplacement aImplReplacements_Hungarian[] =
+ {
+ { XK_Right, "Jobbra" },
+ { XK_Left, "Balra" },
+ { XK_Up, "Fel" },
+ { XK_Down, "Le" },
+ { XK_Return, "Enter" },
+ { XK_space, "Sz\303\263k\303\266z" },
+ { XK_asterisk, "Csillag" },
+ { XK_slash, "Oszt\303\241sjel" },
+ };
+
+ const struct KeyboardReplacements aKeyboards[] =
+ {
+ { "ca", aImplReplacements_Catalan, SAL_N_ELEMENTS(aImplReplacements_Catalan) },
+ { "de", aImplReplacements_German, SAL_N_ELEMENTS(aImplReplacements_German) },
+ { "sl", aImplReplacements_Slovenian, SAL_N_ELEMENTS(aImplReplacements_Slovenian) },
+ { "es", aImplReplacements_Spanish, SAL_N_ELEMENTS(aImplReplacements_Spanish) },
+ { "et", aImplReplacements_Estonian, SAL_N_ELEMENTS(aImplReplacements_Estonian) },
+ { "fr", aImplReplacements_French, SAL_N_ELEMENTS(aImplReplacements_French) },
+ { "hu", aImplReplacements_Hungarian, SAL_N_ELEMENTS(aImplReplacements_Hungarian) },
+ { "it", aImplReplacements_Italian, SAL_N_ELEMENTS(aImplReplacements_Italian) },
+ { "lt", aImplReplacements_Lithuanian, SAL_N_ELEMENTS(aImplReplacements_Lithuanian) },
+ { "nl", aImplReplacements_Dutch, SAL_N_ELEMENTS(aImplReplacements_Dutch) },
+ { "no", aImplReplacements_Norwegian, SAL_N_ELEMENTS(aImplReplacements_Norwegian) },
+ { "pt", aImplReplacements_Portuguese, SAL_N_ELEMENTS(aImplReplacements_Portuguese) },
+ { "ru", aImplReplacements_Russian, SAL_N_ELEMENTS(aImplReplacements_Russian) },
+ { "sv", aImplReplacements_Swedish, SAL_N_ELEMENTS(aImplReplacements_Swedish) },
+ { "tr", aImplReplacements_Turkish, SAL_N_ELEMENTS(aImplReplacements_Turkish) },
+ };
+
+ // translate keycodes, used within the displayed menu shortcuts
+ OUString getKeysymReplacementName( std::u16string_view pLang, KeySym nSymbol )
+ {
+ for(const auto & rKeyboard : aKeyboards)
+ {
+ if( o3tl::equalsAscii( pLang, rKeyboard.pLangName ) )
+ {
+ const struct KeysymNameReplacement* pRepl = rKeyboard.pReplacements;
+ for( int m = rKeyboard.nReplacements ; m ; )
+ {
+ if( nSymbol == pRepl[--m].aSymbol )
+ return OUString( pRepl[m].pName, strlen(pRepl[m].pName), RTL_TEXTENCODING_UTF8 );
+ }
+ }
+ }
+
+ // try english fallbacks
+ const struct KeysymNameReplacement* pRepl = aImplReplacements_English;
+ for( int m = SAL_N_ELEMENTS(aImplReplacements_English); m ; )
+ {
+ if( nSymbol == pRepl[--m].aSymbol )
+ return OUString( pRepl[m].pName, strlen(pRepl[m].pName), RTL_TEXTENCODING_UTF8 );
+ }
+
+ return OUString();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/randrwrapper.cxx b/vcl/unx/generic/app/randrwrapper.cxx
new file mode 100644
index 000000000..431c70ae3
--- /dev/null
+++ b/vcl/unx/generic/app/randrwrapper.cxx
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifdef USE_RANDR
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrandr.h>
+
+#include <sal/log.hxx>
+
+namespace
+{
+
+class RandRWrapper
+{
+ bool m_bValid;
+
+ explicit RandRWrapper(Display*);
+public:
+ static RandRWrapper& get(Display*);
+ static void releaseWrapper();
+
+ Bool XRRQueryExtension(Display* i_pDisp, int* o_event_base, int* o_error_base )
+ {
+ Bool bRet = False;
+ if( m_bValid )
+ bRet = ::XRRQueryExtension( i_pDisp, o_event_base, o_error_base );
+ return bRet;
+ }
+ XRRScreenConfiguration* XRRGetScreenInfo( Display* i_pDisp, Drawable i_aDrawable )
+ {
+ return m_bValid ? ::XRRGetScreenInfo( i_pDisp, i_aDrawable ) : nullptr;
+ }
+ void XRRFreeScreenConfigInfo( XRRScreenConfiguration* i_pConfig )
+ {
+ if( m_bValid )
+ ::XRRFreeScreenConfigInfo( i_pConfig );
+ }
+ void XRRSelectInput( Display* i_pDisp, ::Window i_window, int i_nMask )
+ {
+ if( m_bValid )
+ ::XRRSelectInput( i_pDisp, i_window, i_nMask );
+ }
+ int XRRUpdateConfiguration( XEvent* i_pEvent )
+ {
+ return m_bValid ? ::XRRUpdateConfiguration( i_pEvent ) : 0;
+ }
+ XRRScreenSize* XRRConfigSizes( XRRScreenConfiguration* i_pConfig, int* o_nSizes )
+ {
+ return m_bValid ? ::XRRConfigSizes( i_pConfig, o_nSizes ) : nullptr;
+ }
+ SizeID XRRConfigCurrentConfiguration( XRRScreenConfiguration* i_pConfig, Rotation* o_pRot )
+ {
+ return m_bValid ? ::XRRConfigCurrentConfiguration( i_pConfig, o_pRot ) : 0;
+ }
+ int XRRRootToScreen( Display *dpy, ::Window root )
+ {
+ return m_bValid ? ::XRRRootToScreen( dpy, root ) : -1;
+ }
+};
+
+RandRWrapper::RandRWrapper( Display* pDisplay ) :
+ m_bValid( true )
+{
+ int nEventBase = 0, nErrorBase = 0;
+ if( !XRRQueryExtension( pDisplay, &nEventBase, &nErrorBase ) )
+ m_bValid = false;
+}
+
+RandRWrapper* pWrapper = nullptr;
+
+RandRWrapper& RandRWrapper::get( Display* i_pDisplay )
+{
+ if( ! pWrapper )
+ pWrapper = new RandRWrapper( i_pDisplay );
+ return *pWrapper;
+}
+
+void RandRWrapper::releaseWrapper()
+{
+ delete pWrapper;
+ pWrapper = nullptr;
+}
+
+} // namespace
+
+#endif
+
+#include <unx/saldisp.hxx>
+#if OSL_DEBUG_LEVEL > 1
+#include <cstdio>
+#endif
+
+void SalDisplay::InitRandR( ::Window aRoot ) const
+{
+ #ifdef USE_RANDR
+ RandRWrapper::get( GetDisplay() ).XRRSelectInput( GetDisplay(), aRoot, RRScreenChangeNotifyMask );
+ #else
+ (void)this;
+ (void)aRoot;
+ #endif
+}
+
+void SalDisplay::DeInitRandR()
+{
+ #ifdef USE_RANDR
+ RandRWrapper::releaseWrapper();
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "SalDisplay::DeInitRandR().");
+#endif
+ #endif
+}
+
+void SalDisplay::processRandREvent( XEvent* pEvent )
+{
+#ifdef USE_RANDR
+ XConfigureEvent* pCnfEvent=reinterpret_cast<XConfigureEvent*>(pEvent);
+ if( !pWrapper || pWrapper->XRRRootToScreen(GetDisplay(),pCnfEvent->window) == -1 )
+ return;
+
+ int nRet = pWrapper->XRRUpdateConfiguration( pEvent );
+ if( nRet != 1 || pEvent->type == ConfigureNotify) // this should then be a XRRScreenChangeNotifyEvent
+ return;
+
+ // update screens
+ bool bNotify = false;
+ for(ScreenData & rScreen : m_aScreens)
+ {
+ if( rScreen.m_bInit )
+ {
+ XRRScreenConfiguration *pConfig = nullptr;
+ XRRScreenSize *pSizes = nullptr;
+ int nSizes = 0;
+ Rotation nRot = 0;
+ SizeID nId = 0;
+
+ pConfig = pWrapper->XRRGetScreenInfo( GetDisplay(), rScreen.m_aRoot );
+ nId = pWrapper->XRRConfigCurrentConfiguration( pConfig, &nRot );
+ pSizes = pWrapper->XRRConfigSizes( pConfig, &nSizes );
+ XRRScreenSize *pTargetSize = pSizes + nId;
+
+ bNotify = bNotify ||
+ rScreen.m_aSize.Width() != pTargetSize->width ||
+ rScreen.m_aSize.Height() != pTargetSize->height;
+
+ rScreen.m_aSize = Size( pTargetSize->width, pTargetSize->height );
+
+ pWrapper->XRRFreeScreenConfigInfo( pConfig );
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "screen " << nId
+ << " changed to size " << (int)pTargetSize->width
+ << "x" << (int)pTargetSize->height);
+#endif
+ }
+ }
+ if( bNotify )
+ emitDisplayChanged();
+#else
+ (void)this;
+ (void)pEvent;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/saldata.cxx b/vcl/unx/generic/app/saldata.cxx
new file mode 100644
index 000000000..488e9937d
--- /dev/null
+++ b/vcl/unx/generic/app/saldata.cxx
@@ -0,0 +1,778 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <errno.h>
+#ifdef SUN
+#include <sys/systeminfo.h>
+#endif
+#ifdef AIX
+#include <strings.h>
+#endif
+#ifdef FREEBSD
+#include <sys/types.h>
+#include <sys/time.h>
+#endif
+
+#include <osl/process.h>
+
+#include <unx/saldisp.hxx>
+#include <unx/saldata.hxx>
+#include <unx/salunxtime.h>
+#include <unx/sm.hxx>
+#include <unx/i18n_im.hxx>
+
+#include <X11/Xlib.h>
+#include <X11/Xproto.h>
+
+#include <salinst.hxx>
+#include <saltimer.hxx>
+
+#include <osl/diagnose.h>
+#include <osl/signal.h>
+#include <osl/thread.h>
+#include <sal/log.hxx>
+
+#include <vcl/svapp.hxx>
+
+X11SalData* GetX11SalData()
+{
+ return static_cast<X11SalData*>(ImplGetSVData()->mpSalData);
+}
+
+extern "C" {
+
+static int XErrorHdl( Display *pDisplay, XErrorEvent *pEvent )
+{
+ GetX11SalData()->XError( pDisplay, pEvent );
+ return 0;
+}
+
+static int XIOErrorHdl( Display * )
+{
+ if ( Application::IsMainThread() )
+ {
+ /* #106197# hack: until a real shutdown procedure exists
+ * _exit ASAP
+ */
+ if( ImplGetSVData()->maAppData.mbAppQuit )
+ _exit(1);
+
+ // really bad hack
+ if( ! SessionManagerClient::checkDocumentsSaved() )
+ /* oslSignalAction eToDo = */ osl_raiseSignal (OSL_SIGNAL_USER_X11SUBSYSTEMERROR, nullptr);
+ }
+
+ std::fprintf( stderr, "X IO Error\n" );
+ std::fflush( stdout );
+ std::fflush( stderr );
+
+ /* #106197# the same reasons to use _exit instead of exit in salmain
+ * do apply here. Since there is nothing to be done after an XIO
+ * error we have to _exit immediately.
+ */
+ _exit(1);
+ return 0;
+}
+
+}
+
+const struct timeval noyield_ = { 0, 0 };
+const struct timeval yield_ = { 0, 10000 };
+
+static const char* XRequest[] = {
+ // see /usr/lib/X11/XErrorDB, /usr/openwin/lib/XErrorDB ...
+ nullptr,
+ "X_CreateWindow",
+ "X_ChangeWindowAttributes",
+ "X_GetWindowAttributes",
+ "X_DestroyWindow",
+ "X_DestroySubwindows",
+ "X_ChangeSaveSet",
+ "X_ReparentWindow",
+ "X_MapWindow",
+ "X_MapSubwindows",
+ "X_UnmapWindow",
+ "X_UnmapSubwindows",
+ "X_ConfigureWindow",
+ "X_CirculateWindow",
+ "X_GetGeometry",
+ "X_QueryTree",
+ "X_InternAtom",
+ "X_GetAtomName",
+ "X_ChangeProperty",
+ "X_DeleteProperty",
+ "X_GetProperty",
+ "X_ListProperties",
+ "X_SetSelectionOwner",
+ "X_GetSelectionOwner",
+ "X_ConvertSelection",
+ "X_SendEvent",
+ "X_GrabPointer",
+ "X_UngrabPointer",
+ "X_GrabButton",
+ "X_UngrabButton",
+ "X_ChangeActivePointerGrab",
+ "X_GrabKeyboard",
+ "X_UngrabKeyboard",
+ "X_GrabKey",
+ "X_UngrabKey",
+ "X_AllowEvents",
+ "X_GrabServer",
+ "X_UngrabServer",
+ "X_QueryPointer",
+ "X_GetMotionEvents",
+ "X_TranslateCoords",
+ "X_WarpPointer",
+ "X_SetInputFocus",
+ "X_GetInputFocus",
+ "X_QueryKeymap",
+ "X_OpenFont",
+ "X_CloseFont",
+ "X_QueryFont",
+ "X_QueryTextExtents",
+ "X_ListFonts",
+ "X_ListFontsWithInfo",
+ "X_SetFontPath",
+ "X_GetFontPath",
+ "X_CreatePixmap",
+ "X_FreePixmap",
+ "X_CreateGC",
+ "X_ChangeGC",
+ "X_CopyGC",
+ "X_SetDashes",
+ "X_SetClipRectangles",
+ "X_FreeGC",
+ "X_ClearArea",
+ "X_CopyArea",
+ "X_CopyPlane",
+ "X_PolyPoint",
+ "X_PolyLine",
+ "X_PolySegment",
+ "X_PolyRectangle",
+ "X_PolyArc",
+ "X_FillPoly",
+ "X_PolyFillRectangle",
+ "X_PolyFillArc",
+ "X_PutImage",
+ "X_GetImage",
+ "X_PolyText8",
+ "X_PolyText16",
+ "X_ImageText8",
+ "X_ImageText16",
+ "X_CreateColormap",
+ "X_FreeColormap",
+ "X_CopyColormapAndFree",
+ "X_InstallColormap",
+ "X_UninstallColormap",
+ "X_ListInstalledColormaps",
+ "X_AllocColor",
+ "X_AllocNamedColor",
+ "X_AllocColorCells",
+ "X_AllocColorPlanes",
+ "X_FreeColors",
+ "X_StoreColors",
+ "X_StoreNamedColor",
+ "X_QueryColors",
+ "X_LookupColor",
+ "X_CreateCursor",
+ "X_CreateGlyphCursor",
+ "X_FreeCursor",
+ "X_RecolorCursor",
+ "X_QueryBestSize",
+ "X_QueryExtension",
+ "X_ListExtensions",
+ "X_ChangeKeyboardMapping",
+ "X_GetKeyboardMapping",
+ "X_ChangeKeyboardControl",
+ "X_GetKeyboardControl",
+ "X_Bell",
+ "X_ChangePointerControl",
+ "X_GetPointerControl",
+ "X_SetScreenSaver",
+ "X_GetScreenSaver",
+ "X_ChangeHosts",
+ "X_ListHosts",
+ "X_SetAccessControl",
+ "X_SetCloseDownMode",
+ "X_KillClient",
+ "X_RotateProperties",
+ "X_ForceScreenSaver",
+ "X_SetPointerMapping",
+ "X_GetPointerMapping",
+ "X_SetModifierMapping",
+ "X_GetModifierMapping",
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ "X_NoOperation"
+};
+
+X11SalData::X11SalData()
+ : GenericUnixSalData()
+{
+ pXLib_ = nullptr;
+
+ m_aOrigXIOErrorHandler = XSetIOErrorHandler ( XIOErrorHdl );
+ PushXErrorLevel( !!getenv( "SAL_IGNOREXERRORS" ) );
+}
+
+X11SalData::~X11SalData()
+{
+ DeleteDisplay();
+ PopXErrorLevel();
+ XSetIOErrorHandler (m_aOrigXIOErrorHandler);
+}
+
+void X11SalData::Dispose()
+{
+ delete GetDisplay();
+ SetSalData( nullptr );
+}
+
+void X11SalData::DeleteDisplay()
+{
+ delete GetDisplay();
+ SetDisplay( nullptr );
+ pXLib_.reset();
+}
+
+void X11SalData::Init()
+{
+ pXLib_.reset(new SalXLib());
+ pXLib_->Init();
+}
+
+void X11SalData::ErrorTrapPush()
+{
+ PushXErrorLevel( true );
+}
+
+bool X11SalData::ErrorTrapPop( bool bIgnoreError )
+{
+ bool err = false;
+ if( !bIgnoreError )
+ err = HasXErrorOccurred();
+ ResetXErrorOccurred();
+ PopXErrorLevel();
+ return err;
+}
+
+void X11SalData::PushXErrorLevel( bool bIgnore )
+{
+ m_aXErrorHandlerStack.emplace_back( );
+ XErrorStackEntry& rEnt = m_aXErrorHandlerStack.back();
+ rEnt.m_bWas = false;
+ rEnt.m_bIgnore = bIgnore;
+ rEnt.m_aHandler = XSetErrorHandler( XErrorHdl );
+}
+
+void X11SalData::PopXErrorLevel()
+{
+ if( !m_aXErrorHandlerStack.empty() )
+ {
+ XSetErrorHandler( m_aXErrorHandlerStack.back().m_aHandler );
+ m_aXErrorHandlerStack.pop_back();
+ }
+}
+
+SalXLib::SalXLib()
+{
+ m_aTimeout.tv_sec = 0;
+ m_aTimeout.tv_usec = 0;
+ m_nTimeoutMS = 0;
+
+ nFDs_ = 0;
+ FD_ZERO( &aReadFDS_ );
+ FD_ZERO( &aExceptionFDS_ );
+
+ m_pInputMethod = nullptr;
+ m_pDisplay = nullptr;
+
+ m_pTimeoutFDS[0] = m_pTimeoutFDS[1] = -1;
+ if (pipe (m_pTimeoutFDS) == -1)
+ return;
+
+ // initialize 'wakeup' pipe.
+ int flags;
+
+ // set close-on-exec descriptor flag.
+ if ((flags = fcntl (m_pTimeoutFDS[0], F_GETFD)) != -1)
+ {
+ flags |= FD_CLOEXEC;
+ (void)fcntl(m_pTimeoutFDS[0], F_SETFD, flags);
+ }
+ if ((flags = fcntl (m_pTimeoutFDS[1], F_GETFD)) != -1)
+ {
+ flags |= FD_CLOEXEC;
+ (void)fcntl(m_pTimeoutFDS[1], F_SETFD, flags);
+ }
+
+ // set non-blocking I/O flag.
+ if ((flags = fcntl (m_pTimeoutFDS[0], F_GETFL)) != -1)
+ {
+ flags |= O_NONBLOCK;
+ (void)fcntl(m_pTimeoutFDS[0], F_SETFL, flags);
+ }
+ if ((flags = fcntl (m_pTimeoutFDS[1], F_GETFL)) != -1)
+ {
+ flags |= O_NONBLOCK;
+ (void)fcntl(m_pTimeoutFDS[1], F_SETFL, flags);
+ }
+
+ // insert [0] into read descriptor set.
+ FD_SET( m_pTimeoutFDS[0], &aReadFDS_ );
+ nFDs_ = m_pTimeoutFDS[0] + 1;
+}
+
+SalXLib::~SalXLib()
+{
+ // close 'wakeup' pipe.
+ close (m_pTimeoutFDS[0]);
+ close (m_pTimeoutFDS[1]);
+
+ m_pInputMethod.reset();
+}
+
+static Display *OpenX11Display(OString& rDisplay)
+{
+ /*
+ * open connection to X11 Display
+ * try in this order:
+ * o -display command line parameter,
+ * o $DISPLAY environment variable
+ * o default display
+ */
+
+ Display *pDisp = nullptr;
+
+ // is there a -display command line parameter?
+
+ sal_uInt32 nParams = osl_getCommandArgCount();
+ OUString aParam;
+ for (sal_uInt32 i=0; i<nParams; i++)
+ {
+ osl_getCommandArg(i, &aParam.pData);
+ if ( aParam == "-display" )
+ {
+ osl_getCommandArg(i+1, &aParam.pData);
+ rDisplay = OUStringToOString(
+ aParam, osl_getThreadTextEncoding());
+
+ if ((pDisp = XOpenDisplay(rDisplay.getStr()))!=nullptr)
+ {
+ /*
+ * if a -display switch was used, we need
+ * to set the environment accordingly since
+ * the clipboard build another connection
+ * to the xserver using $DISPLAY
+ */
+ OUString envVar("DISPLAY");
+ osl_setEnvironment(envVar.pData, aParam.pData);
+ }
+ break;
+ }
+ }
+
+ if (!pDisp && rDisplay.isEmpty())
+ {
+ // Open $DISPLAY or default...
+ char *pDisplay = getenv("DISPLAY");
+ if (pDisplay != nullptr)
+ rDisplay = OString(pDisplay);
+ pDisp = XOpenDisplay(pDisplay);
+ }
+
+ return pDisp;
+}
+
+void SalXLib::Init()
+{
+ m_pInputMethod.reset( new SalI18N_InputMethod );
+ m_pInputMethod->SetLocale();
+ XrmInitialize();
+
+ OString aDisplay;
+ m_pDisplay = OpenX11Display(aDisplay);
+
+ if ( m_pDisplay )
+ return;
+
+ OUString aProgramFileURL;
+ osl_getExecutableFile( &aProgramFileURL.pData );
+ OUString aProgramSystemPath;
+ osl_getSystemPathFromFileURL (aProgramFileURL.pData, &aProgramSystemPath.pData);
+ OString aProgramName = OUStringToOString(
+ aProgramSystemPath,
+ osl_getThreadTextEncoding() );
+ std::fprintf( stderr, "%s X11 error: Can't open display: %s\n",
+ aProgramName.getStr(), aDisplay.getStr());
+ std::fprintf( stderr, " Set DISPLAY environment variable, use -display option\n");
+ std::fprintf( stderr, " or check permissions of your X-Server\n");
+ std::fprintf( stderr, " (See \"man X\" resp. \"man xhost\" for details)\n");
+ std::fflush( stderr );
+ exit(0);
+
+}
+
+extern "C" {
+static void EmitFontpathWarning()
+{
+ static Bool bOnce = False;
+ if ( !bOnce )
+ {
+ bOnce = True;
+ std::fprintf( stderr, "Please verify your fontpath settings\n"
+ "\t(See \"man xset\" for details"
+ " or ask your system administrator)\n" );
+ }
+}
+
+} /* extern "C" */
+
+static void PrintXError( Display *pDisplay, XErrorEvent *pEvent )
+{
+ char msg[ 120 ] = "";
+ XGetErrorText( pDisplay, pEvent->error_code, msg, sizeof( msg ) );
+ std::fprintf( stderr, "X-Error: %s\n", msg );
+ if( pEvent->request_code < SAL_N_ELEMENTS( XRequest ) )
+ {
+ const char* pName = XRequest[pEvent->request_code];
+ if( !pName )
+ pName = "BadRequest?";
+ std::fprintf( stderr, "\tMajor opcode: %d (%s)\n", pEvent->request_code, pName );
+ }
+ else
+ {
+ std::fprintf( stderr, "\tMajor opcode: %d\n", pEvent->request_code );
+ // TODO: also display extension name?
+ std::fprintf( stderr, "\tMinor opcode: %d\n", pEvent->minor_code );
+ }
+
+ std::fprintf( stderr, "\tResource ID: 0x%lx\n",
+ pEvent->resourceid );
+ std::fprintf( stderr, "\tSerial No: %ld (%ld)\n",
+ pEvent->serial, LastKnownRequestProcessed(pDisplay) );
+
+ if( !getenv( "SAL_SYNCHRONIZE" ) )
+ {
+ std::fprintf( stderr, "These errors are reported asynchronously,\n");
+ std::fprintf( stderr, "set environment variable SAL_SYNCHRONIZE to 1 to help debugging\n");
+ }
+
+ std::fflush( stdout );
+ std::fflush( stderr );
+}
+
+void X11SalData::XError( Display *pDisplay, XErrorEvent *pEvent )
+{
+ if( ! m_aXErrorHandlerStack.back().m_bIgnore )
+ {
+ if ( (pEvent->error_code == BadAlloc)
+ && (pEvent->request_code == X_OpenFont) )
+ {
+ static Bool bOnce = False;
+ if ( !bOnce )
+ {
+ std::fprintf(stderr, "X-Error occurred in a request for X_OpenFont\n");
+ EmitFontpathWarning();
+
+ bOnce = True ;
+ }
+ return;
+ }
+ /* ignore
+ * X_SetInputFocus: it's a hint only anyway
+ * X_GetProperty: this is part of the XGetWindowProperty call and will
+ * be handled by the return value of that function
+ */
+ else if( pEvent->request_code == X_SetInputFocus ||
+ pEvent->request_code == X_GetProperty
+ )
+ return;
+
+ if( pDisplay != vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay() )
+ return;
+
+ PrintXError( pDisplay, pEvent );
+
+ oslSignalAction eToDo = osl_raiseSignal (OSL_SIGNAL_USER_X11SUBSYSTEMERROR, nullptr);
+ switch (eToDo)
+ {
+ case osl_Signal_ActIgnore :
+ return;
+ case osl_Signal_ActAbortApp :
+ abort();
+ case osl_Signal_ActKillApp :
+ exit(0);
+ case osl_Signal_ActCallNextHdl :
+ break;
+ default :
+ break;
+ }
+
+ }
+
+ m_aXErrorHandlerStack.back().m_bWas = true;
+}
+
+void X11SalData::Timeout()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->maSchedCtx.mpSalTimer )
+ pSVData->maSchedCtx.mpSalTimer->CallCallback();
+}
+
+namespace {
+
+struct YieldEntry
+{
+ int fd; // file descriptor for reading
+ void* data; // data for predicate and callback
+ YieldFunc pending; // predicate (determines pending events)
+ YieldFunc queued; // read and queue up events
+ YieldFunc handle; // handle pending events
+
+ int HasPendingEvent() const { return pending( fd, data ); }
+ int IsEventQueued() const { return queued( fd, data ); }
+ void HandleNextEvent() const { handle( fd, data ); }
+};
+
+}
+
+#define MAX_NUM_DESCRIPTORS 128
+
+static YieldEntry yieldTable[ MAX_NUM_DESCRIPTORS ];
+
+void SalXLib::Insert( int nFD, void* data,
+ YieldFunc pending,
+ YieldFunc queued,
+ YieldFunc handle )
+{
+ SAL_WARN_IF( !nFD, "vcl", "can not insert stdin descriptor" );
+ SAL_WARN_IF( yieldTable[nFD].fd, "vcl", "SalXLib::Insert fd twice" );
+
+ yieldTable[nFD].fd = nFD;
+ yieldTable[nFD].data = data;
+ yieldTable[nFD].pending = pending;
+ yieldTable[nFD].queued = queued;
+ yieldTable[nFD].handle = handle;
+
+ FD_SET( nFD, &aReadFDS_ );
+ FD_SET( nFD, &aExceptionFDS_ );
+
+ if( nFD >= nFDs_ )
+ nFDs_ = nFD + 1;
+}
+
+void SalXLib::Remove( int nFD )
+{
+ FD_CLR( nFD, &aReadFDS_ );
+ FD_CLR( nFD, &aExceptionFDS_ );
+
+ yieldTable[nFD].fd = 0;
+
+ if ( nFD == nFDs_ )
+ {
+ for ( nFD = nFDs_ - 1;
+ nFD >= 0 && !yieldTable[nFD].fd;
+ nFD-- ) ;
+
+ nFDs_ = nFD + 1;
+ }
+}
+
+bool SalXLib::CheckTimeout( bool bExecuteTimers )
+{
+ bool bRet = false;
+ if( m_aTimeout.tv_sec ) // timer is started
+ {
+ timeval aTimeOfDay;
+ gettimeofday( &aTimeOfDay, nullptr );
+ if( aTimeOfDay >= m_aTimeout )
+ {
+ bRet = true;
+ if( bExecuteTimers )
+ {
+ // timed out, update timeout
+ m_aTimeout = aTimeOfDay;
+ /*
+ * #107827# autorestart immediately, will be stopped (or set
+ * to different value in notify hdl if necessary;
+ * CheckTimeout should return false while
+ * timers are being dispatched.
+ */
+ m_aTimeout += m_nTimeoutMS;
+ // notify
+ X11SalData::Timeout();
+ }
+ }
+ }
+ return bRet;
+}
+
+bool
+SalXLib::Yield( bool bWait, bool bHandleAllCurrentEvents )
+{
+ // check for timeouts here if you want to make screenshots
+ static char* p_prioritize_timer = getenv ("SAL_HIGHPRIORITY_REPAINT");
+ bool bHandledEvent = false;
+ if (p_prioritize_timer != nullptr)
+ bHandledEvent = CheckTimeout();
+
+ const int nMaxEvents = bHandleAllCurrentEvents ? 100 : 1;
+
+ // first, check for already queued events.
+ for ( int nFD = 0; nFD < nFDs_; nFD++ )
+ {
+ YieldEntry* pEntry = &(yieldTable[nFD]);
+ if ( pEntry->fd )
+ {
+ SAL_WARN_IF( nFD != pEntry->fd, "vcl", "wrong fd in Yield()" );
+ for( int i = 0; i < nMaxEvents && pEntry->HasPendingEvent(); i++ )
+ {
+ pEntry->HandleNextEvent();
+ if( ! bHandleAllCurrentEvents )
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ // next, select with or without timeout according to bWait.
+ int nFDs = nFDs_;
+ fd_set ReadFDS = aReadFDS_;
+ fd_set ExceptionFDS = aExceptionFDS_;
+ int nFound = 0;
+
+ timeval Timeout = noyield_;
+ timeval *pTimeout = &Timeout;
+
+
+ if (bWait)
+ {
+ pTimeout = nullptr;
+ if (m_aTimeout.tv_sec) // Timer is started.
+ {
+ // determine remaining timeout.
+ gettimeofday (&Timeout, nullptr);
+ Timeout = m_aTimeout - Timeout;
+ if (yield_ >= Timeout)
+ {
+ // guard against micro timeout.
+ Timeout = yield_;
+ }
+ pTimeout = &Timeout;
+ }
+ }
+
+ {
+ // release YieldMutex (and re-acquire at block end)
+ SolarMutexReleaser aReleaser;
+ nFound = select( nFDs, &ReadFDS, nullptr, &ExceptionFDS, pTimeout );
+ }
+ if( nFound < 0 ) // error
+ {
+#ifdef DBG_UTIL
+ SAL_INFO("vcl.app", "SalXLib::Yield e=" << errno << " f=" << nFound);
+#endif
+ if( EINTR == errno )
+ {
+ errno = 0;
+ }
+ }
+
+ // usually handle timeouts here (as in 5.2)
+ if (p_prioritize_timer == nullptr)
+ bHandledEvent = CheckTimeout() || bHandledEvent;
+
+ // handle wakeup events.
+ if ((nFound > 0) && FD_ISSET(m_pTimeoutFDS[0], &ReadFDS))
+ {
+ int buffer;
+ while (read (m_pTimeoutFDS[0], &buffer, sizeof(buffer)) > 0)
+ continue;
+ nFound -= 1;
+ }
+
+ // handle other events.
+ if( nFound > 0 )
+ {
+ // now we are in the protected section !
+ // recall select if we have acquired fd's, ready for reading,
+
+ struct timeval noTimeout = { 0, 0 };
+ nFound = select( nFDs_, &ReadFDS, nullptr,
+ &ExceptionFDS, &noTimeout );
+
+ // someone-else has done the job for us
+ if (nFound == 0)
+ {
+ return false;
+ }
+
+ for ( int nFD = 0; nFD < nFDs_; nFD++ )
+ {
+ YieldEntry* pEntry = &(yieldTable[nFD]);
+ if ( pEntry->fd )
+ {
+ if ( FD_ISSET( nFD, &ExceptionFDS ) ) {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.app", "SalXLib::Yield exception.");
+#endif
+ nFound--;
+ }
+ if ( FD_ISSET( nFD, &ReadFDS ) )
+ {
+ for( int i = 0; pEntry->IsEventQueued() && i < nMaxEvents; i++ )
+ {
+ pEntry->HandleNextEvent();
+ bHandledEvent = true;
+ // if a recursive call has done the job
+ // so abort here
+ }
+ nFound--;
+ }
+ }
+ }
+ }
+
+ return bHandledEvent;
+}
+
+void SalXLib::Wakeup()
+{
+ OSL_VERIFY(write (m_pTimeoutFDS[1], "", 1) == 1);
+}
+
+void SalXLib::TriggerUserEventProcessing()
+{
+ Wakeup();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/saldisp.cxx b/vcl/unx/generic/app/saldisp.cxx
new file mode 100644
index 000000000..490b4b771
--- /dev/null
+++ b/vcl/unx/generic/app/saldisp.cxx
@@ -0,0 +1,2865 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <unistd.h>
+
+#if defined(__sun) || defined(AIX)
+#include <osl/module.h>
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/XKBlib.h>
+
+#include <X11/cursorfont.h>
+#include <unx/x11_cursors/salcursors.h>
+#include <unx/x11_cursors/invert50.h>
+#ifdef __sun
+#define XK_KOREAN
+#endif
+#include <X11/keysym.h>
+#include <X11/Xatom.h>
+
+#ifdef USE_XINERAMA_XORG
+#include <X11/extensions/Xinerama.h>
+#endif
+
+#include <i18nlangtag/languagetag.hxx>
+#include <tools/debug.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+
+#include <sal/log.hxx>
+#include <sal/types.h>
+#include <unx/i18n_im.hxx>
+#include <unx/i18n_xkb.hxx>
+#include <unx/saldisp.hxx>
+#include <unx/saldata.hxx>
+#include <salinst.hxx>
+#include <unx/salframe.h>
+#include <vcl/keycodes.hxx>
+#include <unx/salbmp.h>
+#include <osl/diagnose.h>
+#include <unx/salobj.h>
+#include <unx/sm.hxx>
+#include <unx/wmadaptor.hxx>
+#include <unx/x11/xrender_peer.hxx>
+#include <unx/glyphcache.hxx>
+
+#include <poll.h>
+#include <memory>
+#include <vector>
+
+/* From <X11/Intrinsic.h> */
+typedef unsigned long Pixel;
+
+using namespace vcl_sal;
+
+#ifdef DBG_UTIL
+static const char *Null( const char *p ) { return p ? p : ""; }
+static const char *GetEnv( const char *p ) { return Null( getenv( p ) ); }
+static const char *KeyStr( KeySym n ) { return Null( XKeysymToString( n ) ); }
+
+static const char *GetAtomName( Display *d, Atom a )
+{ return Null( XGetAtomName( d, a ) ); }
+
+static double Hypothenuse( tools::Long w, tools::Long h )
+{ return sqrt( static_cast<double>((w*w)+(h*h)) ); }
+#endif
+
+static int ColorDiff( int r, int g, int b )
+{ return (r*r)+(g*g)+(b*b); }
+
+static int ColorDiff( Color c1, int r, int g, int b )
+{ return ColorDiff( static_cast<int>(c1.GetRed())-r,
+ static_cast<int>(c1.GetGreen())-g,
+ static_cast<int>(c1.GetBlue())-b ); }
+
+static int sal_Shift( Pixel nMask )
+{
+ int i = 24;
+ if( nMask < 0x00010000 ) { nMask <<= 16; i -= 16; }
+ if( nMask < 0x01000000 ) { nMask <<= 8; i -= 8; }
+ if( nMask < 0x10000000 ) { nMask <<= 4; i -= 4; }
+ if( nMask < 0x40000000 ) { nMask <<= 2; i -= 2; }
+ if( nMask < 0x80000000 ) { i -= 1; }
+ return i;
+}
+
+static int sal_significantBits( Pixel nMask )
+{
+ int nRotate = sizeof(Pixel)*4;
+ int nBits = 0;
+ while( nRotate-- )
+ {
+ if( nMask & 1 )
+ nBits++;
+ nMask >>= 1;
+ }
+ return nBits;
+}
+
+// check if the resolution is sane
+static bool sal_ValidDPI(tools::Long nDPI)
+{
+ return (nDPI >= 50) && (nDPI <= 500);
+}
+
+static bool sal_GetVisualInfo( Display *pDisplay, XID nVID, XVisualInfo &rVI )
+{
+ int nInfos;
+ XVisualInfo aTemplate;
+ XVisualInfo*pInfo;
+
+ aTemplate.visualid = nVID;
+
+ pInfo = XGetVisualInfo( pDisplay, VisualIDMask, &aTemplate, &nInfos );
+ if( !pInfo )
+ return false;
+
+ rVI = *pInfo;
+ XFree( pInfo );
+
+ SAL_WARN_IF( rVI.visualid != nVID, "vcl",
+ "sal_GetVisualInfo: could not get correct visual by visualId" );
+ return true;
+}
+
+extern "C" srv_vendor_t
+sal_GetServerVendor( Display *p_display )
+{
+ struct vendor_t {
+ srv_vendor_t e_vendor; // vendor as enum
+ const char* p_name; // vendor name as returned by VendorString()
+ unsigned int n_len; // number of chars to compare
+ };
+
+ static const vendor_t vendorlist[] = {
+ { vendor_sun, "Sun Microsystems, Inc.", 10 },
+ };
+
+ // handle regular server vendors
+ char *p_name = ServerVendor( p_display );
+ for (auto const & vendor : vendorlist)
+ {
+ if ( strncmp (p_name, vendor.p_name, vendor.n_len) == 0 )
+ return vendor.e_vendor;
+ }
+
+ // vendor not found in list
+ return vendor_unknown;
+}
+
+bool SalDisplay::BestVisual( Display *pDisplay,
+ int nScreen,
+ XVisualInfo &rVI )
+{
+ VisualID nDefVID = XVisualIDFromVisual( DefaultVisual( pDisplay, nScreen ) );
+ VisualID nVID = 0;
+ char *pVID = getenv( "SAL_VISUAL" );
+ if( pVID )
+ sscanf( pVID, "%li", &nVID );
+
+ if( nVID && sal_GetVisualInfo( pDisplay, nVID, rVI ) )
+ return rVI.visualid == nDefVID;
+
+ XVisualInfo aVI;
+ aVI.screen = nScreen;
+ // get all visuals
+ int nVisuals;
+ XVisualInfo* pVInfos = XGetVisualInfo( pDisplay, VisualScreenMask,
+ &aVI, &nVisuals );
+ // pVInfos should contain at least one visual, otherwise
+ // we're in trouble
+ std::vector<int> aWeights(nVisuals);
+ int i;
+ for( i = 0; i < nVisuals; i++ )
+ {
+ bool bUsable = false;
+ int nTrueColor = 1;
+
+ if ( pVInfos[i].screen != nScreen )
+ {
+ bUsable = false;
+ }
+ else if( pVInfos[i].c_class == TrueColor )
+ {
+ nTrueColor = 2048;
+ if( pVInfos[i].depth == 24 )
+ bUsable = true;
+ }
+ else if( pVInfos[i].c_class == PseudoColor )
+ {
+ bUsable = true;
+ }
+ aWeights[i] = bUsable ? nTrueColor*pVInfos[i].depth : -1024;
+ aWeights[i] -= pVInfos[ i ].visualid;
+ }
+
+ int nBestVisual = 0;
+ int nBestWeight = -1024;
+ for( i = 0; i < nVisuals; i++ )
+ {
+ if (aWeights[i] > nBestWeight)
+ {
+ nBestWeight = aWeights[i];
+ nBestVisual = i;
+ }
+ }
+
+ rVI = pVInfos[ nBestVisual ];
+
+ XFree( pVInfos );
+ return rVI.visualid == nDefVID;
+}
+
+SalDisplay::SalDisplay( Display *display ) :
+ pXLib_( nullptr ),
+ mpKbdExtension( nullptr ),
+ pDisp_( display ),
+ m_nXDefaultScreen( 0 ),
+ nMaxRequestSize_( 0 ),
+ meServerVendor( vendor_unknown ),
+ bNumLockFromXS_( false ),
+ nNumLockIndex_( 0 ),
+ nShiftKeySym_( 0 ),
+ nCtrlKeySym_( 0 ),
+ nMod1KeySym_( 0 ),
+ m_bXinerama( false ),
+ m_nLastUserEventTime( CurrentTime )
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "SalDisplay::SalDisplay().");
+#endif
+ GenericUnixSalData *pData = GetGenericUnixSalData();
+
+ SAL_WARN_IF( pData->GetDisplay(), "vcl", "Second SalDisplay created !!!" );
+ pData->SetDisplay( this );
+
+ m_nXDefaultScreen = SalX11Screen( DefaultScreen( pDisp_ ) );
+}
+
+SalDisplay::~SalDisplay()
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "SalDisplay::~SalDisplay().");
+#endif
+ if( pDisp_ )
+ {
+ doDestruct();
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "display " << pDisp_ << " closed.");
+#endif
+ pDisp_ = nullptr;
+ }
+ // don't do this in doDestruct since RandR extension adds hooks into Display
+ // that is XCloseDisplay still needs the RandR library if it was used
+ DeInitRandR();
+}
+
+void SalDisplay::doDestruct()
+{
+ GenericUnixSalData *pData = GetGenericUnixSalData();
+
+ m_pWMAdaptor.reset();
+ X11SalBitmap::ImplDestroyCache();
+
+ if (ImplGetSVData())
+ {
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(pData);
+ Display* const pX11Disp = pSalDisp->GetDisplay();
+ int nMaxScreens = pSalDisp->GetXScreenCount();
+ XRenderPeer& rRenderPeer = XRenderPeer::GetInstance();
+
+ for (int i = 0; i < nMaxScreens; i++)
+ {
+ SalDisplay::RenderEntryMap& rMap = pSalDisp->GetRenderEntries(SalX11Screen(i));
+ for (auto const& elem : rMap)
+ {
+ if (elem.second.m_aPixmap)
+ ::XFreePixmap(pX11Disp, elem.second.m_aPixmap);
+ if (elem.second.m_aPicture)
+ rRenderPeer.FreePicture(elem.second.m_aPicture);
+ }
+ rMap.clear();
+ }
+ }
+ FreetypeManager::get().ClearFontCache();
+
+ if( IsDisplay() )
+ {
+ delete mpKbdExtension;
+ mpKbdExtension = nullptr;
+
+ for( size_t i = 0; i < m_aScreens.size(); i++ )
+ {
+ ScreenData& rData = m_aScreens[i];
+ if( rData.m_bInit )
+ {
+ if( rData.m_aMonoGC != rData.m_aCopyGC )
+ XFreeGC( pDisp_, rData.m_aMonoGC );
+ XFreeGC( pDisp_, rData.m_aCopyGC );
+ XFreeGC( pDisp_, rData.m_aAndInvertedGC );
+ XFreeGC( pDisp_, rData.m_aAndGC );
+ XFreeGC( pDisp_, rData.m_aOrGC );
+ XFreeGC( pDisp_, rData.m_aStippleGC );
+ XFreePixmap( pDisp_, rData.m_hInvert50 );
+ XDestroyWindow( pDisp_, rData.m_aRefWindow );
+ Colormap aColMap = rData.m_aColormap.GetXColormap();
+ if( aColMap != None && aColMap != DefaultColormap( pDisp_, i ) )
+ XFreeColormap( pDisp_, aColMap );
+ }
+ }
+
+ for( const Cursor & aCsr : aPointerCache_ )
+ {
+ if( aCsr )
+ XFreeCursor( pDisp_, aCsr );
+ }
+
+ if( pXLib_ )
+ pXLib_->Remove( ConnectionNumber( pDisp_ ) );
+ }
+
+ if( pData->GetDisplay() == static_cast<const SalGenericDisplay *>( this ) )
+ pData->SetDisplay( nullptr );
+}
+
+static int DisplayHasEvent( int fd, void * data )
+{
+ auto pDisplay = static_cast<SalX11Display *>(data);
+ SAL_WARN_IF( ConnectionNumber( pDisplay->GetDisplay() ) != fd, "vcl",
+ "wrong fd in DisplayHasEvent" );
+ if( ! pDisplay->IsDisplay() )
+ return 0;
+
+ bool result;
+
+ SolarMutexGuard aGuard;
+ result = pDisplay->IsEvent();
+ return int(result);
+}
+static int DisplayQueue( int fd, void * data )
+{
+ auto pDisplay = static_cast<SalX11Display *>(data);
+ SAL_WARN_IF( ConnectionNumber( pDisplay->GetDisplay() ) != fd, "vcl",
+ "wrong fd in DisplayHasEvent" );
+ int result;
+
+ SolarMutexGuard aGuard;
+ result = XEventsQueued( pDisplay->GetDisplay(),
+ QueuedAfterReading );
+ return result;
+}
+static int DisplayYield( int fd, void * data )
+{
+ auto pDisplay = static_cast<SalX11Display *>(data);
+ SAL_WARN_IF( ConnectionNumber( pDisplay->GetDisplay() ) != fd, "vcl",
+ "wrong fd in DisplayHasEvent" );
+
+ SolarMutexGuard aGuard;
+ pDisplay->Yield();
+ return 1;
+}
+
+SalX11Display::SalX11Display( Display *display )
+ : SalDisplay( display )
+{
+ Init();
+
+ pXLib_ = GetX11SalData()->GetLib();
+ pXLib_->Insert( ConnectionNumber( pDisp_ ),
+ this,
+ reinterpret_cast<YieldFunc>(DisplayHasEvent),
+ reinterpret_cast<YieldFunc>(DisplayQueue),
+ reinterpret_cast<YieldFunc>(DisplayYield) );
+}
+
+SalX11Display::~SalX11Display()
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "SalX11Display::~SalX11Display().");
+#endif
+ if( pDisp_ )
+ {
+ doDestruct();
+ XCloseDisplay( pDisp_ );
+ pDisp_ = nullptr;
+ }
+}
+
+void SalX11Display::TriggerUserEventProcessing()
+{
+ if( pXLib_ )
+ pXLib_->TriggerUserEventProcessing();
+}
+
+SalDisplay::ScreenData *
+SalDisplay::initScreen( SalX11Screen nXScreen ) const
+{
+ if( nXScreen.getXScreen() >= m_aScreens.size() )
+ nXScreen = m_nXDefaultScreen;
+ ScreenData* pSD = const_cast<ScreenData *>(&m_aScreens[nXScreen.getXScreen()]);
+ if( pSD->m_bInit )
+ return nullptr;
+ pSD->m_bInit = true;
+
+ XVisualInfo aVI;
+ Colormap aColMap;
+
+ if( SalDisplay::BestVisual( pDisp_, nXScreen.getXScreen(), aVI ) ) // DefaultVisual
+ aColMap = DefaultColormap( pDisp_, nXScreen.getXScreen() );
+ else
+ aColMap = XCreateColormap( pDisp_,
+ RootWindow( pDisp_, nXScreen.getXScreen() ),
+ aVI.visual,
+ AllocNone );
+
+ Screen* pScreen = ScreenOfDisplay( pDisp_, nXScreen.getXScreen() );
+
+ pSD->m_aSize = Size( WidthOfScreen( pScreen ), HeightOfScreen( pScreen ) );
+ pSD->m_aRoot = RootWindow( pDisp_, nXScreen.getXScreen() );
+ pSD->m_aVisual = SalVisual( &aVI );
+ pSD->m_aColormap = SalColormap( this, aColMap, nXScreen );
+
+ // we're interested in configure notification of root windows
+ InitRandR( pSD->m_aRoot );
+
+ // - - - - - - - - - - Reference Window/Default Drawable - -
+ XSetWindowAttributes aXWAttributes;
+ aXWAttributes.border_pixel = 0;
+ aXWAttributes.background_pixel = 0;
+ aXWAttributes.colormap = aColMap;
+ pSD->m_aRefWindow = XCreateWindow( pDisp_,
+ pSD->m_aRoot,
+ 0,0, 16,16, 0,
+ pSD->m_aVisual.GetDepth(),
+ InputOutput,
+ pSD->m_aVisual.GetVisual(),
+ CWBorderPixel|CWBackPixel|CWColormap,
+ &aXWAttributes );
+
+ // set client leader (session id gets set when session is started)
+ if( pSD->m_aRefWindow )
+ {
+ // client leader must have WM_CLIENT_LEADER pointing to itself
+ XChangeProperty( pDisp_,
+ pSD->m_aRefWindow,
+ XInternAtom( pDisp_, "WM_CLIENT_LEADER", False ),
+ XA_WINDOW,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(&pSD->m_aRefWindow),
+ 1
+ );
+
+ OString aExec(OUStringToOString(SessionManagerClient::getExecName(), osl_getThreadTextEncoding()));
+ const char* argv[1];
+ argv[0] = aExec.getStr();
+ XSetCommand( pDisp_, pSD->m_aRefWindow, const_cast<char**>(argv), 1 );
+ XSelectInput( pDisp_, pSD->m_aRefWindow, PropertyChangeMask );
+
+ // - - - - - - - - - - GCs - - - - - - - - - - - - - - - - -
+ XGCValues values;
+ values.graphics_exposures = False;
+ values.fill_style = FillOpaqueStippled;
+ values.background = (1<<pSD->m_aVisual.GetDepth())-1;
+ values.foreground = 0;
+
+ pSD->m_aCopyGC = XCreateGC( pDisp_,
+ pSD->m_aRefWindow,
+ GCGraphicsExposures
+ | GCForeground
+ | GCBackground,
+ &values );
+ pSD->m_aAndInvertedGC= XCreateGC( pDisp_,
+ pSD->m_aRefWindow,
+ GCGraphicsExposures
+ | GCForeground
+ | GCBackground,
+ &values );
+ pSD->m_aAndGC = XCreateGC( pDisp_,
+ pSD->m_aRefWindow,
+ GCGraphicsExposures
+ | GCForeground
+ | GCBackground,
+ &values );
+ pSD->m_aOrGC = XCreateGC( pDisp_,
+ pSD->m_aRefWindow,
+ GCGraphicsExposures
+ | GCForeground
+ | GCBackground,
+ &values );
+ pSD->m_aStippleGC = XCreateGC( pDisp_,
+ pSD->m_aRefWindow,
+ GCGraphicsExposures
+ | GCFillStyle
+ | GCForeground
+ | GCBackground,
+ &values );
+
+ XSetFunction( pDisp_, pSD->m_aAndInvertedGC, GXandInverted );
+ XSetFunction( pDisp_, pSD->m_aAndGC, GXand );
+ // PowerPC Solaris 2.5 (XSun 3500) Bug: GXor = GXnop
+ XSetFunction( pDisp_, pSD->m_aOrGC, GXxor );
+
+ if( 1 == pSD->m_aVisual.GetDepth() )
+ {
+ XSetFunction( pDisp_, pSD->m_aCopyGC, GXcopyInverted );
+ pSD->m_aMonoGC = pSD->m_aCopyGC;
+ }
+ else
+ {
+ Pixmap hPixmap = XCreatePixmap( pDisp_, pSD->m_aRefWindow, 1, 1, 1 );
+ pSD->m_aMonoGC = XCreateGC( pDisp_,
+ hPixmap,
+ GCGraphicsExposures,
+ &values );
+ XFreePixmap( pDisp_, hPixmap );
+ }
+ pSD->m_hInvert50 = XCreateBitmapFromData( pDisp_,
+ pSD->m_aRefWindow,
+ reinterpret_cast<const char*>(invert50_bits),
+ invert50_width,
+ invert50_height );
+ }
+ return pSD;
+}
+
+void SalDisplay::Init()
+{
+ for( Cursor & aCsr : aPointerCache_ )
+ aCsr = None;
+
+ m_bXinerama = false;
+
+ int nDisplayScreens = ScreenCount( pDisp_ );
+ m_aScreens = std::vector<ScreenData>(nDisplayScreens);
+
+ bool bExactResolution = false;
+ /* #i15507#
+ * Xft resolution should take precedence since
+ * it is what modern desktops use.
+ */
+ const char* pValStr = XGetDefault( pDisp_, "Xft", "dpi" );
+ if( pValStr != nullptr )
+ {
+ const OString aValStr( pValStr );
+ const tools::Long nDPI = static_cast<tools::Long>(aValStr.toDouble());
+ // guard against insane resolution
+ if( sal_ValidDPI(nDPI) )
+ {
+ aResolution_ = Pair( nDPI, nDPI );
+ bExactResolution = true;
+ }
+ }
+ if( !bExactResolution )
+ {
+ /* if Xft.dpi is not set, try and find the DPI from the
+ * reported screen sizes and resolution. If there are multiple
+ * screens, just fall back to the default 96x96
+ */
+ tools::Long xDPI = 96;
+ tools::Long yDPI = 96;
+ if (m_aScreens.size() == 1) {
+ xDPI = static_cast<tools::Long>(round(DisplayWidth(pDisp_, 0)*25.4/DisplayWidthMM(pDisp_, 0)));
+ yDPI = static_cast<tools::Long>(round(DisplayHeight(pDisp_, 0)*25.4/DisplayHeightMM(pDisp_, 0)));
+ // if either is invalid set it equal to the other
+ if (!sal_ValidDPI(xDPI) && sal_ValidDPI(yDPI))
+ xDPI = yDPI;
+ if (!sal_ValidDPI(yDPI) && sal_ValidDPI(xDPI))
+ yDPI = xDPI;
+ // if both are invalid, reset them to the default
+ if (!sal_ValidDPI(xDPI) && !sal_ValidDPI(yDPI))
+ xDPI = yDPI = 96;
+ }
+ aResolution_ = Pair( xDPI, yDPI );
+ }
+
+ nMaxRequestSize_ = XExtendedMaxRequestSize( pDisp_ ) * 4;
+ if( !nMaxRequestSize_ )
+ nMaxRequestSize_ = XMaxRequestSize( pDisp_ ) * 4;
+
+ meServerVendor = sal_GetServerVendor(pDisp_);
+ X11SalBitmap::ImplCreateCache();
+
+ // - - - - - - - - - - Synchronize - - - - - - - - - - - - -
+ if( getenv( "SAL_SYNCHRONIZE" ) )
+ XSynchronize( pDisp_, True );
+
+ // - - - - - - - - - - Keyboardmapping - - - - - - - - - - -
+ ModifierMapping();
+
+ // - - - - - - - - - - Window Manager - - - - - - - - - - -
+ m_pWMAdaptor = ::vcl_sal::WMAdaptor::createWMAdaptor( this );
+
+ InitXinerama();
+
+#ifdef DBG_UTIL
+ PrintInfo();
+#endif
+}
+
+void SalX11Display::SetupInput()
+{
+ GetGenericUnixSalData()->ErrorTrapPush();
+ SalI18N_KeyboardExtension *pKbdExtension = new SalI18N_KeyboardExtension( pDisp_ );
+ XSync( pDisp_, False );
+
+ bool bError = GetGenericUnixSalData()->ErrorTrapPop( false );
+ GetGenericUnixSalData()->ErrorTrapPush();
+ pKbdExtension->UseExtension( ! bError );
+ GetGenericUnixSalData()->ErrorTrapPop();
+
+ SetKbdExtension( pKbdExtension );
+}
+
+// Sound
+void SalDisplay::Beep() const
+{
+ XBell( pDisp_, 100 );
+}
+
+// Keyboard
+
+namespace {
+
+bool InitXkb(Display* dpy)
+{
+ int nOpcode, nEvent, nError;
+ int nXkbMajor = XkbMajorVersion;
+ int nXkbMinor = XkbMinorVersion;
+
+ if (!XkbLibraryVersion(&nXkbMajor, &nXkbMinor))
+ return false;
+
+ return XkbQueryExtension(
+ dpy, &nOpcode, &nEvent, &nError, &nXkbMajor, &nXkbMinor);
+}
+
+unsigned int GetKeySymMask(Display* dpy, KeySym nKeySym)
+{
+ int nMask = 0;
+ XModifierKeymap* pXmkMap = XGetModifierMapping(dpy);
+ KeyCode nKeyCode = XKeysymToKeycode(dpy, nKeySym);
+ if (nKeyCode == NoSymbol)
+ return 0;
+
+ for (int i = 0; i < 8; ++i)
+ {
+ KeyCode nThisKeyCode = pXmkMap->modifiermap[pXmkMap->max_keypermod*i];
+ if (nThisKeyCode == nKeyCode)
+ nMask = 1 << i;
+ }
+ XFreeModifiermap(pXmkMap);
+ return nMask;
+}
+
+}
+
+void SalDisplay::SimulateKeyPress( sal_uInt16 nKeyCode )
+{
+ if (nKeyCode != KEY_CAPSLOCK)
+ return;
+
+ Display* dpy = GetDisplay();
+ if (!InitXkb(dpy))
+ return;
+
+ unsigned int nMask = GetKeySymMask(dpy, XK_Caps_Lock);
+ XkbStateRec xkbState;
+ XkbGetState(dpy, XkbUseCoreKbd, &xkbState);
+ unsigned int nCapsLockState = xkbState.locked_mods & nMask;
+ if (nCapsLockState)
+ XkbLockModifiers (dpy, XkbUseCoreKbd, nMask, 0);
+ else
+ XkbLockModifiers (dpy, XkbUseCoreKbd, nMask, nMask);
+}
+
+KeyIndicatorState SalDisplay::GetIndicatorState() const
+{
+ unsigned int _state = 0;
+ KeyIndicatorState nState = KeyIndicatorState::NONE;
+ XkbGetIndicatorState(pDisp_, XkbUseCoreKbd, &_state);
+
+ if (_state & 0x00000001)
+ nState |= KeyIndicatorState::CAPSLOCK;
+ if (_state & 0x00000002)
+ nState |= KeyIndicatorState::NUMLOCK;
+ if (_state & 0x00000004)
+ nState |= KeyIndicatorState::SCROLLLOCK;
+
+ return nState;
+}
+
+OUString SalDisplay::GetKeyNameFromKeySym( KeySym nKeySym ) const
+{
+ OUString aLang = Application::GetSettings().GetUILanguageTag().getLanguage();
+ OUString aRet;
+
+ // return an empty string for keysyms that are not bound to
+ // any key code
+ KeyCode aKeyCode = XKeysymToKeycode( GetDisplay(), nKeySym );
+ static_assert(NoSymbol == 0, "X11 inconsistency");
+ if( aKeyCode != NoSymbol )
+ {
+ if( !nKeySym )
+ aRet = "???";
+ else
+ {
+ aRet = ::vcl_sal::getKeysymReplacementName( aLang, nKeySym );
+ if( aRet.isEmpty() )
+ {
+ const char *pString = XKeysymToString( nKeySym );
+ if (pString)
+ {
+ int n = strlen( pString );
+ if( n > 2 && pString[n-2] == '_' )
+ aRet = OUString( pString, n-2, RTL_TEXTENCODING_ISO_8859_1 );
+ else
+ aRet = OUString( pString, n, RTL_TEXTENCODING_ISO_8859_1 );
+ }
+ else
+ aRet = "???";
+ }
+ }
+ }
+ return aRet;
+}
+
+static KeySym sal_XModifier2Keysym( Display *pDisplay,
+ XModifierKeymap const *pXModMap,
+ int n )
+{
+ return XkbKeycodeToKeysym( pDisplay,
+ pXModMap->modifiermap[n*pXModMap->max_keypermod],
+ 0,0 );
+}
+
+void SalDisplay::ModifierMapping()
+{
+ XModifierKeymap *pXModMap = XGetModifierMapping( pDisp_ );
+
+ bNumLockFromXS_ = True;
+ nShiftKeySym_ = sal_XModifier2Keysym( pDisp_, pXModMap, ShiftMapIndex );
+ nCtrlKeySym_ = sal_XModifier2Keysym( pDisp_, pXModMap, ControlMapIndex );
+ nMod1KeySym_ = sal_XModifier2Keysym( pDisp_, pXModMap, Mod1MapIndex );
+ // on Sun and SCO servers XLookupString does not account for NumLock
+ if( GetServerVendor() == vendor_sun )
+ {
+ KeyCode aNumLock = XKeysymToKeycode( pDisp_, XK_Num_Lock );
+
+ if( aNumLock )
+ for( int i = ShiftMapIndex; i <= Mod5MapIndex; i++ )
+ {
+ if( pXModMap->modifiermap[i*pXModMap->max_keypermod] == aNumLock )
+ {
+ bNumLockFromXS_ = False;
+ nNumLockIndex_ = i;
+ break;
+ }
+ }
+ }
+
+ XFreeModifiermap( pXModMap );
+}
+
+OUString SalDisplay::GetKeyName( sal_uInt16 nKeyCode ) const
+{
+ OUString aStrMap;
+ OUString aCustomKeyName;
+
+ if( nKeyCode & KEY_MOD1 )
+ aStrMap += GetKeyNameFromKeySym( nCtrlKeySym_ );
+
+ if( nKeyCode & KEY_MOD2 )
+ {
+ if( !aStrMap.isEmpty() )
+ aStrMap += "+";
+ aStrMap += GetKeyNameFromKeySym( nMod1KeySym_ );
+ }
+
+ if( nKeyCode & KEY_SHIFT )
+ {
+ if( !aStrMap.isEmpty() )
+ aStrMap += "+";
+ aStrMap += GetKeyNameFromKeySym( nShiftKeySym_ );
+ }
+ nKeyCode &= 0x0FFF;
+
+ KeySym nKeySym = 0;
+
+ if( KEY_0 <= nKeyCode && nKeyCode <= KEY_9 )
+ nKeySym = XK_0 + (nKeyCode - KEY_0);
+ else if( KEY_A <= nKeyCode && nKeyCode <= KEY_Z )
+ nKeySym = XK_A + (nKeyCode - KEY_A);
+ else if( KEY_F1 <= nKeyCode && nKeyCode <= KEY_F26 ) // does this key exist?
+ nKeySym = XK_F1 + (nKeyCode - KEY_F1);
+ else switch( nKeyCode )
+ {
+ case KEY_DOWN:
+ nKeySym = XK_Down;
+ break;
+ case KEY_UP:
+ nKeySym = XK_Up;
+ break;
+ case KEY_LEFT:
+ nKeySym = XK_Left;
+ break;
+ case KEY_RIGHT:
+ nKeySym = XK_Right;
+ break;
+ case KEY_HOME:
+ nKeySym = XK_Home;
+ break;
+ case KEY_END:
+ nKeySym = XK_End;
+ break;
+ case KEY_PAGEUP:
+ nKeySym = XK_Page_Up;
+ break;
+ case KEY_PAGEDOWN:
+ nKeySym = XK_Page_Down;
+ break;
+ case KEY_RETURN:
+ nKeySym = XK_Return;
+ break;
+ case KEY_ESCAPE:
+ nKeySym = XK_Escape;
+ break;
+ case KEY_TAB:
+ nKeySym = XK_Tab;
+ break;
+ case KEY_BACKSPACE:
+ nKeySym = XK_BackSpace;
+ break;
+ case KEY_SPACE:
+ nKeySym = XK_space;
+ break;
+ case KEY_INSERT:
+ nKeySym = XK_Insert;
+ break;
+ case KEY_DELETE:
+ nKeySym = XK_Delete;
+ break;
+
+ #if !defined (SunXK_Undo)
+ // we don't intend to use SunXK_Undo, but if it has not been
+ // defined already, then we _do_ need the following:
+ #define SunXK_Props 0x1005FF70
+ #define SunXK_Front 0x1005FF71
+ #define SunXK_Copy 0x1005FF72
+ #define SunXK_Open 0x1005FF73
+ #define SunXK_Paste 0x1005FF74
+ #define SunXK_Cut 0x1005FF75
+ #endif
+ // the following are for XF86 systems
+ #define XF86XK_Copy 0x1008FF57
+ #define XF86XK_Cut 0x1008FF58
+ #define XF86XK_Open 0x1008FF6B
+ #define XF86XK_Paste 0x1008FF6D
+ // which leaves Apollo and OSF systems in the lurch
+
+ case KEY_REPEAT:
+ nKeySym = XK_Redo;
+ break;
+ case KEY_PROPERTIES:
+ nKeySym = SunXK_Props;
+ break;
+ case KEY_UNDO:
+ nKeySym = XK_Undo;
+ break;
+ case KEY_FRONT:
+ nKeySym = SunXK_Front;
+ break;
+ case KEY_COPY:
+ nKeySym = GetServerVendor() == vendor_sun ? SunXK_Copy : XF86XK_Copy;
+ break;
+ case KEY_OPEN:
+ nKeySym = GetServerVendor() == vendor_sun ? SunXK_Open : XF86XK_Open;
+ break;
+ case KEY_PASTE:
+ nKeySym = GetServerVendor() == vendor_sun ? SunXK_Paste : XF86XK_Paste;
+ break;
+ case KEY_FIND:
+ nKeySym = XK_Find;
+ break;
+ case KEY_CUT:
+ nKeySym = GetServerVendor() == vendor_sun ? SunXK_Cut : XF86XK_Cut;
+ /* The original code here had:
+ nKeySym = GetServerVendor() == vendor_sun ? SunXK_Cut : XK_L10;
+ if anyone can remember which non-vendor_sun system used this
+ XK_L10 keysym, and why this hack only applied to KEY_CUT,
+ then please re-hack this code to put it back
+ */
+ break;
+ case KEY_ADD:
+ aCustomKeyName = "+";
+ break;
+ case KEY_SUBTRACT:
+ aCustomKeyName = "-";
+ break;
+ case KEY_MULTIPLY:
+ nKeySym = XK_asterisk;
+ break;
+ case KEY_DIVIDE:
+ nKeySym = XK_slash;
+ break;
+ case KEY_POINT:
+ aCustomKeyName = ".";
+ break;
+ case KEY_COMMA:
+ nKeySym = XK_comma;
+ break;
+ case KEY_LESS:
+ nKeySym = XK_less;
+ break;
+ case KEY_GREATER:
+ nKeySym = XK_greater;
+ break;
+ case KEY_EQUAL:
+ nKeySym = XK_equal;
+ break;
+ case KEY_HELP:
+ nKeySym = XK_Help;
+ break;
+ case KEY_HANGUL_HANJA:
+ nKeySym = XK_Hangul_Hanja;
+ break;
+ case KEY_TILDE:
+ nKeySym = XK_asciitilde;
+ break;
+ case KEY_QUOTELEFT:
+ nKeySym = XK_grave;
+ break;
+ case KEY_BRACKETLEFT:
+ aCustomKeyName = "[";
+ break;
+ case KEY_BRACKETRIGHT:
+ aCustomKeyName = "]";
+ break;
+ case KEY_SEMICOLON:
+ aCustomKeyName = ";";
+ break;
+ case KEY_QUOTERIGHT:
+ aCustomKeyName = "'";
+ break;
+ default:
+ nKeySym = 0;
+ break;
+ }
+
+ if( nKeySym )
+ {
+ OUString aKeyName = GetKeyNameFromKeySym( nKeySym );
+ if( !aKeyName.isEmpty() )
+ {
+ if( !aStrMap.isEmpty() )
+ aStrMap += "+";
+ aStrMap += aKeyName;
+ }
+ else
+ aStrMap.clear();
+ }
+ else if (!aCustomKeyName.isEmpty())
+ {
+ // For semicolon, bracket left and bracket right, it's better to use
+ // their keys than their names. (fdo#32891)
+ if (!aStrMap.isEmpty())
+ aStrMap += "+";
+ aStrMap += aCustomKeyName;
+ }
+ else
+ aStrMap.clear();
+
+ return aStrMap;
+}
+
+#ifndef IsISOKey
+#define IsISOKey( n ) (0x0000FE00==((n)&0xFFFFFF00))
+#endif
+
+sal_uInt16 SalDisplay::GetKeyCode( KeySym keysym, char*pcPrintable ) const
+{
+ sal_uInt16 nKey = 0;
+
+ if( XK_a <= keysym && XK_z >= keysym )
+ nKey = static_cast<sal_uInt16>(KEY_A + (keysym - XK_a));
+ else if( XK_A <= keysym && XK_Z >= keysym )
+ nKey = static_cast<sal_uInt16>(KEY_A + (keysym - XK_A));
+ else if( XK_0 <= keysym && XK_9 >= keysym )
+ nKey = static_cast<sal_uInt16>(KEY_0 + (keysym - XK_0));
+ else if( IsModifierKey( keysym ) )
+ ;
+ else if( IsKeypadKey( keysym ) )
+ {
+ if( (keysym >= XK_KP_0) && (keysym <= XK_KP_9) )
+ {
+ nKey = static_cast<sal_uInt16>(KEY_0 + (keysym - XK_KP_0));
+ *pcPrintable = '0' + nKey - KEY_0;
+ }
+ else if( IsPFKey( keysym ) )
+ nKey = static_cast<sal_uInt16>(KEY_F1 + (keysym - XK_KP_F1));
+ else switch( keysym )
+ {
+ case XK_KP_Space:
+ nKey = KEY_SPACE;
+ *pcPrintable = ' ';
+ break;
+ case XK_KP_Tab:
+ nKey = KEY_TAB;
+ break;
+ case XK_KP_Enter:
+ nKey = KEY_RETURN;
+ break;
+ case XK_KP_Begin:
+ case XK_KP_Home:
+ nKey = KEY_HOME;
+ break;
+ case XK_KP_Left:
+ nKey = KEY_LEFT;
+ break;
+ case XK_KP_Up:
+ nKey = KEY_UP;
+ break;
+ case XK_KP_Right:
+ nKey = KEY_RIGHT;
+ break;
+ case XK_KP_Down:
+ nKey = KEY_DOWN;
+ break;
+ case XK_KP_Page_Up: // XK_KP_Page_Up
+ nKey = KEY_PAGEUP;
+ break;
+ case XK_KP_Page_Down: // XK_KP_Page_Down
+ nKey = KEY_PAGEDOWN;
+ break;
+ case XK_KP_End:
+ nKey = KEY_END;
+ break;
+ case XK_KP_Insert:
+ nKey = KEY_INSERT;
+ break;
+ case XK_KP_Delete:
+ nKey = KEY_DELETE;
+ break;
+ case XK_KP_Equal:
+ nKey = KEY_EQUAL;
+ *pcPrintable = '=';
+ break;
+ case XK_KP_Multiply:
+ nKey = KEY_MULTIPLY;
+ *pcPrintable = '*';
+ break;
+ case XK_KP_Add:
+ nKey = KEY_ADD;
+ *pcPrintable = '+';
+ break;
+ case XK_KP_Separator:
+ nKey = KEY_DECIMAL;
+ *pcPrintable = ',';
+ break;
+ case XK_KP_Subtract:
+ nKey = KEY_SUBTRACT;
+ *pcPrintable = '-';
+ break;
+ case XK_KP_Decimal:
+ nKey = KEY_DECIMAL;
+ *pcPrintable = '.';
+ break;
+ case XK_KP_Divide:
+ nKey = KEY_DIVIDE;
+ *pcPrintable = '/';
+ break;
+ }
+ }
+ else if( IsFunctionKey( keysym ) )
+ {
+ if( bNumLockFromXS_ )
+ {
+ if( keysym >= XK_F1 && keysym <= XK_F26 )
+ nKey = static_cast<sal_uInt16>(KEY_F1 + keysym - XK_F1);
+ }
+ else switch( keysym )
+ {
+ // - - - - - Sun X-Server keyboard without Cursorblock ??? - - -
+ case XK_R7: // XK_F27:
+ nKey = KEY_HOME;
+ break;
+ case XK_R8: // XK_F28:
+ nKey = KEY_UP;
+ break;
+ case XK_R9: // XK_F29:
+ nKey = KEY_PAGEUP;
+ break;
+ case XK_R10: // XK_F30:
+ nKey = KEY_LEFT;
+ break;
+ case XK_R11: // XK_F31:
+ nKey = 0; // KEY_F31
+ break;
+ case XK_R12: // XK_F32:
+ nKey = KEY_RIGHT;
+ break;
+ case XK_R13: // XK_F33:
+ nKey = KEY_END;
+ break;
+ case XK_R14: // XK_F34:
+ nKey = KEY_DOWN;
+ break;
+ case XK_R15: // XK_F35:
+ nKey = KEY_PAGEDOWN;
+ break;
+ // - - - - - Sun X-Server keyboard ??? - - - - - - - - - - - -
+ case XK_L1: // XK_F11:
+ nKey = KEY_F11; // on a sun keyboard this actually is usually SunXK_Stop = 0x0000FF69 (XK_Cancel),
+ // but VCL doesn't have a key definition for that
+ break;
+ case XK_L2: // XK_F12:
+ if ( GetServerVendor() == vendor_sun )
+ nKey = KEY_REPEAT;
+ else
+ nKey = KEY_F12;
+ break;
+ case XK_L3: // XK_F13:
+ nKey = KEY_PROPERTIES; // KEY_F13
+ break;
+ case XK_L4: // XK_F14:
+ nKey = KEY_UNDO; // KEY_F14
+ break;
+ case XK_L5: // XK_F15:
+ nKey = KEY_F15; // KEY_FRONT
+ break;
+ case XK_L6: // XK_F16:
+ nKey = KEY_COPY; // KEY_F16
+ break;
+ case XK_L7: // XK_F17:
+ nKey = KEY_F17; // KEY_OPEN
+ break;
+ case XK_L8: // XK_F18:
+ nKey = KEY_PASTE; // KEY_F18
+ break;
+ case XK_L9: // XK_F19:
+ nKey = KEY_F19; // KEY_FIND
+ break;
+ case XK_L10: // XK_F20:
+ nKey = KEY_CUT; // KEY_F20
+ break;
+ default:
+ if( keysym >= XK_F1 && keysym <= XK_F26 )
+ nKey = static_cast<sal_uInt16>(KEY_F1 + keysym - XK_F1);
+ break;
+ }
+ }
+ else if( IsCursorKey( keysym ) )
+ {
+ switch( keysym )
+ {
+ case XK_Begin:
+ case XK_Home:
+ nKey = KEY_HOME;
+ break;
+ case XK_Left:
+ nKey = KEY_LEFT;
+ break;
+ case XK_Up:
+ nKey = KEY_UP;
+ break;
+ case XK_Right:
+ nKey = KEY_RIGHT;
+ break;
+ case XK_Down:
+ nKey = KEY_DOWN;
+ break;
+ case XK_Page_Up: // XK_Page_Up
+ nKey = KEY_PAGEUP;
+ break;
+ case XK_Page_Down: // XK_Page_Down
+ nKey = KEY_PAGEDOWN;
+ break;
+ case XK_End:
+ nKey = KEY_END;
+ break;
+ }
+ }
+ else if( IsMiscFunctionKey( keysym ) )
+ {
+ switch( keysym )
+ {
+ case XK_Insert:
+ nKey = KEY_INSERT;
+ break;
+ case XK_Redo:
+ nKey = KEY_REPEAT;
+ break;
+ case XK_Undo:
+ nKey = KEY_UNDO;
+ break;
+ case XK_Find:
+ nKey = KEY_FIND;
+ break;
+ case XK_Help:
+ nKey = KEY_HELP;
+ break;
+ case XK_Menu:
+ nKey = KEY_CONTEXTMENU;
+ break;
+ }
+ }
+ else if( IsISOKey( keysym ) ) // XK_ISO_
+ {
+ switch( keysym )
+ {
+ case 0xFE20: // XK_ISO_Left_Tab:
+ nKey = KEY_TAB;
+ break;
+ }
+ }
+ else switch( keysym )
+ {
+ case XK_Return:
+ nKey = KEY_RETURN;
+ break;
+ case XK_BackSpace:
+ nKey = KEY_BACKSPACE;
+ break;
+ case XK_Delete:
+ nKey = KEY_DELETE;
+ break;
+ case XK_space:
+ nKey = KEY_SPACE;
+ break;
+ case XK_Tab:
+ nKey = KEY_TAB;
+ break;
+ case XK_Escape:
+ nKey = KEY_ESCAPE;
+ break;
+ case XK_plus:
+ nKey = KEY_ADD;
+ break;
+ case XK_minus:
+ nKey = KEY_SUBTRACT;
+ break;
+ case XK_asterisk:
+ nKey = KEY_MULTIPLY;
+ break;
+ case XK_slash:
+ nKey = KEY_DIVIDE;
+ break;
+ case XK_period:
+ nKey = KEY_POINT;
+ *pcPrintable = '.';
+ break;
+ case XK_comma:
+ nKey = KEY_COMMA;
+ break;
+ case XK_less:
+ nKey = KEY_LESS;
+ break;
+ case XK_greater:
+ nKey = KEY_GREATER;
+ break;
+ case XK_equal:
+ nKey = KEY_EQUAL;
+ break;
+ case XK_Hangul_Hanja:
+ nKey = KEY_HANGUL_HANJA;
+ break;
+ case XK_asciitilde:
+ nKey = KEY_TILDE;
+ *pcPrintable = '~';
+ break;
+ case XK_grave:
+ nKey = KEY_QUOTELEFT;
+ *pcPrintable = '`';
+ break;
+ case XK_bracketleft:
+ nKey = KEY_BRACKETLEFT;
+ *pcPrintable = '[';
+ break;
+ case XK_bracketright:
+ nKey = KEY_BRACKETRIGHT;
+ *pcPrintable = ']';
+ break;
+ case XK_semicolon:
+ nKey = KEY_SEMICOLON;
+ *pcPrintable = ';';
+ break;
+ case XK_quoteright:
+ nKey = KEY_QUOTERIGHT;
+ *pcPrintable = '\'';
+ break;
+ // - - - - - - - - - - - - - Apollo - - - - - - - - - - - - - 0x1000
+ case 0x1000FF02: // apXK_Copy
+ nKey = KEY_COPY;
+ break;
+ case 0x1000FF03: // apXK_Cut
+ nKey = KEY_CUT;
+ break;
+ case 0x1000FF04: // apXK_Paste
+ nKey = KEY_PASTE;
+ break;
+ case 0x1000FF14: // apXK_Repeat
+ nKey = KEY_REPEAT;
+ break;
+ // Exit, Save
+ // - - - - - - - - - - - - - - D E C - - - - - - - - - - - - - 0x1000
+ case 0x1000FF00:
+ nKey = KEY_DELETE;
+ break;
+ // - - - - - - - - - - - - - - H P - - - - - - - - - - - - - 0x1000
+ case 0x1000FF73: // hpXK_DeleteChar
+ nKey = KEY_DELETE;
+ break;
+ case 0x1000FF74: // hpXK_BackTab
+ case 0x1000FF75: // hpXK_KP_BackTab
+ nKey = KEY_TAB;
+ break;
+ // - - - - - - - - - - - - - - I B M - - - - - - - - - - - - -
+ // - - - - - - - - - - - - - - O S F - - - - - - - - - - - - - 0x1004
+ case 0x1004FF02: // osfXK_Copy
+ nKey = KEY_COPY;
+ break;
+ case 0x1004FF03: // osfXK_Cut
+ nKey = KEY_CUT;
+ break;
+ case 0x1004FF04: // osfXK_Paste
+ nKey = KEY_PASTE;
+ break;
+ case 0x1004FF07: // osfXK_BackTab
+ nKey = KEY_TAB;
+ break;
+ case 0x1004FF08: // osfXK_BackSpace
+ nKey = KEY_BACKSPACE;
+ break;
+ case 0x1004FF1B: // osfXK_Escape
+ nKey = KEY_ESCAPE;
+ break;
+ // Up, Down, Left, Right, PageUp, PageDown
+ // - - - - - - - - - - - - - - S C O - - - - - - - - - - - - -
+ // - - - - - - - - - - - - - - S G I - - - - - - - - - - - - - 0x1007
+ // - - - - - - - - - - - - - - S N I - - - - - - - - - - - - -
+ // - - - - - - - - - - - - - - S U N - - - - - - - - - - - - - 0x1005
+ case 0x1005FF10: // SunXK_F36
+ nKey = KEY_F11;
+ break;
+ case 0x1005FF11: // SunXK_F37
+ nKey = KEY_F12;
+ break;
+ case 0x1005FF70: // SunXK_Props
+ nKey = KEY_PROPERTIES;
+ break;
+ case 0x1005FF71: // SunXK_Front
+ nKey = KEY_FRONT;
+ break;
+ case 0x1005FF72: // SunXK_Copy
+ nKey = KEY_COPY;
+ break;
+ case 0x1005FF73: // SunXK_Open
+ nKey = KEY_OPEN;
+ break;
+ case 0x1005FF74: // SunXK_Paste
+ nKey = KEY_PASTE;
+ break;
+ case 0x1005FF75: // SunXK_Cut
+ nKey = KEY_CUT;
+ break;
+ }
+ return nKey;
+}
+
+KeySym SalDisplay::GetKeySym( XKeyEvent *pEvent,
+ char *pPrintable,
+ int *pLen,
+ KeySym *pUnmodifiedKeySym,
+ Status *pStatusReturn,
+ XIC aInputContext ) const
+{
+ KeySym nKeySym = 0;
+ memset( pPrintable, 0, *pLen );
+ *pStatusReturn = 0;
+
+ SalI18N_InputMethod* const pInputMethod =
+ pXLib_ ? pXLib_->GetInputMethod() : nullptr;
+
+ // first get the printable of the possibly modified KeySym
+ if ( (aInputContext == nullptr)
+ || (pEvent->type == KeyRelease)
+ || (pInputMethod != nullptr && pInputMethod->PosixLocale()) )
+ {
+ // XmbLookupString must not be called for KeyRelease events
+ // Cannot enter space in c locale problem #89616# #88978# btraq #4478197
+ *pLen = XLookupString( pEvent, pPrintable, 1, &nKeySym, nullptr );
+ }
+ else
+ {
+ *pLen = XmbLookupString( aInputContext,
+ pEvent, pPrintable, *pLen - 1, &nKeySym, pStatusReturn );
+
+ // Lookup the string again, now with appropriate size
+ if ( *pStatusReturn == XBufferOverflow )
+ {
+ pPrintable[ 0 ] = '\0';
+ return 0;
+ }
+
+ switch ( *pStatusReturn )
+ {
+ case XBufferOverflow:
+ /* unhandled error */
+ break;
+ case XLookupNone:
+ /* unhandled error */
+ break;
+ case XLookupKeySym:
+ /* this is a strange one: on exceed sometimes
+ * no printable is returned for the first char entered,
+ * just to retry lookup solves the problem. The problem
+ * is not yet fully understood, so restrict 2nd lookup
+ * to 7bit ascii chars */
+ if ( (XK_space <= nKeySym) && (XK_asciitilde >= nKeySym) )
+ {
+ *pLen = 1;
+ pPrintable[ 0 ] = static_cast<char>(nKeySym);
+ }
+ break;
+ case XLookupBoth:
+ case XLookupChars:
+
+ /* nothing to, char already in pPrintable */
+ break;
+ }
+ }
+
+ if( !bNumLockFromXS_
+ && (IsCursorKey(nKeySym)
+ || IsFunctionKey(nKeySym)
+ || IsKeypadKey(nKeySym)
+ || XK_Delete == nKeySym ) )
+ {
+ // For some X-servers special care is needed for Keypad keys.
+ // For example Solaris XServer:
+ // 2, 4, 6, 8 are classified as Cursorkeys (Up, Down, Left, Right)
+ // 1, 3, 5, 9 are classified as Functionkeys (F27,F29,F33,F35)
+ // 0 as Keypadkey, and the decimal point key not at all (KP_Insert)
+ KeySym nNewKeySym = XLookupKeysym( pEvent, nNumLockIndex_ );
+ if( nNewKeySym != NoSymbol )
+ nKeySym = nNewKeySym;
+ }
+
+ // Now get the unmodified KeySym for KeyCode retrieval
+ // try to strip off modifiers, e.g. Ctrl-$ becomes Ctrl-Shift-4
+ *pUnmodifiedKeySym = XkbKeycodeToKeysym( GetDisplay(), pEvent->keycode, 0, 0);
+
+ return nKeySym;
+}
+
+// Pointer
+static unsigned char nullmask_bits[] = { 0x00, 0x00, 0x00, 0x00 };
+static unsigned char nullcurs_bits[] = { 0x00, 0x00, 0x00, 0x00 };
+
+#define MAKE_BITMAP( name ) \
+ XCreateBitmapFromData( pDisp_, \
+ DefaultRootWindow( pDisp_ ), \
+ reinterpret_cast<const char*>(name##_bits), \
+ name##_width, \
+ name##_height )
+
+#define MAKE_CURSOR( name ) \
+ aCursBitmap = MAKE_BITMAP( name##curs ); \
+ aMaskBitmap = MAKE_BITMAP( name##mask ); \
+ nXHot = name##curs_x_hot; \
+ nYHot = name##curs_y_hot
+
+Cursor SalDisplay::GetPointer( PointerStyle ePointerStyle )
+{
+ Cursor &aCur = aPointerCache_[ePointerStyle];
+
+ if( aCur != None )
+ return aCur;
+
+ Pixmap aCursBitmap = None, aMaskBitmap = None;
+ unsigned int nXHot = 0, nYHot = 0;
+
+ switch( ePointerStyle )
+ {
+ case PointerStyle::Null:
+ MAKE_CURSOR( null );
+ break;
+ case PointerStyle::Arrow:
+ aCur = XCreateFontCursor( pDisp_, XC_left_ptr );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::Wait:
+ aCur = XCreateFontCursor( pDisp_, XC_watch );
+ break;
+ case PointerStyle::Text: // Mouse Pointer is a "I" Beam
+ aCur = XCreateFontCursor( pDisp_, XC_xterm );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::Help:
+ aCur = XCreateFontCursor( pDisp_, XC_question_arrow );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::Cross: // Mouse Pointer is a cross
+ aCur = XCreateFontCursor( pDisp_, XC_crosshair );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::NSize:
+ aCur = XCreateFontCursor( pDisp_, XC_sb_v_double_arrow );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::SSize:
+ aCur = XCreateFontCursor( pDisp_, XC_sb_v_double_arrow );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::WSize:
+ aCur = XCreateFontCursor( pDisp_, XC_sb_h_double_arrow );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::ESize:
+ aCur = XCreateFontCursor( pDisp_, XC_sb_h_double_arrow );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::WindowNSize:
+ aCur = XCreateFontCursor( pDisp_, XC_top_side );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::WindowSSize:
+ aCur = XCreateFontCursor( pDisp_, XC_bottom_side );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::WindowWSize:
+ aCur = XCreateFontCursor( pDisp_, XC_left_side );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::WindowESize:
+ aCur = XCreateFontCursor( pDisp_, XC_right_side );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::NWSize:
+ aCur = XCreateFontCursor( pDisp_, XC_top_left_corner );
+ break;
+ case PointerStyle::NESize:
+ aCur = XCreateFontCursor( pDisp_, XC_top_right_corner );
+ break;
+ case PointerStyle::SWSize:
+ aCur = XCreateFontCursor( pDisp_, XC_bottom_left_corner );
+ break;
+ case PointerStyle::SESize:
+ aCur = XCreateFontCursor( pDisp_, XC_bottom_right_corner );
+ break;
+ case PointerStyle::WindowNWSize:
+ aCur = XCreateFontCursor( pDisp_, XC_top_left_corner );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::WindowNESize:
+ aCur = XCreateFontCursor( pDisp_, XC_top_right_corner );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::WindowSWSize:
+ aCur = XCreateFontCursor( pDisp_, XC_bottom_left_corner );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::WindowSESize:
+ aCur = XCreateFontCursor( pDisp_, XC_bottom_right_corner );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::HSplit:
+ aCur = XCreateFontCursor( pDisp_, XC_sb_h_double_arrow );
+ break;
+ case PointerStyle::VSplit:
+ aCur = XCreateFontCursor( pDisp_, XC_sb_v_double_arrow );
+ break;
+ case PointerStyle::HSizeBar:
+ aCur = XCreateFontCursor( pDisp_, XC_sb_h_double_arrow ); // ???
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::VSizeBar:
+ aCur = XCreateFontCursor( pDisp_, XC_sb_v_double_arrow ); // ???
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::RefHand:
+ aCur = XCreateFontCursor( pDisp_, XC_hand1 );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::Hand:
+ aCur = XCreateFontCursor( pDisp_, XC_hand2 );
+ break;
+ case PointerStyle::Magnify:
+ MAKE_CURSOR( magnify_ );
+ break;
+ case PointerStyle::Fill:
+ MAKE_CURSOR( fill_ );
+ break;
+ case PointerStyle::Move:
+ aCur = XCreateFontCursor( pDisp_, XC_fleur );
+ break;
+ case PointerStyle::MoveData:
+ MAKE_CURSOR( movedata_ );
+ break;
+ case PointerStyle::CopyData:
+ MAKE_CURSOR( copydata_ );
+ break;
+ case PointerStyle::MoveFile:
+ MAKE_CURSOR( movefile_ );
+ break;
+ case PointerStyle::CopyFile:
+ MAKE_CURSOR( copyfile_ );
+ break;
+ case PointerStyle::MoveFiles:
+ MAKE_CURSOR( movefiles_ );
+ break;
+ case PointerStyle::CopyFiles:
+ MAKE_CURSOR( copyfiles_ );
+ break;
+ case PointerStyle::NotAllowed:
+ MAKE_CURSOR( nodrop_ );
+ break;
+ case PointerStyle::Rotate:
+ MAKE_CURSOR( rotate_ );
+ break;
+ case PointerStyle::HShear:
+ MAKE_CURSOR( hshear_ );
+ break;
+ case PointerStyle::VShear:
+ MAKE_CURSOR( vshear_ );
+ break;
+ case PointerStyle::DrawLine:
+ MAKE_CURSOR( drawline_ );
+ break;
+ case PointerStyle::DrawRect:
+ MAKE_CURSOR( drawrect_ );
+ break;
+ case PointerStyle::DrawPolygon:
+ MAKE_CURSOR( drawpolygon_ );
+ break;
+ case PointerStyle::DrawBezier:
+ MAKE_CURSOR( drawbezier_ );
+ break;
+ case PointerStyle::DrawArc:
+ MAKE_CURSOR( drawarc_ );
+ break;
+ case PointerStyle::DrawPie:
+ MAKE_CURSOR( drawpie_ );
+ break;
+ case PointerStyle::DrawCircleCut:
+ MAKE_CURSOR( drawcirclecut_ );
+ break;
+ case PointerStyle::DrawEllipse:
+ MAKE_CURSOR( drawellipse_ );
+ break;
+ case PointerStyle::DrawConnect:
+ MAKE_CURSOR( drawconnect_ );
+ break;
+ case PointerStyle::DrawText:
+ MAKE_CURSOR( drawtext_ );
+ break;
+ case PointerStyle::Mirror:
+ MAKE_CURSOR( mirror_ );
+ break;
+ case PointerStyle::Crook:
+ MAKE_CURSOR( crook_ );
+ break;
+ case PointerStyle::Crop:
+ MAKE_CURSOR( crop_ );
+ break;
+ case PointerStyle::MovePoint:
+ MAKE_CURSOR( movepoint_ );
+ break;
+ case PointerStyle::MoveBezierWeight:
+ MAKE_CURSOR( movebezierweight_ );
+ break;
+ case PointerStyle::DrawFreehand:
+ MAKE_CURSOR( drawfreehand_ );
+ break;
+ case PointerStyle::DrawCaption:
+ MAKE_CURSOR( drawcaption_ );
+ break;
+ case PointerStyle::Pen: // Mouse Pointer is a pencil
+ aCur = XCreateFontCursor( pDisp_, XC_pencil );
+ SAL_WARN_IF( aCur == None, "vcl", "GetPointer: Could not define cursor" );
+ break;
+ case PointerStyle::LinkData:
+ MAKE_CURSOR( linkdata_ );
+ break;
+ case PointerStyle::MoveDataLink:
+ MAKE_CURSOR( movedlnk_ );
+ break;
+ case PointerStyle::CopyDataLink:
+ MAKE_CURSOR( copydlnk_ );
+ break;
+ case PointerStyle::LinkFile:
+ MAKE_CURSOR( linkfile_ );
+ break;
+ case PointerStyle::MoveFileLink:
+ MAKE_CURSOR( moveflnk_ );
+ break;
+ case PointerStyle::CopyFileLink:
+ MAKE_CURSOR( copyflnk_ );
+ break;
+ case PointerStyle::Chart:
+ MAKE_CURSOR( chart_ );
+ break;
+ case PointerStyle::Detective:
+ MAKE_CURSOR( detective_ );
+ break;
+ case PointerStyle::PivotCol:
+ MAKE_CURSOR( pivotcol_ );
+ break;
+ case PointerStyle::PivotRow:
+ MAKE_CURSOR( pivotrow_ );
+ break;
+ case PointerStyle::PivotField:
+ MAKE_CURSOR( pivotfld_ );
+ break;
+ case PointerStyle::PivotDelete:
+ MAKE_CURSOR( pivotdel_ );
+ break;
+ case PointerStyle::Chain:
+ MAKE_CURSOR( chain_ );
+ break;
+ case PointerStyle::ChainNotAllowed:
+ MAKE_CURSOR( chainnot_ );
+ break;
+ case PointerStyle::AutoScrollN:
+ MAKE_CURSOR(asn_ );
+ break;
+ case PointerStyle::AutoScrollS:
+ MAKE_CURSOR( ass_ );
+ break;
+ case PointerStyle::AutoScrollW:
+ MAKE_CURSOR( asw_ );
+ break;
+ case PointerStyle::AutoScrollE:
+ MAKE_CURSOR( ase_ );
+ break;
+ case PointerStyle::AutoScrollNW:
+ MAKE_CURSOR( asnw_ );
+ break;
+ case PointerStyle::AutoScrollNE:
+ MAKE_CURSOR( asne_ );
+ break;
+ case PointerStyle::AutoScrollSW:
+ MAKE_CURSOR( assw_ );
+ break;
+ case PointerStyle::AutoScrollSE:
+ MAKE_CURSOR( asse_ );
+ break;
+ case PointerStyle::AutoScrollNS:
+ MAKE_CURSOR( asns_ );
+ break;
+ case PointerStyle::AutoScrollWE:
+ MAKE_CURSOR( aswe_ );
+ break;
+ case PointerStyle::AutoScrollNSWE:
+ MAKE_CURSOR( asnswe_ );
+ break;
+ case PointerStyle::TextVertical:
+ MAKE_CURSOR( vertcurs_ );
+ break;
+
+ // #i32329# Enhanced table selection
+ case PointerStyle::TabSelectS:
+ MAKE_CURSOR( tblsels_ );
+ break;
+ case PointerStyle::TabSelectE:
+ MAKE_CURSOR( tblsele_ );
+ break;
+ case PointerStyle::TabSelectSE:
+ MAKE_CURSOR( tblselse_ );
+ break;
+ case PointerStyle::TabSelectW:
+ MAKE_CURSOR( tblselw_ );
+ break;
+ case PointerStyle::TabSelectSW:
+ MAKE_CURSOR( tblselsw_ );
+ break;
+
+ case PointerStyle::HideWhitespace:
+ MAKE_CURSOR( hidewhitespace_ );
+ break;
+ case PointerStyle::ShowWhitespace:
+ MAKE_CURSOR( showwhitespace_ );
+ break;
+ case PointerStyle::FatCross:
+ MAKE_CURSOR( fatcross_ );
+ break;
+
+ default:
+ OSL_FAIL("pointer not implemented");
+ aCur = XCreateFontCursor( pDisp_, XC_arrow );
+ break;
+ }
+
+ if( None == aCur )
+ {
+ XColor aBlack, aWhite, aDummy;
+ Colormap hColormap = GetColormap(m_nXDefaultScreen).GetXColormap();
+
+ XAllocNamedColor( pDisp_, hColormap, "black", &aBlack, &aDummy );
+ XAllocNamedColor( pDisp_, hColormap, "white", &aWhite, &aDummy );
+
+ aCur = XCreatePixmapCursor( pDisp_,
+ aCursBitmap, aMaskBitmap,
+ &aBlack, &aWhite,
+ nXHot, nYHot );
+
+ XFreePixmap( pDisp_, aCursBitmap );
+ XFreePixmap( pDisp_, aMaskBitmap );
+ }
+
+ return aCur;
+}
+
+int SalDisplay::CaptureMouse( SalFrame *pCapture )
+{
+ static const char* pEnv = getenv( "SAL_NO_MOUSEGRABS" );
+
+ if( !pCapture )
+ {
+ m_pCapture = nullptr;
+ if( !pEnv || !*pEnv )
+ XUngrabPointer( GetDisplay(), CurrentTime );
+ XFlush( GetDisplay() );
+ return 0;
+ }
+
+ m_pCapture = nullptr;
+
+ // FIXME: get rid of X11SalFrame
+ const SystemEnvData* pEnvData = pCapture->GetSystemData();
+ if( !pEnv || !*pEnv )
+ {
+ int ret = XGrabPointer( GetDisplay(),
+ static_cast<::Window>(pEnvData->GetWindowHandle(pCapture)),
+ False,
+ PointerMotionMask| ButtonPressMask|ButtonReleaseMask,
+ GrabModeAsync,
+ GrabModeAsync,
+ None,
+ static_cast<X11SalFrame*>(pCapture)->GetCursor(),
+ CurrentTime );
+
+ if( ret != GrabSuccess )
+ {
+ SAL_WARN("vcl", "SalDisplay::CaptureMouse could not grab pointer: " << ret);
+ return -1;
+ }
+ }
+
+ m_pCapture = pCapture;
+ return 1;
+}
+
+// Events
+
+bool SalX11Display::IsEvent()
+{
+ if( HasUserEvents() || XEventsQueued( pDisp_, QueuedAlready ) )
+ return true;
+
+ XFlush( pDisp_ );
+ return false;
+}
+
+void SalX11Display::Yield()
+{
+ if( DispatchInternalEvent() )
+ return;
+
+ XEvent aEvent;
+ DBG_ASSERT(GetSalInstance()->GetYieldMutex()->IsCurrentThread(),
+ "will crash soon since solar mutex not locked in SalDisplay::Yield" );
+
+ XNextEvent( pDisp_, &aEvent );
+
+ // coverity[overrun-buffer-val : FALSE] - coverity has problems with uno::Sequence
+ Dispatch( &aEvent );
+
+#ifdef DBG_UTIL
+ if( GetX11SalData()->HasXErrorOccurred() )
+ {
+ XFlush( pDisp_ );
+ DbgPrintDisplayEvent("SalDisplay::Yield (WasXError)", &aEvent);
+ }
+#endif
+ GetX11SalData()->ResetXErrorOccurred();
+}
+
+void SalX11Display::Dispatch( XEvent *pEvent )
+{
+ SalI18N_InputMethod* const pInputMethod =
+ pXLib_ ? pXLib_->GetInputMethod() : nullptr;
+
+ if( pInputMethod )
+ {
+ ::Window aFrameWindow = None;
+ if( pEvent->type == KeyPress || pEvent->type == KeyRelease )
+ {
+ const ::Window aWindow = pEvent->xkey.window;
+ for( auto pSalFrame : m_aFrames )
+ {
+ const X11SalFrame* pFrame = static_cast< const X11SalFrame* >( pSalFrame );
+ const ::Window aCurFrameWindow = pFrame->GetWindow();
+ if( aCurFrameWindow == aWindow || pFrame->GetShellWindow() == aWindow )
+ {
+ aFrameWindow = aCurFrameWindow;
+ break;
+ }
+ }
+ }
+ if( pInputMethod->FilterEvent( pEvent, aFrameWindow ) )
+ return;
+ }
+
+ SalInstance* pInstance = GetSalInstance();
+ pInstance->CallEventCallback( pEvent, sizeof( XEvent ) );
+
+ switch( pEvent->type )
+ {
+ case MotionNotify:
+ while( XCheckWindowEvent( pEvent->xany.display,
+ pEvent->xany.window,
+ ButtonMotionMask,
+ pEvent ) )
+ ;
+ m_nLastUserEventTime = pEvent->xmotion.time;
+ break;
+ case PropertyNotify:
+ if( pEvent->xproperty.atom == getWMAdaptor()->getAtom( WMAdaptor::VCL_SYSTEM_SETTINGS ) )
+ {
+ for(const ScreenData & rScreen : m_aScreens)
+ {
+ if( pEvent->xproperty.window == rScreen.m_aRefWindow )
+ {
+ for (auto pSalFrame : m_aFrames )
+ pSalFrame->CallCallback( SalEvent::SettingsChanged, nullptr );
+ return;
+ }
+ }
+ }
+ break;
+ case MappingNotify:
+ if( MappingModifier == pEvent->xmapping.request )
+ {
+ XRefreshKeyboardMapping( &pEvent->xmapping );
+ ModifierMapping();
+ }
+ break;
+ case ButtonPress:
+ case ButtonRelease:
+ m_nLastUserEventTime = pEvent->xbutton.time;
+ break;
+ case KeyPress:
+ case KeyRelease:
+ m_nLastUserEventTime = pEvent->xkey.time;
+ break;
+ default:
+
+ if ( GetKbdExtension()->UseExtension()
+ && GetKbdExtension()->GetEventBase() == pEvent->type )
+ {
+ GetKbdExtension()->Dispatch( pEvent );
+ return;
+ }
+ break;
+ }
+
+ for (auto pSalFrame : m_aFrames )
+ {
+ X11SalFrame* pFrame = static_cast<X11SalFrame*>( pSalFrame );
+
+ ::Window aDispatchWindow = pEvent->xany.window;
+ if( pFrame->GetWindow() == aDispatchWindow
+ || pFrame->GetShellWindow() == aDispatchWindow
+ || pFrame->GetForeignParent() == aDispatchWindow
+ )
+ {
+ pFrame->Dispatch( pEvent );
+ return;
+ }
+ if( pEvent->type == ConfigureNotify && pEvent->xconfigure.window == pFrame->GetStackingWindow() )
+ {
+ pFrame->Dispatch( pEvent );
+ return;
+ }
+ }
+
+ // dispatch to salobjects
+ X11SalObject::Dispatch( pEvent );
+
+ // is this perhaps a root window that changed size ?
+ processRandREvent( pEvent );
+}
+
+#ifdef DBG_UTIL
+void SalDisplay::DbgPrintDisplayEvent(const char *pComment, const XEvent *pEvent) const
+{
+ static const char* const EventNames[] =
+ {
+ nullptr,
+ nullptr,
+ "KeyPress",
+ "KeyRelease",
+ "ButtonPress",
+ "ButtonRelease",
+ "MotionNotify",
+ "EnterNotify",
+ "LeaveNotify",
+ "FocusIn",
+ "FocusOut",
+ "KeymapNotify",
+ "Expose",
+ "GraphicsExpose",
+ "NoExpose",
+ "VisibilityNotify",
+ "CreateNotify",
+ "DestroyNotify",
+ "UnmapNotify",
+ "MapNotify",
+ "MapRequest",
+ "ReparentNotify",
+ "ConfigureNotify",
+ "ConfigureRequest",
+ "GravityNotify",
+ "ResizeRequest",
+ "CirculateNotify",
+ "CirculateRequest",
+ "PropertyNotify",
+ "SelectionClear",
+ "SelectionRequest",
+ "SelectionNotify",
+ "ColormapNotify",
+ "ClientMessage",
+ "MappingNotify"
+ };
+
+ if( pEvent->type <= MappingNotify )
+ {
+ SAL_INFO("vcl.app", "[" << pComment << "] "
+ << EventNames[pEvent->type]
+ << " s=" << pEvent->xany.send_event
+ << " w=" << pEvent->xany.window);
+
+ switch( pEvent->type )
+ {
+ case KeyPress:
+ case KeyRelease:
+ SAL_INFO("vcl.app", "\t\ts=" << pEvent->xkey.state
+ << " c=" << pEvent->xkey.keycode);
+ break;
+
+ case ButtonPress:
+ case ButtonRelease:
+ SAL_INFO("vcl.app", "\t\ts=" << pEvent->xbutton.state
+ << " b=" << pEvent->xbutton.button
+ << " x=" << pEvent->xbutton.x
+ << " y=" << pEvent->xbutton.y
+ << " rx=" << pEvent->xbutton.x_root
+ << " ry=" << pEvent->xbutton.y_root);
+ break;
+
+ case MotionNotify:
+ SAL_INFO("vcl.app", "\t\ts=" << pEvent->xmotion.state
+ << " x=" << pEvent->xmotion.x
+ << " y=" << pEvent->xmotion.y);
+ break;
+
+ case EnterNotify:
+ case LeaveNotify:
+ SAL_INFO("vcl.app", "\t\tm=" << pEvent->xcrossing.mode
+ << " f=" << pEvent->xcrossing.focus
+ << " x=" << pEvent->xcrossing.x
+ << " y=" << pEvent->xcrossing.y);
+ break;
+
+ case FocusIn:
+ case FocusOut:
+ SAL_INFO("vcl.app", "\t\tm=" << pEvent->xfocus.mode
+ << " d=" << pEvent->xfocus.detail);
+ break;
+
+ case Expose:
+ case GraphicsExpose:
+ SAL_INFO("vcl.app", "\t\tc=" << pEvent->xexpose.count
+ << " " << pEvent->xexpose.width
+ << "*" << pEvent->xexpose.height
+ << " " << pEvent->xexpose.x
+ << "+" << pEvent->xexpose.y );
+ break;
+
+ case VisibilityNotify:
+ SAL_INFO("vcl.app", "\t\ts=" << pEvent->xvisibility.state);
+ break;
+
+ case CreateNotify:
+ case DestroyNotify:
+ break;
+
+ case MapNotify:
+ case UnmapNotify:
+ break;
+
+ case ReparentNotify:
+ SAL_INFO("vcl.app", "\t\tp=" << sal::static_int_cast< int >(
+ pEvent->xreparent.parent)
+ << " x=" << pEvent->xreparent.x
+ << " y=" << pEvent->xreparent.y );
+ break;
+
+ case ConfigureNotify:
+ SAL_INFO("vcl.app", "\t\tb=" << pEvent->xconfigure.border_width
+ << " " << pEvent->xconfigure.width
+ << "*" << pEvent->xconfigure.height
+ << " " << pEvent->xconfigure.x
+ << "+" << pEvent->xconfigure.y);
+ break;
+
+ case PropertyNotify:
+ SAL_INFO("vcl.app", "\t\ta=" << GetAtomName(
+ pDisp_, pEvent->xproperty.atom)
+ << std::showbase << std::hex << std::uppercase
+ << " (" << sal::static_int_cast< unsigned int >(
+ pEvent->xproperty.atom) << ").");
+ break;
+
+ case ColormapNotify:
+ SAL_INFO("vcl.app", "\t\tc=" << pEvent->xcolormap.colormap
+ << " n=" << pEvent->xcolormap.c_new
+ << " s=" << pEvent->xcolormap.state);
+ break;
+
+ case ClientMessage:
+ SAL_INFO("vcl.app", "\t\ta=" << GetAtomName(
+ pDisp_, pEvent->xclient.message_type)
+ << std::showbase << std::hex << std::uppercase
+ << " (" << sal::static_int_cast< unsigned int >(
+ pEvent->xclient.message_type) << ")"
+ << std::dec
+ << " f=" << pEvent->xclient.format
+ << std::hex
+ << " [" << pEvent->xclient.data.l[0]
+ << "," << pEvent->xclient.data.l[1]
+ << "," << pEvent->xclient.data.l[2]
+ << "," << pEvent->xclient.data.l[3]
+ << "," << pEvent->xclient.data.l[4]
+ << "]");
+ break;
+
+ case MappingNotify:
+ SAL_INFO("vcl.app", "\t\tr="
+ << (MappingModifier == pEvent->xmapping.request ?
+ "MappingModifier" :
+ (MappingKeyboard == pEvent->xmapping.request ?
+ "MappingKeyboard" : "MappingPointer"))
+ << "d");
+
+ break;
+ }
+ }
+ else
+ SAL_INFO("vcl.app", "[" << pComment << "] "
+ << pEvent->type
+ << " s=" << pEvent->xany.send_event
+ << " w=" << pEvent->xany.window);
+}
+
+void SalDisplay::PrintInfo() const
+{
+ if( IsDisplay() )
+ {
+ SAL_INFO( "vcl", "Environment" );
+ SAL_INFO( "vcl", "\t$DISPLAY \t\"" << GetEnv( "DISPLAY" ) << "\"");
+ SAL_INFO( "vcl", "\t$SAL_VISUAL \t\"" << GetEnv( "SAL_VISUAL" ) << "\"");
+ SAL_INFO( "vcl", "\t$SAL_IGNOREXERRORS\t\"" << GetEnv( "SAL_IGNOREXERRORS" ) << "\"");
+ SAL_INFO( "vcl", "\t$SAL_PROPERTIES \t\"" << GetEnv( "SAL_PROPERTIES" ) << "\"");
+ SAL_INFO( "vcl", "\t$SAL_SYNCHRONIZE \t\"" << GetEnv( "SAL_SYNCHRONIZE" ) << "\"");
+
+ char sHostname[ 120 ];
+ gethostname (sHostname, 120 );
+ SAL_INFO( "vcl", "Client" );
+ SAL_INFO( "vcl", "\tHost \t\"" << sHostname << "\"");
+
+ SAL_INFO( "vcl", "Display" );
+ SAL_INFO( "vcl", "\tHost \t\"" << DisplayString(pDisp_) << "\"");
+ SAL_INFO( "vcl", "\tVendor (Release) \t\"" << ServerVendor(pDisp_) << " (" << VendorRelease(pDisp_) << ")\"");
+ SAL_INFO( "vcl", "\tProtocol \t" << ProtocolVersion(pDisp_) << "." << ProtocolRevision(pDisp_) );
+ SAL_INFO( "vcl", "\tScreen (count,def)\t" << m_nXDefaultScreen.getXScreen() << " (" << ScreenCount(pDisp_) << "," << DefaultScreen(pDisp_) << ")");
+ SAL_INFO( "vcl", "\tshift ctrl alt \t" << KeyStr( nShiftKeySym_ ) << " (0x" << std::hex << sal::static_int_cast< unsigned int >(nShiftKeySym_) << ") "
+ << KeyStr( nCtrlKeySym_ ) << " (0x" << sal::static_int_cast< unsigned int >(nCtrlKeySym_) << ") "
+ << KeyStr( nMod1KeySym_ ) << " (0x" << sal::static_int_cast< unsigned int >(nMod1KeySym_) << ")");
+ if( XExtendedMaxRequestSize(pDisp_) != 0 )
+ SAL_INFO( "vcl", "\tXMaxRequestSize \t" << XMaxRequestSize(pDisp_) * 4 << " " << XExtendedMaxRequestSize(pDisp_) * 4 << " [bytes]");
+ SAL_INFO( "vcl", "\tWMName \t" << getWMAdaptor()->getWindowManagerName() );
+ }
+ SAL_INFO( "vcl", "Screen" );
+ SAL_INFO( "vcl", "\tResolution/Size \t" << aResolution_.A() << "*" << aResolution_.B()
+ << " " << m_aScreens[m_nXDefaultScreen.getXScreen()].m_aSize.Width() << "*" << m_aScreens[m_nXDefaultScreen.getXScreen()].m_aSize.Height()
+ << " " << (Hypothenuse( DisplayWidthMM ( pDisp_, m_nXDefaultScreen.getXScreen() ),
+ DisplayHeightMM( pDisp_, m_nXDefaultScreen.getXScreen() ) ) / 25.4 ) << "\"" );
+ SAL_INFO( "vcl", "\tBlack&White \t" << GetColormap(m_nXDefaultScreen).GetBlackPixel() << " "
+ << GetColormap(m_nXDefaultScreen).GetWhitePixel() );
+ SAL_INFO( "vcl", "\tRGB \t0x" << std::hex << GetVisual(m_nXDefaultScreen).red_mask
+ << " 0x" << GetVisual(m_nXDefaultScreen).green_mask
+ << " 0x" << GetVisual(m_nXDefaultScreen).blue_mask);
+}
+#endif
+
+void SalDisplay::addXineramaScreenUnique( int i, tools::Long i_nX, tools::Long i_nY, tools::Long i_nWidth, tools::Long i_nHeight )
+{
+ // see if any frame buffers are at the same coordinates
+ // this can happen with weird configuration e.g. on
+ // XFree86 and Clone displays
+ const size_t nScreens = m_aXineramaScreens.size();
+ for( size_t n = 0; n < nScreens; n++ )
+ {
+ if( m_aXineramaScreens[n].Left() == i_nX &&
+ m_aXineramaScreens[n].Top() == i_nY )
+ {
+ if( m_aXineramaScreens[n].GetWidth() < i_nWidth ||
+ m_aXineramaScreens[n].GetHeight() < i_nHeight )
+ {
+ m_aXineramaScreenIndexMap[i] = n;
+ m_aXineramaScreens[n].SetSize( Size( i_nWidth, i_nHeight ) );
+ }
+ return;
+ }
+ }
+ m_aXineramaScreenIndexMap[i] = m_aXineramaScreens.size();
+ m_aXineramaScreens.emplace_back( Point( i_nX, i_nY ), Size( i_nWidth, i_nHeight ) );
+}
+
+void SalDisplay::InitXinerama()
+{
+ if( m_aScreens.size() > 1 )
+ {
+ m_bXinerama = false;
+ return; // multiple screens mean no xinerama
+ }
+#if defined(USE_XINERAMA_XORG)
+ if( !XineramaIsActive( pDisp_ ) )
+ return;
+
+ int nFramebuffers = 1;
+ XineramaScreenInfo* pScreens = XineramaQueryScreens( pDisp_, &nFramebuffers );
+ if( !pScreens )
+ return;
+
+ if( nFramebuffers > 1 )
+ {
+ m_aXineramaScreens = std::vector<tools::Rectangle>();
+ m_aXineramaScreenIndexMap = std::vector<int>(nFramebuffers);
+ for( int i = 0; i < nFramebuffers; i++ )
+ {
+ addXineramaScreenUnique( i, pScreens[i].x_org,
+ pScreens[i].y_org,
+ pScreens[i].width,
+ pScreens[i].height );
+ }
+ m_bXinerama = m_aXineramaScreens.size() > 1;
+ }
+ XFree( pScreens );
+#endif
+#if OSL_DEBUG_LEVEL > 1
+ if( m_bXinerama )
+ {
+ for (auto const& screen : m_aXineramaScreens)
+ SAL_INFO("vcl.app", "Xinerama screen: "
+ << screen.GetWidth()
+ << "x" << screen.GetHeight()
+ << "+" << screen.Left()
+ << "+" << screen.Top());
+ }
+#endif
+}
+
+extern "C"
+{
+ static Bool timestamp_predicate( Display*, XEvent* i_pEvent, XPointer i_pArg )
+ {
+ SalDisplay* pSalDisplay = reinterpret_cast<SalDisplay*>(i_pArg);
+ if( i_pEvent->type == PropertyNotify &&
+ i_pEvent->xproperty.window == pSalDisplay->GetDrawable( pSalDisplay->GetDefaultXScreen() ) &&
+ i_pEvent->xproperty.atom == pSalDisplay->getWMAdaptor()->getAtom( WMAdaptor::SAL_GETTIMEEVENT )
+ )
+ return True;
+
+ return False;
+ }
+}
+
+Time SalDisplay::GetEventTimeImpl( bool i_bAlwaysReget ) const
+{
+ if( m_nLastUserEventTime == CurrentTime || i_bAlwaysReget )
+ {
+ // get current server time
+ unsigned char c = 0;
+ XEvent aEvent;
+ Atom nAtom = getWMAdaptor()->getAtom( WMAdaptor::SAL_GETTIMEEVENT );
+ XChangeProperty( GetDisplay(), GetDrawable( GetDefaultXScreen() ),
+ nAtom, nAtom, 8, PropModeReplace, &c, 1 );
+ XIfEvent( GetDisplay(), &aEvent, timestamp_predicate, reinterpret_cast<XPointer>(const_cast<SalDisplay *>(this)));
+ m_nLastUserEventTime = aEvent.xproperty.time;
+ }
+ return m_nLastUserEventTime;
+}
+
+bool SalDisplay::XIfEventWithTimeout( XEvent* o_pEvent, XPointer i_pPredicateData,
+ X_if_predicate i_pPredicate ) const
+{
+ /* #i99360# ugly workaround an X11 library bug
+ this replaces the following call:
+ XIfEvent( GetDisplay(), o_pEvent, i_pPredicate, i_pPredicateData );
+ */
+ bool bRet = true;
+
+ if( ! XCheckIfEvent( GetDisplay(), o_pEvent, i_pPredicate, i_pPredicateData ) )
+ {
+ // wait for some event to arrive
+ struct pollfd aFD;
+ aFD.fd = ConnectionNumber(GetDisplay());
+ aFD.events = POLLIN;
+ aFD.revents = 0;
+ tools::Long nTimeout = 1000;
+ (void)poll(&aFD, 1, nTimeout);
+ if( ! XCheckIfEvent( GetDisplay(), o_pEvent, i_pPredicate, i_pPredicateData ) )
+ {
+ (void)poll(&aFD, 1, nTimeout); // try once more for a packet of events from the Xserver
+ if( ! XCheckIfEvent( GetDisplay(), o_pEvent, i_pPredicate, i_pPredicateData ) )
+ {
+ bRet = false;
+ }
+ }
+ }
+ return bRet;
+}
+
+SalVisual::SalVisual()
+ : eRGBMode_(SalRGB::RGB), nRedShift_(0), nGreenShift_(0), nBlueShift_(0)
+ , nRedBits_(0), nGreenBits_(0), nBlueBits_(0)
+{
+ visual = nullptr;
+}
+
+SalVisual::SalVisual( const XVisualInfo* pXVI )
+{
+ *static_cast<XVisualInfo*>(this) = *pXVI;
+ if( GetClass() != TrueColor )
+ {
+ eRGBMode_ = SalRGB::RGB;
+ nRedShift_ = nGreenShift_ = nBlueShift_ = 0;
+ nRedBits_ = nGreenBits_ = nBlueBits_ = 0;
+ return;
+ }
+
+ nRedShift_ = sal_Shift( red_mask );
+ nGreenShift_ = sal_Shift( green_mask );
+ nBlueShift_ = sal_Shift( blue_mask );
+
+ nRedBits_ = sal_significantBits( red_mask );
+ nGreenBits_ = sal_significantBits( green_mask );
+ nBlueBits_ = sal_significantBits( blue_mask );
+
+ if( GetDepth() == 24 )
+ if( red_mask == 0xFF0000 )
+ if( green_mask == 0xFF00 )
+ if( blue_mask == 0xFF )
+ eRGBMode_ = SalRGB::RGB;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else if( blue_mask == 0xFF00 )
+ if( green_mask == 0xFF )
+ eRGBMode_ = SalRGB::RBG;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else if( green_mask == 0xFF0000 )
+ if( red_mask == 0xFF00 )
+ if( blue_mask == 0xFF )
+ eRGBMode_ = SalRGB::GRB;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else if( blue_mask == 0xFF00 )
+ if( red_mask == 0xFF )
+ eRGBMode_ = SalRGB::GBR;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else if( blue_mask == 0xFF0000 )
+ if( red_mask == 0xFF00 )
+ if( green_mask == 0xFF )
+ eRGBMode_ = SalRGB::BRG;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else if( green_mask == 0xFF00 )
+ if( red_mask == 0xFF )
+ eRGBMode_ = SalRGB::BGR;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+ else
+ eRGBMode_ = SalRGB::otherSalRGB;
+}
+
+// Converts the order of bytes of a Pixel into bytes of a Color
+// This is not reversible for the 6 XXXA
+
+// Color is RGB (ABGR) a=0xFF000000, r=0xFF0000, g=0xFF00, b=0xFF
+
+#define SALCOLOR SalRGB::RGB
+#define SALCOLORREVERSE SalRGB::BGR
+
+Color SalVisual::GetTCColor( Pixel nPixel ) const
+{
+ if( SALCOLOR == eRGBMode_ )
+ return Color(ColorTransparency, nPixel);
+
+ if( SALCOLORREVERSE == eRGBMode_ )
+ return Color( (nPixel & 0x0000FF),
+ (nPixel & 0x00FF00) >> 8,
+ (nPixel & 0xFF0000) >> 16);
+
+ Pixel r = nPixel & red_mask;
+ Pixel g = nPixel & green_mask;
+ Pixel b = nPixel & blue_mask;
+
+ if( SalRGB::otherSalRGB != eRGBMode_ ) // 8+8+8=24
+ return Color( r >> nRedShift_,
+ g >> nGreenShift_,
+ b >> nBlueShift_ );
+
+ if( nRedShift_ > 0 ) r >>= nRedShift_; else r <<= -nRedShift_;
+ if( nGreenShift_ > 0 ) g >>= nGreenShift_; else g <<= -nGreenShift_;
+ if( nBlueShift_ > 0 ) b >>= nBlueShift_; else b <<= -nBlueShift_;
+
+ if( nRedBits_ != 8 )
+ r |= (r & 0xff) >> (8-nRedBits_);
+ if( nGreenBits_ != 8 )
+ g |= (g & 0xff) >> (8-nGreenBits_);
+ if( nBlueBits_ != 8 )
+ b |= (b & 0xff) >> (8-nBlueBits_);
+
+ return Color( r, g, b );
+}
+
+Pixel SalVisual::GetTCPixel( Color nColor ) const
+{
+ if( SALCOLOR == eRGBMode_ )
+ return static_cast<Pixel>(sal_uInt32(nColor));
+
+ Pixel r = static_cast<Pixel>( nColor.GetRed() );
+ Pixel g = static_cast<Pixel>( nColor.GetGreen() );
+ Pixel b = static_cast<Pixel>( nColor.GetBlue() );
+
+ if( SALCOLORREVERSE == eRGBMode_ )
+ return (b << 16) | (g << 8) | r;
+
+ if( SalRGB::otherSalRGB != eRGBMode_ ) // 8+8+8=24
+ return (r << nRedShift_) | (g << nGreenShift_) | (b << nBlueShift_);
+
+ if( nRedShift_ > 0 ) r <<= nRedShift_; else r >>= -nRedShift_;
+ if( nGreenShift_ > 0 ) g <<= nGreenShift_; else g >>= -nGreenShift_;
+ if( nBlueShift_ > 0 ) b <<= nBlueShift_; else b >>= -nBlueShift_;
+
+ return (r&red_mask) | (g&green_mask) | (b&blue_mask);
+}
+
+SalColormap::SalColormap( const SalDisplay *pDisplay, Colormap hColormap,
+ SalX11Screen nXScreen )
+ : m_pDisplay( pDisplay ),
+ m_hColormap( hColormap )
+{
+ m_aVisual = m_pDisplay->GetVisual( nXScreen );
+
+ XColor aColor;
+
+ GetXPixel( aColor, 0x00, 0x00, 0x00 );
+ m_nBlackPixel = aColor.pixel;
+
+ GetXPixel( aColor, 0xFF, 0xFF, 0xFF );
+ m_nWhitePixel = aColor.pixel;
+
+ m_nUsed = 1 << m_aVisual.GetDepth();
+
+ if( m_aVisual.GetClass() != PseudoColor )
+ return;
+
+ int r, g, b;
+
+ // black, white, gray, ~gray = 4
+ GetXPixels( aColor, 0xC0, 0xC0, 0xC0 );
+
+ // light colors: 3 * 2 = 6
+
+ GetXPixels( aColor, 0x00, 0x00, 0xFF );
+ GetXPixels( aColor, 0x00, 0xFF, 0x00 );
+ GetXPixels( aColor, 0x00, 0xFF, 0xFF );
+
+ // standard colors: 7 * 2 = 14
+ GetXPixels( aColor, 0x00, 0x00, 0x80 );
+ GetXPixels( aColor, 0x00, 0x80, 0x00 );
+ GetXPixels( aColor, 0x00, 0x80, 0x80 );
+ GetXPixels( aColor, 0x80, 0x00, 0x00 );
+ GetXPixels( aColor, 0x80, 0x00, 0x80 );
+ GetXPixels( aColor, 0x80, 0x80, 0x00 );
+ GetXPixels( aColor, 0x80, 0x80, 0x80 );
+ GetXPixels( aColor, 0x00, 0xB8, 0xFF ); // Blue 7
+
+ // cube: 6*6*6 - 8 = 208
+ for( r = 0; r < 0x100; r += 0x33 ) // 0x33, 0x66, 0x99, 0xCC, 0xFF
+ for( g = 0; g < 0x100; g += 0x33 )
+ for( b = 0; b < 0x100; b += 0x33 )
+ GetXPixels( aColor, r, g, b );
+
+ // gray: 16 - 6 = 10
+ for( g = 0x11; g < 0xFF; g += 0x11 )
+ GetXPixels( aColor, g, g, g );
+
+ // green: 16 - 6 = 10
+ for( g = 0x11; g < 0xFF; g += 0x11 )
+ GetXPixels( aColor, 0, g, 0 );
+
+ // red: 16 - 6 = 10
+ for( r = 0x11; r < 0xFF; r += 0x11 )
+ GetXPixels( aColor, r, 0, 0 );
+
+ // blue: 16 - 6 = 10
+ for( b = 0x11; b < 0xFF; b += 0x11 )
+ GetXPixels( aColor, 0, 0, b );
+
+}
+
+// MonoChrome
+SalColormap::SalColormap()
+ : m_pDisplay( vcl_sal::getSalDisplay(GetGenericUnixSalData()) ),
+ m_hColormap( None ),
+ m_nWhitePixel( 1 ),
+ m_nBlackPixel( 0 ),
+ m_nUsed( 2 )
+{
+ m_aPalette = std::vector<Color>(m_nUsed);
+
+ m_aPalette[m_nBlackPixel] = COL_BLACK;
+ m_aPalette[m_nWhitePixel] = COL_WHITE;
+}
+
+// TrueColor
+SalColormap::SalColormap( sal_uInt16 nDepth )
+ : m_pDisplay( vcl_sal::getSalDisplay(GetGenericUnixSalData()) ),
+ m_hColormap( None ),
+ m_nWhitePixel( (1 << nDepth) - 1 ),
+ m_nBlackPixel( 0x00000000 ),
+ m_nUsed( 1 << nDepth )
+{
+ SalX11Screen nXScreen( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDefaultXScreen() );
+ const SalVisual *pVisual = &m_pDisplay->GetVisual( nXScreen );
+
+ if( pVisual->GetClass() == TrueColor && pVisual->GetDepth() == nDepth )
+ m_aVisual = *pVisual;
+ else
+ {
+ XVisualInfo aVI;
+
+ if( !XMatchVisualInfo( m_pDisplay->GetDisplay(),
+ m_pDisplay->GetDefaultXScreen().getXScreen(),
+ nDepth,
+ TrueColor,
+ &aVI ) )
+ {
+ aVI.visual = new Visual;
+ aVI.visualid = VisualID(-1);
+ aVI.screen = -1;
+ aVI.depth = nDepth;
+ aVI.c_class = TrueColor;
+ if( 24 == nDepth ) // 888
+ {
+ aVI.red_mask = 0xFF0000;
+ aVI.green_mask = 0x00FF00;
+ aVI.blue_mask = 0x0000FF;
+ }
+ else if( 8 == nDepth ) // 332
+ {
+ aVI.red_mask = 0x0000E0;
+ aVI.green_mask = 0x00001C;
+ aVI.blue_mask = 0x000003;
+ }
+ else
+ {
+ aVI.red_mask = 0x000000;
+ aVI.green_mask = 0x000000;
+ aVI.blue_mask = 0x000000;
+ }
+ aVI.colormap_size = 0;
+ aVI.bits_per_rgb = 8;
+
+ aVI.visual->ext_data = nullptr;
+ aVI.visual->visualid = aVI.visualid;
+ aVI.visual->c_class = aVI.c_class;
+ aVI.visual->red_mask = aVI.red_mask;
+ aVI.visual->green_mask = aVI.green_mask;
+ aVI.visual->blue_mask = aVI.blue_mask;
+ aVI.visual->bits_per_rgb = aVI.bits_per_rgb;
+ aVI.visual->map_entries = aVI.colormap_size;
+
+ m_aVisual = SalVisual( &aVI );
+ m_aVisualOwnership.owner = true;
+ }
+ else
+ m_aVisual = SalVisual( &aVI );
+ }
+}
+
+SalColormap::~SalColormap()
+{
+ if (m_aVisualOwnership.owner)
+ {
+ delete m_aVisual.visual;
+ }
+}
+
+void SalColormap::GetPalette()
+{
+ Pixel i;
+ m_aPalette = std::vector<Color>(m_nUsed);
+
+ std::unique_ptr<XColor[]> aColor(new XColor[m_nUsed]);
+
+ for( i = 0; i < m_nUsed; i++ )
+ {
+ aColor[i].red = aColor[i].green = aColor[i].blue = 0;
+ aColor[i].pixel = i;
+ }
+
+ XQueryColors( m_pDisplay->GetDisplay(), m_hColormap, aColor.get(), m_nUsed );
+
+ for( i = 0; i < m_nUsed; i++ )
+ {
+ m_aPalette[i] = Color( aColor[i].red >> 8,
+ aColor[i].green >> 8,
+ aColor[i].blue >> 8 );
+ }
+}
+
+static sal_uInt16 sal_Lookup( const std::vector<Color>& rPalette,
+ int r, int g, int b,
+ Pixel nUsed )
+{
+ sal_uInt16 nPixel = 0;
+ int nBest = ColorDiff( rPalette[0], r, g, b );
+
+ for( Pixel i = 1; i < nUsed; i++ )
+ {
+ int n = ColorDiff( rPalette[i], r, g, b );
+
+ if( n < nBest )
+ {
+ if( !n )
+ return i;
+
+ nPixel = i;
+ nBest = n;
+ }
+ }
+ return nPixel;
+}
+
+void SalColormap::GetLookupTable()
+{
+ m_aLookupTable = std::vector<sal_uInt16>(16*16*16);
+
+ int i = 0;
+ for( int r = 0; r < 256; r += 17 )
+ for( int g = 0; g < 256; g += 17 )
+ for( int b = 0; b < 256; b += 17 )
+ m_aLookupTable[i++] = sal_Lookup( m_aPalette, r, g, b, m_nUsed );
+}
+
+Color SalColormap::GetColor( Pixel nPixel ) const
+{
+ if( m_nBlackPixel == nPixel ) return COL_BLACK;
+ if( m_nWhitePixel == nPixel ) return COL_WHITE;
+
+ if( m_aVisual.GetVisual() )
+ {
+ if( m_aVisual.GetClass() == TrueColor )
+ return m_aVisual.GetTCColor( nPixel );
+
+ if( m_aPalette.empty()
+ && m_hColormap
+ && m_aVisual.GetDepth() <= 12
+ && m_aVisual.GetClass() == PseudoColor )
+ const_cast<SalColormap*>(this)->GetPalette();
+ }
+
+ if( !m_aPalette.empty() && nPixel < m_nUsed )
+ return m_aPalette[nPixel];
+
+ if( !m_hColormap )
+ {
+ SAL_WARN("vcl", "SalColormap::GetColor() !m_hColormap");
+ return Color(ColorTransparency, nPixel);
+ }
+
+ // DirectColor, StaticColor, StaticGray, GrayScale
+ XColor aColor;
+
+ aColor.pixel = nPixel;
+
+ XQueryColor( m_pDisplay->GetDisplay(), m_hColormap, &aColor );
+
+ return Color( aColor.red>>8, aColor.green>>8, aColor.blue>>8 );
+}
+
+inline bool SalColormap::GetXPixel( XColor &rColor,
+ int r,
+ int g,
+ int b ) const
+{
+ rColor.red = r * 257;
+ rColor.green = g * 257;
+ rColor.blue = b * 257;
+ return XAllocColor( GetXDisplay(), m_hColormap, &rColor );
+}
+
+bool SalColormap::GetXPixels( XColor &rColor,
+ int r,
+ int g,
+ int b ) const
+{
+ if( !GetXPixel( rColor, r, g, b ) )
+ return false;
+ if( rColor.pixel & 1 )
+ return true;
+ return GetXPixel( rColor, r^0xFF, g^0xFF, b^0xFF );
+}
+
+Pixel SalColormap::GetPixel( Color nColor ) const
+{
+ if( SALCOLOR_NONE == nColor ) return 0;
+ if( COL_BLACK == nColor ) return m_nBlackPixel;
+ if( COL_WHITE == nColor ) return m_nWhitePixel;
+
+ if( m_aVisual.GetClass() == TrueColor )
+ return m_aVisual.GetTCPixel( nColor );
+
+ if( m_aLookupTable.empty() )
+ {
+ if( m_aPalette.empty()
+ && m_hColormap
+ && m_aVisual.GetDepth() <= 12
+ && m_aVisual.GetClass() == PseudoColor ) // what else ???
+ const_cast<SalColormap*>(this)->GetPalette();
+
+ if( !m_aPalette.empty() )
+ for( Pixel i = 0; i < m_nUsed; i++ )
+ if( m_aPalette[i] == nColor )
+ return i;
+
+ if( m_hColormap )
+ {
+ // DirectColor, StaticColor, StaticGray, GrayScale (PseudoColor)
+ XColor aColor;
+
+ if( GetXPixel( aColor,
+ nColor.GetRed(),
+ nColor.GetGreen(),
+ nColor.GetBlue() ) )
+ {
+ if( !m_aPalette.empty() && m_aPalette[aColor.pixel] == Color(0) )
+ {
+ const_cast<SalColormap*>(this)->m_aPalette[aColor.pixel] = nColor;
+
+ if( !(aColor.pixel & 1) && m_aPalette[aColor.pixel+1] == Color(0) )
+ {
+ XColor aInversColor;
+
+ Color nInversColor(ColorTransparency, sal_uInt32(nColor) ^ 0xFFFFFF);
+
+ GetXPixel( aInversColor,
+ nInversColor.GetRed(),
+ nInversColor.GetGreen(),
+ nInversColor.GetBlue() );
+
+ if( m_aPalette[aInversColor.pixel] == Color(0) )
+ const_cast<SalColormap*>(this)->m_aPalette[aInversColor.pixel] = nInversColor;
+#ifdef DBG_UTIL
+ else
+ SAL_INFO("vcl.app", "SalColormap::GetPixel() "
+ << std::showbase << std::setfill('0')
+ << std::setw(6) << std::hex
+ << static_cast< unsigned long >(
+ sal_uInt32(nColor))
+ << "="
+ << std::dec
+ << aColor.pixel << " "
+ << std::showbase << std::setfill('0')
+ << std::setw(6) << std::hex
+ << static_cast< unsigned long >(
+ sal_uInt32(nInversColor))
+ << "="
+ << std::dec
+ << aInversColor.pixel);
+#endif
+ }
+ }
+
+ return aColor.pixel;
+ }
+
+#ifdef DBG_UTIL
+ SAL_INFO("vcl.app", "SalColormap::GetPixel() !XAllocColor "
+ << std::hex
+ << static_cast< unsigned long >(sal_uInt32(nColor)));
+#endif
+ }
+
+ if( m_aPalette.empty() )
+ {
+#ifdef DBG_UTIL
+ SAL_INFO("vcl.app", "SalColormap::GetPixel() Palette empty "
+ << std::hex
+ << static_cast< unsigned long >(sal_uInt32(nColor)));
+#endif
+ return sal_uInt32(nColor);
+ }
+
+ const_cast<SalColormap*>(this)->GetLookupTable();
+ }
+
+ // color matching via palette
+ sal_uInt16 r = nColor.GetRed();
+ sal_uInt16 g = nColor.GetGreen();
+ sal_uInt16 b = nColor.GetBlue();
+ return m_aLookupTable[ (((r+8)/17) << 8)
+ + (((g+8)/17) << 4)
+ + ((b+8)/17) ];
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/salinst.cxx b/vcl/unx/generic/app/salinst.cxx
new file mode 100644
index 000000000..502f4c7f7
--- /dev/null
+++ b/vcl/unx/generic/app/salinst.cxx
@@ -0,0 +1,254 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <stdlib.h>
+
+#include <config_features.h>
+#include <vcl/skia/SkiaHelper.hxx>
+#include <config_skia.h>
+#if HAVE_FEATURE_SKIA
+#include <skia/x11/gdiimpl.hxx>
+#include <skia/salbmp.hxx>
+#endif
+
+#include <unx/saldata.hxx>
+#include <unx/saldisp.hxx>
+#include <unx/salinst.h>
+#include <unx/geninst.h>
+#include <unx/genpspgraphics.h>
+#include <unx/salframe.h>
+#include <unx/sm.hxx>
+#include <unx/i18n_im.hxx>
+#include <unx/salbmp.h>
+
+#include <vcl/inputtypes.hxx>
+
+#include <salwtype.hxx>
+
+// plugin factory function
+extern "C"
+{
+ VCLPLUG_GEN_PUBLIC SalInstance* create_SalInstance()
+ {
+ /* #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();
+
+ X11SalInstance* pInstance = new X11SalInstance( std::make_unique<SalYieldMutex>() );
+
+ // initialize SalData
+ X11SalData *pSalData = new X11SalData();
+
+ pSalData->Init();
+ pInstance->SetLib( pSalData->GetLib() );
+
+ return pInstance;
+ }
+}
+
+X11SalInstance::X11SalInstance(std::unique_ptr<SalYieldMutex> pMutex)
+ : SalGenericInstance(std::move(pMutex))
+ , mpXLib(nullptr)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mxToolkitName = OUString("x11");
+ m_bSupportsOpenGL = true;
+#if HAVE_FEATURE_SKIA
+ X11SkiaSalGraphicsImpl::prepareSkia();
+#if SKIA_USE_BITMAP32
+ if (SkiaHelper::isVCLSkiaEnabled())
+ m_bSupportsBitmap32 = true;
+#endif
+#endif
+}
+
+X11SalInstance::~X11SalInstance()
+{
+ // close session management
+ SessionManagerClient::close();
+
+ // dispose SalDisplay list from SalData
+ // would be done in a static destructor else which is
+ // a little late
+ GetGenericUnixSalData()->Dispose();
+
+#if HAVE_FEATURE_SKIA
+ SkiaHelper::cleanup();
+#endif
+}
+
+SalX11Display* X11SalInstance::CreateDisplay() const
+{
+ return new SalX11Display( mpXLib->GetDisplay() );
+}
+
+// AnyInput from sv/mow/source/app/svapp.cxx
+
+namespace {
+
+struct PredicateReturn
+{
+ VclInputFlags nType;
+ bool bRet;
+};
+
+}
+
+extern "C" {
+static Bool ImplPredicateEvent( Display *, XEvent *pEvent, char *pData )
+{
+ PredicateReturn *pPre = reinterpret_cast<PredicateReturn *>(pData);
+
+ if ( pPre->bRet )
+ return False;
+
+ VclInputFlags nType;
+
+ switch( pEvent->type )
+ {
+ case ButtonPress:
+ case ButtonRelease:
+ case MotionNotify:
+ case EnterNotify:
+ case LeaveNotify:
+ nType = VclInputFlags::MOUSE;
+ break;
+
+ case KeyPress:
+ //case KeyRelease:
+ nType = VclInputFlags::KEYBOARD;
+ break;
+ case Expose:
+ case GraphicsExpose:
+ case NoExpose:
+ nType = VclInputFlags::PAINT;
+ break;
+ default:
+ nType = VclInputFlags::NONE;
+ }
+
+ if ( (nType & pPre->nType) || ( nType == VclInputFlags::NONE && (pPre->nType & VclInputFlags::OTHER) ) )
+ pPre->bRet = true;
+
+ return False;
+}
+}
+
+bool X11SalInstance::AnyInput(VclInputFlags nType)
+{
+ GenericUnixSalData *pData = GetGenericUnixSalData();
+ Display *pDisplay = vcl_sal::getSalDisplay(pData)->GetDisplay();
+ bool bRet = false;
+
+ if( (nType & VclInputFlags::TIMER) && (mpXLib && mpXLib->CheckTimeout(false)) )
+ bRet = true;
+
+ if( !bRet && XPending(pDisplay) )
+ {
+ PredicateReturn aInput;
+ XEvent aEvent;
+
+ aInput.bRet = false;
+ aInput.nType = nType;
+
+ XCheckIfEvent(pDisplay, &aEvent, ImplPredicateEvent,
+ reinterpret_cast<char *>(&aInput) );
+
+ bRet = aInput.bRet;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "AnyInput "
+ << std::showbase << std::hex
+ << static_cast<unsigned int>(nType)
+ << " = " << (bRet ? "true" : "false"));
+#endif
+ return bRet;
+}
+
+bool X11SalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
+{
+ return mpXLib->Yield( bWait, bHandleAllCurrentEvents );
+}
+
+OUString X11SalInstance::GetConnectionIdentifier()
+{
+ static const char* pDisplay = getenv( "DISPLAY" );
+ return pDisplay ? OUString::createFromAscii(pDisplay) : OUString();
+}
+
+SalFrame *X11SalInstance::CreateFrame( SalFrame *pParent, SalFrameStyleFlags nSalFrameStyle )
+{
+ SalFrame *pFrame = new X11SalFrame( pParent, nSalFrameStyle );
+
+ return pFrame;
+}
+
+SalFrame* X11SalInstance::CreateChildFrame( SystemParentData* pParentData, SalFrameStyleFlags nStyle )
+{
+ SalFrame* pFrame = new X11SalFrame( nullptr, nStyle, pParentData );
+
+ return pFrame;
+}
+
+void X11SalInstance::DestroyFrame( SalFrame* pFrame )
+{
+ delete pFrame;
+}
+
+void X11SalInstance::AfterAppInit()
+{
+ assert( mpXLib->GetDisplay() );
+ assert( mpXLib->GetInputMethod() );
+
+ SalX11Display *pSalDisplay = CreateDisplay();
+ mpXLib->GetInputMethod()->CreateMethod( mpXLib->GetDisplay() );
+ pSalDisplay->SetupInput();
+}
+
+void X11SalInstance::AddToRecentDocumentList(const OUString&, const OUString&, const OUString&) {}
+
+void X11SalInstance::PostPrintersChanged()
+{
+ SalDisplay* pDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ for (auto pSalFrame : pDisp->getFrames() )
+ pDisp->PostEvent( pSalFrame, nullptr, SalEvent::PrinterChanged );
+}
+
+std::unique_ptr<GenPspGraphics> X11SalInstance::CreatePrintGraphics()
+{
+ return std::make_unique<GenPspGraphics>();
+}
+
+std::shared_ptr<SalBitmap> X11SalInstance::CreateSalBitmap()
+{
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled())
+ return std::make_shared<SkiaSalBitmap>();
+ else
+#endif
+ return std::make_shared<X11SalBitmap>();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/saltimer.cxx b/vcl/unx/generic/app/saltimer.cxx
new file mode 100644
index 000000000..dc7a61dfe
--- /dev/null
+++ b/vcl/unx/generic/app/saltimer.cxx
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sys/time.h>
+
+#include <unx/salunxtime.h>
+#include <unx/saldisp.hxx>
+#include <unx/saltimer.h>
+#include <unx/salinst.h>
+
+void SalXLib::StopTimer()
+{
+ m_aTimeout.tv_sec = 0;
+ m_aTimeout.tv_usec = 0;
+ m_nTimeoutMS = 0;
+}
+
+void SalXLib::StartTimer( sal_uInt64 nMS )
+{
+ timeval Timeout (m_aTimeout); // previous timeout.
+ gettimeofday (&m_aTimeout, nullptr);
+
+ m_nTimeoutMS = nMS;
+ m_aTimeout += m_nTimeoutMS;
+
+ if ((Timeout > m_aTimeout) || (Timeout.tv_sec == 0))
+ {
+ // Wakeup from previous timeout (or stopped timer).
+ Wakeup();
+ }
+}
+
+SalTimer* X11SalInstance::CreateSalTimer()
+{
+ return new X11SalTimer( mpXLib );
+}
+
+X11SalTimer::~X11SalTimer()
+{
+}
+
+void X11SalTimer::Stop()
+{
+ mpXLib->StopTimer();
+}
+
+void X11SalTimer::Start( sal_uInt64 nMS )
+{
+ mpXLib->StartTimer( nMS );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/sm.cxx b/vcl/unx/generic/app/sm.cxx
new file mode 100644
index 000000000..b6a4c4ad4
--- /dev/null
+++ b/vcl/unx/generic/app/sm.cxx
@@ -0,0 +1,856 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <sal/config.h>
+
+#include <cassert>
+
+#include <string.h>
+#include <unistd.h>
+#include <poll.h>
+#include <fcntl.h>
+
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+
+#include <rtl/process.h>
+#include <osl/security.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include <unx/sm.hxx>
+#include <unx/saldisp.hxx>
+#include <unx/salinst.h>
+
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+
+#include <salframe.hxx>
+#include <salsession.hxx>
+
+namespace {
+
+class IceSalSession : public SalSession
+{
+public:
+ IceSalSession() {}
+
+private:
+ virtual ~IceSalSession() override {}
+
+ virtual void queryInteraction() override;
+ virtual void interactionDone() override;
+ virtual void saveDone() override;
+ virtual bool cancelShutdown() override;
+};
+
+}
+
+std::unique_ptr<SalSession> X11SalInstance::CreateSalSession()
+{
+ SAL_INFO("vcl.sm", "X11SalInstance::CreateSalSession");
+
+ std::unique_ptr<SalSession> p(new IceSalSession);
+ SessionManagerClient::open(p.get());
+ return p;
+}
+
+void IceSalSession::queryInteraction()
+{
+ SAL_INFO("vcl.sm", "IceSalSession::queryInteraction");
+
+ if( ! SessionManagerClient::queryInteraction() )
+ {
+ SAL_INFO("vcl.sm.debug", " call SalSessionInteractionEvent");
+ SalSessionInteractionEvent aEvent( false );
+ CallCallback( &aEvent );
+ }
+}
+
+void IceSalSession::interactionDone()
+{
+ SAL_INFO("vcl.sm", "IceSalSession::interactionDone");
+
+ SessionManagerClient::interactionDone( false );
+}
+
+void IceSalSession::saveDone()
+{
+ SAL_INFO("vcl.sm", "IceSalSession::saveDone");
+
+ SessionManagerClient::saveDone();
+}
+
+bool IceSalSession::cancelShutdown()
+{
+ SAL_INFO("vcl.sm", "IceSalSession::cancelShutdown");
+
+ SessionManagerClient::interactionDone( true );
+ return false;
+}
+
+extern "C" {
+
+static void ICEWatchProc(
+ IceConn ice_conn, IcePointer client_data, Bool opening,
+ IcePointer * watch_data);
+
+static void ICEConnectionWorker(void * data);
+
+}
+
+class ICEConnectionObserver
+{
+ friend void ICEWatchProc(IceConn, IcePointer, Bool, IcePointer *);
+
+ friend void ICEConnectionWorker(void *);
+
+ struct pollfd* m_pFilehandles;
+ int m_nConnections;
+ IceConn* m_pConnections;
+ int m_nWakeupFiles[2];
+ oslThread m_ICEThread;
+ IceIOErrorHandler m_origIOErrorHandler;
+ IceErrorHandler m_origErrorHandler;
+
+ void wakeup();
+
+public:
+ osl::Mutex m_ICEMutex;
+
+ ICEConnectionObserver()
+ : m_pFilehandles(nullptr)
+ , m_nConnections(0)
+ , m_pConnections(nullptr)
+ , m_ICEThread(nullptr)
+ , m_origIOErrorHandler(nullptr)
+ , m_origErrorHandler(nullptr)
+ {
+ SAL_INFO("vcl.sm", "ICEConnectionObserver::ICEConnectionObserver");
+
+ m_nWakeupFiles[0] = m_nWakeupFiles[1] = 0;
+ }
+
+ void activate();
+ void deactivate();
+ void terminate(oslThread iceThread);
+};
+
+SalSession * SessionManagerClient::m_pSession = nullptr;
+std::unique_ptr< ICEConnectionObserver >
+SessionManagerClient::m_xICEConnectionObserver;
+SmcConn SessionManagerClient::m_pSmcConnection = nullptr;
+OString SessionManagerClient::m_aClientID = "";
+OString SessionManagerClient::m_aTimeID = "";
+OString SessionManagerClient::m_aClientTimeID = "";
+bool SessionManagerClient::m_bDocSaveDone = false; // HACK
+
+extern "C" {
+
+static void IgnoreIceErrors(
+ SAL_UNUSED_PARAMETER IceConn, SAL_UNUSED_PARAMETER Bool,
+ SAL_UNUSED_PARAMETER int, SAL_UNUSED_PARAMETER unsigned long,
+ SAL_UNUSED_PARAMETER int, SAL_UNUSED_PARAMETER int,
+ SAL_UNUSED_PARAMETER IcePointer)
+{}
+
+static void IgnoreIceIOErrors(SAL_UNUSED_PARAMETER IceConn) {}
+
+}
+
+static SmProp* pSmProps = nullptr;
+static SmProp** ppSmProps = nullptr;
+static char ** ppSmDel = nullptr;
+
+static int nSmProps = 0;
+static int nSmDel = 0;
+static unsigned char *pSmRestartHint = nullptr;
+
+
+enum { eCloneCommand, eProgram, eRestartCommand, eUserId, eRestartStyleHint };
+enum { eDiscardCommand };
+
+
+static void BuildSmPropertyList()
+{
+ SAL_INFO("vcl.sm", "BuildSmPropertyList");
+
+ if( ! pSmProps )
+ {
+ nSmProps = 5;
+ nSmDel = 1;
+ pSmProps = new SmProp[ nSmProps ];
+ ppSmProps = new SmProp*[ nSmProps ];
+ ppSmDel = new char*[ nSmDel ];
+ }
+
+ OString aExec(OUStringToOString(SessionManagerClient::getExecName(), osl_getThreadTextEncoding()));
+
+ pSmProps[ eCloneCommand ].name = const_cast<char*>(SmCloneCommand);
+ pSmProps[ eCloneCommand ].type = const_cast<char*>(SmLISTofARRAY8);
+ pSmProps[ eCloneCommand ].num_vals = 1;
+ pSmProps[ eCloneCommand ].vals = new SmPropValue;
+ pSmProps[ eCloneCommand ].vals->length = aExec.getLength()+1;
+ pSmProps[ eCloneCommand ].vals->value = strdup( aExec.getStr() );
+
+ pSmProps[ eProgram ].name = const_cast<char*>(SmProgram);
+ pSmProps[ eProgram ].type = const_cast<char*>(SmARRAY8);
+ pSmProps[ eProgram ].num_vals = 1;
+ pSmProps[ eProgram ].vals = new SmPropValue;
+ pSmProps[ eProgram ].vals->length = aExec.getLength()+1;
+ pSmProps[ eProgram ].vals->value = strdup( aExec.getStr() );
+
+ pSmProps[ eRestartCommand ].name = const_cast<char*>(SmRestartCommand);
+ pSmProps[ eRestartCommand ].type = const_cast<char*>(SmLISTofARRAY8);
+ pSmProps[ eRestartCommand ].num_vals = 3;
+ pSmProps[ eRestartCommand ].vals = new SmPropValue[3];
+ pSmProps[ eRestartCommand ].vals[0].length = aExec.getLength()+1;
+ pSmProps[ eRestartCommand ].vals[0].value = strdup( aExec.getStr() );
+ OString aRestartOption = "--session=" + SessionManagerClient::getSessionID();
+ pSmProps[ eRestartCommand ].vals[1].length = aRestartOption.getLength()+1;
+ pSmProps[ eRestartCommand ].vals[1].value = strdup(aRestartOption.getStr());
+ OString aRestartOptionNoLogo("--nologo");
+ pSmProps[ eRestartCommand ].vals[2].length = aRestartOptionNoLogo.getLength()+1;
+ pSmProps[ eRestartCommand ].vals[2].value = strdup(aRestartOptionNoLogo.getStr());
+
+ OUString aUserName;
+ OString aUser;
+ oslSecurity aSec = osl_getCurrentSecurity();
+ if( aSec )
+ {
+ osl_getUserName( aSec, &aUserName.pData );
+ aUser = OUStringToOString( aUserName, osl_getThreadTextEncoding() );
+ osl_freeSecurityHandle( aSec );
+ }
+
+ pSmProps[ eUserId ].name = const_cast<char*>(SmUserID);
+ pSmProps[ eUserId ].type = const_cast<char*>(SmARRAY8);
+ pSmProps[ eUserId ].num_vals = 1;
+ pSmProps[ eUserId ].vals = new SmPropValue;
+ pSmProps[ eUserId ].vals->value = strdup( aUser.getStr() );
+ pSmProps[ eUserId ].vals->length = rtl_str_getLength( static_cast<char *>(pSmProps[ 3 ].vals->value) )+1;
+
+ pSmProps[ eRestartStyleHint ].name = const_cast<char*>(SmRestartStyleHint);
+ pSmProps[ eRestartStyleHint ].type = const_cast<char*>(SmCARD8);
+ pSmProps[ eRestartStyleHint ].num_vals = 1;
+ pSmProps[ eRestartStyleHint ].vals = new SmPropValue;
+ pSmProps[ eRestartStyleHint ].vals->value = malloc(1);
+ pSmRestartHint = static_cast<unsigned char *>(pSmProps[ 4 ].vals->value);
+ *pSmRestartHint = SmRestartIfRunning;
+ pSmProps[ eRestartStyleHint ].vals->length = 1;
+
+ for( int i = 0; i < nSmProps; i++ )
+ ppSmProps[ i ] = &pSmProps[i];
+
+ ppSmDel[eDiscardCommand] = const_cast<char*>(SmDiscardCommand);
+}
+
+bool SessionManagerClient::checkDocumentsSaved()
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::checkDocumentsSaved");
+
+ SAL_INFO("vcl.sm.debug", " m_bcheckDocumentsSaved = " << (m_bDocSaveDone ? "true" : "false" ));
+ return m_bDocSaveDone;
+}
+
+IMPL_STATIC_LINK( SessionManagerClient, SaveYourselfHdl, void*, pStateVal, void )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient, SaveYourselfHdl");
+
+ // Decode argument smuggled in as void*:
+ sal_uIntPtr nStateVal = reinterpret_cast< sal_uIntPtr >(pStateVal);
+ bool shutdown = nStateVal != 0;
+
+ static bool bFirstShutdown=true;
+
+ SAL_INFO("vcl.sm.debug", " shutdown = " << (shutdown ? "true" : "false" ) <<
+ ", bFirstShutdown = " << (bFirstShutdown ? "true" : "false" ));
+ if (shutdown && bFirstShutdown) //first shutdown request
+ {
+ bFirstShutdown = false;
+ /*
+ If we have no actual frames open, e.g. we launched a quickstarter,
+ and then shutdown all our frames leaving just a quickstarter running,
+ then we don't want to launch an empty toplevel frame on the next
+ start. (The job of scheduling the restart of the quick-starter is a
+ task of the quick-starter)
+ */
+ *pSmRestartHint = SmRestartNever;
+ for (auto pSalFrame : vcl_sal::getSalDisplay(GetGenericUnixSalData())->getFrames() )
+ {
+ vcl::Window *pWindow = pSalFrame->GetWindow();
+ if (pWindow && pWindow->IsVisible())
+ {
+ *pSmRestartHint = SmRestartIfRunning;
+ SAL_INFO("vcl.sm.debug", " pSmRestartHint = SmRestartIfRunning");
+ break;
+ }
+ }
+ }
+
+ if( m_pSession )
+ {
+ SalSessionSaveRequestEvent aEvent( shutdown );
+ m_pSession->CallCallback( &aEvent );
+ }
+ else
+ saveDone();
+}
+
+IMPL_STATIC_LINK_NOARG( SessionManagerClient, InteractionHdl, void*, void )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient, InteractionHdl");
+
+ if( m_pSession )
+ {
+ SalSessionInteractionEvent aEvent( true );
+ m_pSession->CallCallback( &aEvent );
+ }
+}
+
+IMPL_STATIC_LINK_NOARG( SessionManagerClient, ShutDownCancelHdl, void*, void )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient, ShutDownCancelHdl");
+
+ if( m_pSession )
+ {
+ SalSessionShutdownCancelEvent aEvent;
+ m_pSession->CallCallback( &aEvent );
+ }
+}
+
+void SessionManagerClient::SaveYourselfProc(
+ SmcConn,
+ SmPointer,
+ int save_type,
+ Bool shutdown,
+ int interact_style,
+ Bool
+ )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::SaveYourselfProc");
+
+ TimeValue now;
+ osl_getSystemTime(&now);
+
+ SAL_INFO("vcl.sm", " save_type = " << ((save_type == SmSaveLocal ) ? "local" :
+ (save_type == SmSaveGlobal) ? "global" : "both") <<
+ ", shutdown = " << (shutdown ? "true" : "false" ) <<
+ ", interact_style = " << ((interact_style == SmInteractStyleNone) ? "SmInteractStyleNone" :
+ (interact_style == SmInteractStyleErrors) ? "SmInteractStyleErrors" :
+ "SmInteractStyleAny"));
+ char num[100];
+ snprintf(num, sizeof(num), "_%" SAL_PRIuUINT32 "_%" SAL_PRIuUINT32, now.Seconds, (now.Nanosec / 1001));
+ m_aTimeID = OString(num);
+
+ BuildSmPropertyList();
+
+ SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eProgram ] );
+ SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eUserId ] );
+
+
+ m_bDocSaveDone = false;
+ /* #i49875# some session managers send a "die" message if the
+ * saveDone does not come early enough for their convenience
+ * this can occasionally happen on startup, especially the first
+ * startup. So shortcut the "not shutting down" case since the
+ * upper layers are currently not interested in that event anyway.
+ */
+ if( ! shutdown )
+ {
+ SessionManagerClient::saveDone();
+ return;
+ }
+ // Smuggle argument in as void*:
+ sal_uIntPtr nStateVal = shutdown;
+ Application::PostUserEvent( LINK( nullptr, SessionManagerClient, SaveYourselfHdl ), reinterpret_cast< void * >(nStateVal) );
+}
+
+IMPL_STATIC_LINK_NOARG( SessionManagerClient, ShutDownHdl, void*, void )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient, ShutDownHdl");
+
+ if( m_pSession )
+ {
+ SalSessionQuitEvent aEvent;
+ m_pSession->CallCallback( &aEvent );
+ }
+
+ SalFrame *pAnyFrame = vcl_sal::getSalDisplay(GetGenericUnixSalData())->anyFrame();
+ SAL_INFO("vcl.sm.debug", " rFrames.empty() = " << (pAnyFrame ? "true" : "false"));
+ if( pAnyFrame )
+ pAnyFrame->CallCallback( SalEvent::Shutdown, nullptr );
+}
+
+void SessionManagerClient::DieProc(
+ SmcConn connection,
+ SmPointer
+ )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::DieProc");
+
+ if( connection == m_pSmcConnection )
+ {
+ SAL_INFO("vcl.sm.debug", " connection == m_pSmcConnection" );
+ Application::PostUserEvent( LINK( nullptr, SessionManagerClient, ShutDownHdl ) );
+ }
+}
+
+void SessionManagerClient::SaveCompleteProc(
+ SmcConn,
+ SmPointer
+ )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::SaveCompleteProc");
+}
+
+void SessionManagerClient::ShutdownCanceledProc(
+ SmcConn connection,
+ SmPointer )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::ShutdownCanceledProc" );
+
+ SAL_INFO("vcl.sm.debug", " connection == m_pSmcConnection = " << (( connection == m_pSmcConnection ) ? "true" : "false"));
+ if( connection == m_pSmcConnection )
+ Application::PostUserEvent( LINK( nullptr, SessionManagerClient, ShutDownCancelHdl ) );
+}
+
+void SessionManagerClient::InteractProc(
+ SmcConn connection,
+ SmPointer )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::InteractProc" );
+
+ SAL_INFO("vcl.sm.debug", " connection == m_pSmcConnection = " << (( connection == m_pSmcConnection ) ? "true" : "false"));
+ if( connection == m_pSmcConnection )
+ Application::PostUserEvent( LINK( nullptr, SessionManagerClient, InteractionHdl ) );
+}
+
+void SessionManagerClient::saveDone()
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::saveDone");
+
+ if( !m_pSmcConnection )
+ return;
+
+ assert(m_xICEConnectionObserver);
+ osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex);
+ //SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eCloneCommand ] );
+ // this message-handling is now equal to kate and plasma desktop
+ SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eRestartCommand ] );
+ SmcDeleteProperties( m_pSmcConnection, 1, &ppSmDel[ eDiscardCommand ] );
+ SmcSetProperties( m_pSmcConnection, 1, &ppSmProps[ eRestartStyleHint ] );
+
+ SmcSaveYourselfDone( m_pSmcConnection, True );
+ SAL_INFO("vcl.sm.debug", " sent SmRestartHint = " << (*pSmRestartHint) );
+ m_bDocSaveDone = true;
+}
+
+void SessionManagerClient::open(SalSession * pSession)
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::open");
+
+ assert(!m_pSession && !m_xICEConnectionObserver && !m_pSmcConnection);
+ // must only be called once
+ m_pSession = pSession;
+ // This is the way Xt does it, so we can too:
+ if( getenv( "SESSION_MANAGER" ) )
+ {
+ SAL_INFO("vcl.sm.debug", " getenv( SESSION_MANAGER ) = true");
+ m_xICEConnectionObserver.reset(new ICEConnectionObserver);
+ m_xICEConnectionObserver->activate();
+
+ {
+ osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex);
+
+ static SmcCallbacks aCallbacks; // does this need to be static?
+ aCallbacks.save_yourself.callback = SaveYourselfProc;
+ aCallbacks.save_yourself.client_data = nullptr;
+ aCallbacks.die.callback = DieProc;
+ aCallbacks.die.client_data = nullptr;
+ aCallbacks.save_complete.callback = SaveCompleteProc;
+ aCallbacks.save_complete.client_data = nullptr;
+ aCallbacks.shutdown_cancelled.callback = ShutdownCanceledProc;
+ aCallbacks.shutdown_cancelled.client_data = nullptr;
+ OString aPrevId(getPreviousSessionID());
+ char* pClientID = nullptr;
+ char aErrBuf[1024];
+ m_pSmcConnection = SmcOpenConnection( nullptr,
+ nullptr,
+ SmProtoMajor,
+ SmProtoMinor,
+ SmcSaveYourselfProcMask |
+ SmcDieProcMask |
+ SmcSaveCompleteProcMask |
+ SmcShutdownCancelledProcMask ,
+ &aCallbacks,
+ aPrevId.isEmpty() ? nullptr : const_cast<char*>(aPrevId.getStr()),
+ &pClientID,
+ sizeof( aErrBuf ),
+ aErrBuf );
+ if( !m_pSmcConnection )
+ SAL_INFO("vcl.sm.debug", " SmcOpenConnection failed: " << aErrBuf);
+ else
+ SAL_INFO("vcl.sm.debug", " SmcOpenConnection succeeded, client ID is " << pClientID );
+ m_aClientID = OString(pClientID);
+ free( pClientID );
+ pClientID = nullptr;
+ }
+
+ SalDisplay* pDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ if( pDisp->GetDrawable(pDisp->GetDefaultXScreen()) && !m_aClientID.isEmpty() )
+ {
+ SAL_INFO("vcl.sm.debug", " SmcOpenConnection open: pDisp->GetDrawable = true");
+ XChangeProperty( pDisp->GetDisplay(),
+ pDisp->GetDrawable( pDisp->GetDefaultXScreen() ),
+ XInternAtom( pDisp->GetDisplay(), "SM_CLIENT_ID", False ),
+ XA_STRING,
+ 8,
+ PropModeReplace,
+ reinterpret_cast<unsigned char const *>(m_aClientID.getStr()),
+ m_aClientID.getLength()
+ );
+ }
+ }
+ else
+ {
+ SAL_INFO("vcl.sm.debug", " getenv( SESSION_MANAGER ) = false");
+ }
+}
+
+const OString& SessionManagerClient::getSessionID()
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::getSessionID");
+
+ m_aClientTimeID = m_aClientID + m_aTimeID;
+
+ SAL_INFO("vcl.sm", " SessionID = " << m_aClientTimeID);
+
+ return m_aClientTimeID;
+}
+
+void SessionManagerClient::close()
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::close");
+
+ if( !m_pSmcConnection )
+ return;
+
+ SAL_INFO("vcl.sm.debug", " attempting SmcCloseConnection");
+ assert(m_xICEConnectionObserver);
+ {
+ osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex);
+ SmcCloseConnection( m_pSmcConnection, 0, nullptr );
+ SAL_INFO("vcl.sm", " SmcCloseConnection closed");
+ }
+ m_xICEConnectionObserver->deactivate();
+ m_xICEConnectionObserver.reset();
+ m_pSmcConnection = nullptr;
+}
+
+bool SessionManagerClient::queryInteraction()
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::queryInteraction");
+
+ bool bRet = false;
+ if( m_pSmcConnection )
+ {
+ assert(m_xICEConnectionObserver);
+ osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex);
+ SAL_INFO("vcl.sm.debug", " SmcInteractRequest" );
+ if( SmcInteractRequest( m_pSmcConnection, SmDialogNormal, InteractProc, nullptr ) )
+ bRet = true;
+ }
+ return bRet;
+}
+
+void SessionManagerClient::interactionDone( bool bCancelShutdown )
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::interactionDone");
+
+ if( m_pSmcConnection )
+ {
+ assert(m_xICEConnectionObserver);
+ osl::MutexGuard g(m_xICEConnectionObserver->m_ICEMutex);
+ SAL_INFO("vcl.sm.debug", " SmcInteractDone = " << (bCancelShutdown ? "true" : "false") );
+ SmcInteractDone( m_pSmcConnection, bCancelShutdown ? True : False );
+ }
+}
+
+OUString SessionManagerClient::getExecName()
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::getExecName");
+
+ OUString aExec, aSysExec;
+ osl_getExecutableFile( &aExec.pData );
+ osl_getSystemPathFromFileURL( aExec.pData, &aSysExec.pData );
+
+ if( aSysExec.endsWith(".bin") )
+ aSysExec = aSysExec.copy( 0, aSysExec.getLength() - RTL_CONSTASCII_LENGTH(".bin") );
+
+ SAL_INFO("vcl.sm.debug", " aSysExec = " << aSysExec);
+ return aSysExec;
+}
+
+OString SessionManagerClient::getPreviousSessionID()
+{
+ SAL_INFO("vcl.sm", "SessionManagerClient::getPreviousSessionID");
+
+ OString aPrevId;
+
+ sal_uInt32 n = rtl_getAppCommandArgCount();
+ for (sal_uInt32 i = 0; i != n; ++i)
+ {
+ OUString aArg;
+ rtl_getAppCommandArg( i, &aArg.pData );
+ if(aArg.match("--session="))
+ {
+ aPrevId = OUStringToOString(
+ aArg.subView(RTL_CONSTASCII_LENGTH("--session=")),
+ osl_getThreadTextEncoding());
+ break;
+ }
+ }
+
+ SAL_INFO("vcl.sm.debug", " previous ID = " << aPrevId);
+ return aPrevId;
+}
+
+void ICEConnectionObserver::activate()
+{
+ SAL_INFO("vcl.sm", "ICEConnectionObserver::activate");
+
+ /*
+ * Default handlers call exit, we don't care that strongly if something
+ * happens to fail
+ */
+ m_origIOErrorHandler = IceSetIOErrorHandler( IgnoreIceIOErrors );
+ m_origErrorHandler = IceSetErrorHandler( IgnoreIceErrors );
+ IceAddConnectionWatch( ICEWatchProc, this );
+}
+
+void ICEConnectionObserver::deactivate()
+{
+ SAL_INFO("vcl.sm", "ICEConnectionObserver::deactivate");
+
+ oslThread t;
+ {
+ osl::MutexGuard g(m_ICEMutex);
+ IceRemoveConnectionWatch( ICEWatchProc, this );
+ IceSetErrorHandler( m_origErrorHandler );
+ IceSetIOErrorHandler( m_origIOErrorHandler );
+ m_nConnections = 0;
+ t = m_ICEThread;
+ m_ICEThread = nullptr;
+ }
+ if (t)
+ {
+ SAL_INFO("vcl.sm.debug", " terminate");
+ terminate(t);
+ }
+}
+
+void ICEConnectionObserver::wakeup()
+{
+ SAL_INFO("vcl.sm", "ICEConnectionObserver::wakeup");
+
+ char cChar = 'w';
+ OSL_VERIFY(write(m_nWakeupFiles[1], &cChar, 1) == 1);
+}
+
+void ICEConnectionObserver::terminate(oslThread iceThread)
+{
+ SAL_INFO("vcl.sm", "ICEConnectionObserver::terminate");
+
+ osl_terminateThread(iceThread);
+ wakeup();
+ osl_joinWithThread(iceThread);
+ osl_destroyThread(iceThread);
+ close(m_nWakeupFiles[1]);
+ close(m_nWakeupFiles[0]);
+}
+
+void ICEConnectionWorker(void * data)
+{
+ SAL_INFO("vcl.sm", "ICEConnectionWorker");
+
+ osl::Thread::setName("ICEConnectionWorker");
+ ICEConnectionObserver * pThis = static_cast< ICEConnectionObserver * >(
+ data);
+ for (;;)
+ {
+ oslThread t;
+ {
+ osl::MutexGuard g(pThis->m_ICEMutex);
+ if (pThis->m_ICEThread == nullptr || pThis->m_nConnections == 0)
+ {
+ break;
+ }
+ t = pThis->m_ICEThread;
+ }
+ if (!osl_scheduleThread(t))
+ {
+ break;
+ }
+
+ int nConnectionsBefore;
+ struct pollfd* pLocalFD;
+ {
+ osl::MutexGuard g(pThis->m_ICEMutex);
+ nConnectionsBefore = pThis->m_nConnections;
+ int nBytes = sizeof( struct pollfd )*(nConnectionsBefore+1);
+ pLocalFD = static_cast<struct pollfd*>(std::malloc( nBytes ));
+ memcpy( pLocalFD, pThis->m_pFilehandles, nBytes );
+ }
+
+ int nRet = poll( pLocalFD,nConnectionsBefore+1,-1 );
+ bool bWakeup = (pLocalFD[0].revents & POLLIN);
+ std::free( pLocalFD );
+
+ if( nRet < 1 )
+ continue;
+
+ // clear wakeup pipe
+ if( bWakeup )
+ {
+ char buf[4];
+ while( read( pThis->m_nWakeupFiles[0], buf, sizeof( buf ) ) > 0 )
+ ;
+ SAL_INFO("vcl.sm.debug", " file handles active in wakeup: " << nRet);
+ if( nRet == 1 )
+ continue;
+ }
+
+ // check fd's after we obtained the lock
+ osl::MutexGuard g(pThis->m_ICEMutex);
+ if( pThis->m_nConnections > 0 && pThis->m_nConnections == nConnectionsBefore )
+ {
+ nRet = poll( pThis->m_pFilehandles+1, pThis->m_nConnections, 0 );
+ if( nRet > 0 )
+ {
+ SAL_INFO("vcl.sm.debug", " IceProcessMessages");
+ Bool bReply;
+ for( int i = 0; i < pThis->m_nConnections; i++ )
+ if( pThis->m_pFilehandles[i+1].revents & POLLIN )
+ IceProcessMessages( pThis->m_pConnections[i], nullptr, &bReply );
+ }
+ }
+ }
+
+ SAL_INFO("vcl.sm.debug", " shutting down ICE dispatch thread");
+}
+
+void ICEWatchProc(
+ IceConn ice_conn, IcePointer client_data, Bool opening,
+ SAL_UNUSED_PARAMETER IcePointer *)
+{
+ SAL_INFO("vcl.sm", "ICEWatchProc");
+
+ // Note: This is a callback function for ICE; this implicitly means that a
+ // call into ICE lib is calling this, so the m_ICEMutex MUST already be
+ // locked by the caller.
+ ICEConnectionObserver * pThis = static_cast< ICEConnectionObserver * >(
+ client_data);
+ if( opening )
+ {
+ SAL_INFO("vcl.sm.debug", " opening");
+ int fd = IceConnectionNumber( ice_conn );
+ pThis->m_nConnections++;
+ pThis->m_pConnections = static_cast<IceConn*>(std::realloc( pThis->m_pConnections, sizeof( IceConn )*pThis->m_nConnections ));
+ pThis->m_pFilehandles = static_cast<struct pollfd*>(std::realloc( pThis->m_pFilehandles, sizeof( struct pollfd )*(pThis->m_nConnections+1) ));
+ pThis->m_pConnections[ pThis->m_nConnections-1 ] = ice_conn;
+ pThis->m_pFilehandles[ pThis->m_nConnections ].fd = fd;
+ pThis->m_pFilehandles[ pThis->m_nConnections ].events = POLLIN;
+ if( pThis->m_nConnections == 1 )
+ {
+ SAL_INFO("vcl.sm.debug", " First connection");
+ if (!pipe(pThis->m_nWakeupFiles))
+ {
+ int flags;
+ pThis->m_pFilehandles[0].fd = pThis->m_nWakeupFiles[0];
+ pThis->m_pFilehandles[0].events = POLLIN;
+ // set close-on-exec and nonblock descriptor flag.
+ if ((flags = fcntl(pThis->m_nWakeupFiles[0], F_GETFD)) != -1)
+ {
+ flags |= FD_CLOEXEC;
+ (void)fcntl(pThis->m_nWakeupFiles[0], F_SETFD, flags);
+ }
+ if ((flags = fcntl(pThis->m_nWakeupFiles[0], F_GETFL)) != -1)
+ {
+ flags |= O_NONBLOCK;
+ (void)fcntl(pThis->m_nWakeupFiles[0], F_SETFL, flags);
+ }
+ // set close-on-exec and nonblock descriptor flag.
+ if ((flags = fcntl(pThis->m_nWakeupFiles[1], F_GETFD)) != -1)
+ {
+ flags |= FD_CLOEXEC;
+ (void)fcntl(pThis->m_nWakeupFiles[1], F_SETFD, flags);
+ }
+ if ((flags = fcntl(pThis->m_nWakeupFiles[1], F_GETFL)) != -1)
+ {
+ flags |= O_NONBLOCK;
+ (void)fcntl(pThis->m_nWakeupFiles[1], F_SETFL, flags);
+ }
+ pThis->m_ICEThread = osl_createThread(
+ ICEConnectionWorker, pThis);
+ }
+ }
+ }
+ else // closing
+ {
+ SAL_INFO("vcl.sm.debug", " closing");
+ for( int i = 0; i < pThis->m_nConnections; i++ )
+ {
+ if( pThis->m_pConnections[i] == ice_conn )
+ {
+ if( i < pThis->m_nConnections-1 )
+ {
+ memmove( pThis->m_pConnections+i, pThis->m_pConnections+i+1, sizeof( IceConn )*(pThis->m_nConnections-i-1) );
+ memmove( pThis->m_pFilehandles+i+1, pThis->m_pFilehandles+i+2, sizeof( struct pollfd )*(pThis->m_nConnections-i-1) );
+ }
+ pThis->m_nConnections--;
+ pThis->m_pConnections = static_cast<IceConn*>(std::realloc( pThis->m_pConnections, sizeof( IceConn )*pThis->m_nConnections ));
+ pThis->m_pFilehandles = static_cast<struct pollfd*>(std::realloc( pThis->m_pFilehandles, sizeof( struct pollfd )*(pThis->m_nConnections+1) ));
+ break;
+ }
+ }
+ if( pThis->m_nConnections == 0 && pThis->m_ICEThread )
+ {
+ SAL_INFO("vcl.sm.debug", " terminating ICEThread");
+ oslThread t = pThis->m_ICEThread;
+ pThis->m_ICEThread = nullptr;
+
+ // must release the mutex here
+ pThis->m_ICEMutex.release();
+
+ pThis->terminate(t);
+
+ // acquire the mutex again, because the caller does not expect
+ // it to be released when calling into SM
+ pThis->m_ICEMutex.acquire();
+ }
+ }
+
+ SAL_INFO( "vcl.sm.debug", " ICE connection on " << IceConnectionNumber( ice_conn ) );
+ SAL_INFO( "vcl.sm.debug", " Display connection is " << ConnectionNumber( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay() ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/app/wmadaptor.cxx b/vcl/unx/generic/app/wmadaptor.cxx
new file mode 100644
index 000000000..37384c7b5
--- /dev/null
+++ b/vcl/unx/generic/app/wmadaptor.cxx
@@ -0,0 +1,2215 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string_view>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <rtl/locale.h>
+
+#include <osl/thread.h>
+#include <osl/process.h>
+#include <sal/macros.h>
+#include <sal/log.hxx>
+#include <comphelper/string.hxx>
+#include <configsettings.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <unx/wmadaptor.hxx>
+#include <unx/saldisp.hxx>
+#include <unx/salframe.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+namespace vcl_sal {
+
+class NetWMAdaptor : public WMAdaptor
+{
+ void setNetWMState( X11SalFrame* pFrame ) const;
+ void initAtoms();
+ virtual bool isValid() const override;
+public:
+ explicit NetWMAdaptor( SalDisplay* );
+
+ virtual void setWMName( X11SalFrame* pFrame, const OUString& rWMName ) const override;
+ virtual void maximizeFrame( X11SalFrame* pFrame, bool bHorizontal = true, bool bVertical = true ) const override;
+ virtual void setFrameTypeAndDecoration( X11SalFrame* pFrame, WMWindowType eType, int nDecorationFlags, X11SalFrame* pTransientFrame ) const override;
+ virtual void enableAlwaysOnTop( X11SalFrame* pFrame, bool bEnable ) const override;
+ virtual int handlePropertyNotify( X11SalFrame* pFrame, XPropertyEvent* pEvent ) const override;
+ virtual void showFullScreen( X11SalFrame* pFrame, bool bFullScreen ) const override;
+ virtual void frameIsMapping( X11SalFrame* pFrame ) const override;
+ virtual void setUserTime( X11SalFrame* i_pFrame, tools::Long i_nUserTime ) const override;
+};
+
+class GnomeWMAdaptor : public WMAdaptor
+{
+ bool m_bValid;
+
+ void setGnomeWMState( X11SalFrame* pFrame ) const;
+ void initAtoms();
+ virtual bool isValid() const override;
+public:
+ explicit GnomeWMAdaptor( SalDisplay * );
+
+ virtual void maximizeFrame( X11SalFrame* pFrame, bool bHorizontal = true, bool bVertical = true ) const override;
+ virtual void enableAlwaysOnTop( X11SalFrame* pFrame, bool bEnable ) const override;
+ virtual int handlePropertyNotify( X11SalFrame* pFrame, XPropertyEvent* pEvent ) const override;
+};
+
+}
+
+using namespace vcl_sal;
+
+namespace {
+
+struct WMAdaptorProtocol
+{
+ const char* pProtocol;
+ int nProtocol;
+};
+
+}
+
+/*
+ * table must be sorted ascending in strings
+ * since it is use with bsearch
+ */
+const WMAdaptorProtocol aProtocolTab[] =
+{
+ { "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE", WMAdaptor::KDE_NET_WM_WINDOW_TYPE_OVERRIDE },
+ { "_NET_ACTIVE_WINDOW", WMAdaptor::NET_ACTIVE_WINDOW },
+ { "_NET_CURRENT_DESKTOP", WMAdaptor::NET_CURRENT_DESKTOP },
+ { "_NET_NUMBER_OF_DESKTOPS", WMAdaptor::NET_NUMBER_OF_DESKTOPS },
+ { "_NET_WM_DESKTOP", WMAdaptor::NET_WM_DESKTOP },
+ { "_NET_WM_ICON", WMAdaptor::NET_WM_ICON },
+ { "_NET_WM_ICON_NAME", WMAdaptor::NET_WM_ICON_NAME },
+ { "_NET_WM_PING", WMAdaptor::NET_WM_PING },
+ { "_NET_WM_STATE", WMAdaptor::NET_WM_STATE },
+ { "_NET_WM_STATE_ABOVE", WMAdaptor::NET_WM_STATE_STAYS_ON_TOP },
+ { "_NET_WM_STATE_FULLSCREEN", WMAdaptor::NET_WM_STATE_FULLSCREEN },
+ { "_NET_WM_STATE_MAXIMIZED_HORIZ", WMAdaptor::NET_WM_STATE_MAXIMIZED_HORZ }, // common bug in e.g. older kwin and sawfish implementations
+ { "_NET_WM_STATE_MAXIMIZED_HORZ", WMAdaptor::NET_WM_STATE_MAXIMIZED_HORZ },
+ { "_NET_WM_STATE_MAXIMIZED_VERT", WMAdaptor::NET_WM_STATE_MAXIMIZED_VERT },
+ { "_NET_WM_STATE_MODAL", WMAdaptor::NET_WM_STATE_MODAL },
+ { "_NET_WM_STATE_SKIP_PAGER", WMAdaptor::NET_WM_STATE_SKIP_PAGER },
+ { "_NET_WM_STATE_SKIP_TASKBAR", WMAdaptor::NET_WM_STATE_SKIP_TASKBAR },
+ { "_NET_WM_STATE_STAYS_ON_TOP", WMAdaptor::NET_WM_STATE_STAYS_ON_TOP },
+ { "_NET_WM_STATE_STICKY", WMAdaptor::NET_WM_STATE_STICKY },
+ { "_NET_WM_STRUT", WMAdaptor::NET_WM_STRUT },
+ { "_NET_WM_STRUT_PARTIAL", WMAdaptor::NET_WM_STRUT_PARTIAL },
+ { "_NET_WM_WINDOW_TYPE", WMAdaptor::NET_WM_WINDOW_TYPE },
+ { "_NET_WM_WINDOW_TYPE_DESKTOP", WMAdaptor::NET_WM_WINDOW_TYPE_DESKTOP },
+ { "_NET_WM_WINDOW_TYPE_DIALOG", WMAdaptor::NET_WM_WINDOW_TYPE_DIALOG },
+ { "_NET_WM_WINDOW_TYPE_DOCK", WMAdaptor::NET_WM_WINDOW_TYPE_DOCK },
+ { "_NET_WM_WINDOW_TYPE_MENU", WMAdaptor::NET_WM_WINDOW_TYPE_MENU },
+ { "_NET_WM_WINDOW_TYPE_NORMAL", WMAdaptor::NET_WM_WINDOW_TYPE_NORMAL },
+ { "_NET_WM_WINDOW_TYPE_SPLASH", WMAdaptor::NET_WM_WINDOW_TYPE_SPLASH },
+ { "_NET_WM_WINDOW_TYPE_SPLASHSCREEN", WMAdaptor::NET_WM_WINDOW_TYPE_SPLASH }, // bug in Metacity 2.4.1
+ { "_NET_WM_WINDOW_TYPE_TOOLBAR", WMAdaptor::NET_WM_WINDOW_TYPE_TOOLBAR },
+ { "_NET_WM_WINDOW_TYPE_UTILITY", WMAdaptor::NET_WM_WINDOW_TYPE_UTILITY },
+ { "_NET_WORKAREA", WMAdaptor::NET_WORKAREA },
+ { "_WIN_APP_STATE", WMAdaptor::WIN_APP_STATE },
+ { "_WIN_CLIENT_LIST", WMAdaptor::WIN_CLIENT_LIST },
+ { "_WIN_EXPANDED_SIZE", WMAdaptor::WIN_EXPANDED_SIZE },
+ { "_WIN_HINTS", WMAdaptor::WIN_HINTS },
+ { "_WIN_ICONS", WMAdaptor::WIN_ICONS },
+ { "_WIN_LAYER", WMAdaptor::WIN_LAYER },
+ { "_WIN_STATE", WMAdaptor::WIN_STATE },
+ { "_WIN_WORKSPACE", WMAdaptor::WIN_WORKSPACE },
+ { "_WIN_WORKSPACE_COUNT", WMAdaptor::WIN_WORKSPACE_COUNT }
+};
+
+/*
+ * table containing atoms to get anyway
+ */
+
+const WMAdaptorProtocol aAtomTab[] =
+{
+ { "WM_STATE", WMAdaptor::WM_STATE },
+ { "_MOTIF_WM_HINTS", WMAdaptor::MOTIF_WM_HINTS },
+ { "WM_PROTOCOLS", WMAdaptor::WM_PROTOCOLS },
+ { "WM_DELETE_WINDOW", WMAdaptor::WM_DELETE_WINDOW },
+ { "WM_TAKE_FOCUS", WMAdaptor::WM_TAKE_FOCUS },
+ { "WM_COMMAND", WMAdaptor::WM_COMMAND },
+ { "WM_CLIENT_LEADER", WMAdaptor::WM_CLIENT_LEADER },
+ { "WM_LOCALE_NAME", WMAdaptor::WM_LOCALE_NAME },
+ { "WM_TRANSIENT_FOR", WMAdaptor::WM_TRANSIENT_FOR },
+ { "SAL_QUITEVENT", WMAdaptor::SAL_QUITEVENT },
+ { "SAL_USEREVENT", WMAdaptor::SAL_USEREVENT },
+ { "SAL_EXTTEXTEVENT", WMAdaptor::SAL_EXTTEXTEVENT },
+ { "SAL_GETTIMEEVENT", WMAdaptor::SAL_GETTIMEEVENT },
+ { "VCL_SYSTEM_SETTINGS", WMAdaptor::VCL_SYSTEM_SETTINGS },
+ { "_XSETTINGS_SETTINGS", WMAdaptor::XSETTINGS },
+ { "_XEMBED", WMAdaptor::XEMBED },
+ { "_XEMBED_INFO", WMAdaptor::XEMBED_INFO },
+ { "_NET_WM_USER_TIME", WMAdaptor::NET_WM_USER_TIME },
+ { "_NET_WM_PID", WMAdaptor::NET_WM_PID }
+};
+
+extern "C" {
+static int compareProtocol( const void* pLeft, const void* pRight )
+{
+ return strcmp( static_cast<const WMAdaptorProtocol*>(pLeft)->pProtocol, static_cast<const WMAdaptorProtocol*>(pRight)->pProtocol );
+}
+}
+
+std::unique_ptr<WMAdaptor> WMAdaptor::createWMAdaptor( SalDisplay* pSalDisplay )
+{
+ std::unique_ptr<WMAdaptor> pAdaptor;
+
+ // try a NetWM
+ pAdaptor.reset(new NetWMAdaptor( pSalDisplay ));
+ if( ! pAdaptor->isValid() )
+ {
+ pAdaptor.reset();
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ SAL_INFO("vcl.app", "WM supports extended WM hints.");
+#endif
+
+ // try a GnomeWM
+ if( ! pAdaptor )
+ {
+ pAdaptor.reset(new GnomeWMAdaptor( pSalDisplay ));
+ if( ! pAdaptor->isValid() )
+ {
+ pAdaptor.reset();
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ SAL_INFO("vcl.app", "WM supports GNOME WM hints.");
+#endif
+ }
+
+ if( ! pAdaptor )
+ pAdaptor.reset(new WMAdaptor( pSalDisplay ));
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "Window Manager's name is \""
+ << pAdaptor->getWindowManagerName()
+ << "\".");
+#endif
+ return pAdaptor;
+}
+
+/*
+ * WMAdaptor constructor
+ */
+
+WMAdaptor::WMAdaptor( SalDisplay* pDisplay ) :
+ m_pSalDisplay( pDisplay ),
+ m_bEnableAlwaysOnTopWorks( false ),
+ m_bLegacyPartialFullscreen( false ),
+ m_nWinGravity( StaticGravity ),
+ m_nInitWinGravity( StaticGravity ),
+ m_bWMshouldSwitchWorkspace( true ),
+ m_bWMshouldSwitchWorkspaceInit( false )
+{
+ Atom aRealType = None;
+ int nFormat = 8;
+ unsigned long nItems = 0;
+ unsigned long nBytesLeft = 0;
+ unsigned char* pProperty = nullptr;
+
+ // default desktops
+ m_nDesktops = 1;
+ m_aWMWorkAreas = ::std::vector< tools::Rectangle >
+ ( 1, tools::Rectangle( Point(), m_pSalDisplay->GetScreenSize( m_pSalDisplay->GetDefaultXScreen() ) ) );
+ m_bEqualWorkAreas = true;
+
+ memset( m_aWMAtoms, 0, sizeof( m_aWMAtoms ) );
+ m_pDisplay = m_pSalDisplay->GetDisplay();
+
+ initAtoms();
+ getNetWmName(); // try to discover e.g. Sawfish
+
+ if( m_aWMName.isEmpty() )
+ {
+ // check for ReflectionX wm (as it needs a workaround in Windows mode
+ Atom aRwmRunning = XInternAtom( m_pDisplay, "RWM_RUNNING", True );
+ if( aRwmRunning != None &&
+ XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ aRwmRunning,
+ 0, 32,
+ False,
+ aRwmRunning,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0 )
+ {
+ if( aRealType == aRwmRunning )
+ m_aWMName = "ReflectionX";
+ XFree( pProperty );
+ }
+ else
+ {
+ aRwmRunning = XInternAtom( m_pDisplay, "_WRQ_WM_RUNNING", True );
+ if( aRwmRunning != None &&
+ XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ aRwmRunning,
+ 0, 32,
+ False,
+ XA_STRING,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0 )
+ {
+ if( aRealType == XA_STRING )
+ m_aWMName = "ReflectionX Windows";
+ XFree( pProperty );
+ }
+ }
+ }
+ if( !m_aWMName.isEmpty() )
+ return;
+
+ Atom aTTAPlatform = XInternAtom( m_pDisplay, "TTA_CLIENT_PLATFORM", True );
+ if( aTTAPlatform == None ||
+ XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ aTTAPlatform,
+ 0, 32,
+ False,
+ XA_STRING,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) != 0 )
+ return;
+
+ if( aRealType == XA_STRING )
+ {
+ m_aWMName = "Tarantella";
+ // #i62319# pretend that AlwaysOnTop works since
+ // the alwaysontop workaround in salframe.cxx results
+ // in a raise/lower loop on a Windows tarantella client
+ // FIXME: this property contains an identification string that
+ // in theory should be good enough to recognize running on a
+ // Windows client; however this string does not seem to be
+ // documented as well as the property itself.
+ m_bEnableAlwaysOnTopWorks = true;
+ }
+ XFree( pProperty );
+}
+
+/*
+ * WMAdaptor destructor
+ */
+
+WMAdaptor::~WMAdaptor()
+{
+}
+
+/*
+ * NetWMAdaptor constructor
+ */
+
+NetWMAdaptor::NetWMAdaptor( SalDisplay* pSalDisplay ) :
+ WMAdaptor( pSalDisplay )
+{
+ // currently all _NET WMs do transient like expected
+
+ Atom aRealType = None;
+ int nFormat = 8;
+ unsigned long nItems = 0;
+ unsigned long nBytesLeft = 0;
+ unsigned char* pProperty = nullptr;
+
+ initAtoms();
+
+ // check for NetWM
+ bool bNetWM = getNetWmName();
+ if( bNetWM
+ && XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ NET_SUPPORTED ],
+ 0, 0,
+ False,
+ XA_ATOM,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && aRealType == XA_ATOM
+ && nFormat == 32
+ )
+ {
+ if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ // collect supported protocols
+ if( XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ NET_SUPPORTED ],
+ 0, nBytesLeft/4,
+ False,
+ XA_ATOM,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && nItems
+ )
+ {
+ Atom* pAtoms = reinterpret_cast<Atom*>(pProperty);
+ char** pAtomNames = static_cast<char**>(alloca( sizeof(char*)*nItems ));
+ if( XGetAtomNames( m_pDisplay, pAtoms, nItems, pAtomNames ) )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "supported protocols:");
+#endif
+ for( unsigned long i = 0; i < nItems; i++ )
+ {
+ // #i80971# protect against invalid atoms
+ if( pAtomNames[i] == nullptr )
+ continue;
+
+ WMAdaptorProtocol aSearch;
+ aSearch.pProtocol = pAtomNames[i];
+ WMAdaptorProtocol* pMatch = static_cast<WMAdaptorProtocol*>(
+ bsearch( &aSearch,
+ aProtocolTab,
+ SAL_N_ELEMENTS( aProtocolTab ),
+ sizeof( struct WMAdaptorProtocol ),
+ compareProtocol ));
+ if( pMatch )
+ {
+ m_aWMAtoms[ pMatch->nProtocol ] = pAtoms[ i ];
+ if( pMatch->nProtocol == NET_WM_STATE_STAYS_ON_TOP )
+ m_bEnableAlwaysOnTopWorks = true;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", " "
+ << pAtomNames[i]
+ << (((pMatch)&&(pMatch->nProtocol != -1)) ?
+ "" : " (unsupported)"));
+#endif
+ XFree( pAtomNames[i] );
+ }
+ }
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+
+ // get number of desktops
+ if( m_aWMAtoms[ NET_NUMBER_OF_DESKTOPS ]
+ && XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ NET_NUMBER_OF_DESKTOPS ],
+ 0, 1,
+ False,
+ XA_CARDINAL,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && pProperty
+ )
+ {
+ m_nDesktops = *reinterpret_cast<long*>(pProperty);
+ XFree( pProperty );
+ pProperty = nullptr;
+ // get work areas
+ if( m_aWMAtoms[ NET_WORKAREA ]
+ && XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ NET_WORKAREA ],
+ 0, 4*m_nDesktops,
+ False,
+ XA_CARDINAL,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty
+ ) == 0
+ && nItems == 4*static_cast<unsigned>(m_nDesktops)
+ )
+ {
+ m_aWMWorkAreas = ::std::vector< tools::Rectangle > ( m_nDesktops );
+ tools::Long* pValues = reinterpret_cast<long*>(pProperty);
+ for( int i = 0; i < m_nDesktops; i++ )
+ {
+ Point aPoint( pValues[4*i],
+ pValues[4*i+1] );
+ Size aSize( pValues[4*i+2],
+ pValues[4*i+3] );
+ tools::Rectangle aWorkArea( aPoint, aSize );
+ m_aWMWorkAreas[i] = aWorkArea;
+ if( aWorkArea != m_aWMWorkAreas[0] )
+ m_bEqualWorkAreas = false;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "workarea " << i
+ << ": " << m_aWMWorkAreas[i].GetWidth()
+ << "x" << m_aWMWorkAreas[i].GetHeight()
+ << "+" << m_aWMWorkAreas[i].Left()
+ << "+" << m_aWMWorkAreas[i].Top());
+#endif
+ }
+ XFree( pProperty );
+ }
+ else
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", nItems/4 << " workareas for "
+ << m_nDesktops << " desktops !");
+#endif
+ if( pProperty )
+ {
+ XFree(pProperty);
+ pProperty = nullptr;
+ }
+ }
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+}
+
+/*
+ * GnomeWMAdaptor constructor
+ */
+
+GnomeWMAdaptor::GnomeWMAdaptor( SalDisplay* pSalDisplay ) :
+ WMAdaptor( pSalDisplay ),
+ m_bValid( false )
+{
+ // currently all Gnome WMs do transient like expected
+
+ Atom aRealType = None;
+ int nFormat = 8;
+ unsigned long nItems = 0;
+ unsigned long nBytesLeft = 0;
+ unsigned char* pProperty = nullptr;
+
+ initAtoms();
+
+ // check for GnomeWM
+ if( m_aWMAtoms[ WIN_SUPPORTING_WM_CHECK ] && m_aWMAtoms[ WIN_PROTOCOLS ] )
+ {
+ if( XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ WIN_SUPPORTING_WM_CHECK ],
+ 0, 1,
+ False,
+ XA_CARDINAL,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && aRealType == XA_CARDINAL
+ && nFormat == 32
+ && nItems != 0
+ )
+ {
+ ::Window aWMChild = *reinterpret_cast< ::Window* >(pProperty);
+ XFree( pProperty );
+ pProperty = nullptr;
+ GetGenericUnixSalData()->ErrorTrapPush();
+ if( XGetWindowProperty( m_pDisplay,
+ aWMChild,
+ m_aWMAtoms[ WIN_SUPPORTING_WM_CHECK ],
+ 0, 1,
+ False,
+ XA_CARDINAL,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && aRealType == XA_CARDINAL
+ && nFormat == 32
+ && nItems != 0 )
+ {
+ if (! GetGenericUnixSalData()->ErrorTrapPop( false ) )
+ {
+ GetGenericUnixSalData()->ErrorTrapPush();
+
+ ::Window aCheckWindow = *reinterpret_cast< ::Window* >(pProperty);
+ XFree( pProperty );
+ pProperty = nullptr;
+ if( aCheckWindow == aWMChild )
+ {
+ m_bValid = true;
+ /*
+ * get name of WM
+ * this is NOT part of the GNOME WM hints, but e.g. Sawfish
+ * already supports this part of the extended WM hints
+ */
+ m_aWMAtoms[ UTF8_STRING ] = XInternAtom( m_pDisplay, "UTF8_STRING", False );
+ getNetWmName();
+ }
+ }
+ else
+ GetGenericUnixSalData()->ErrorTrapPush();
+ }
+ GetGenericUnixSalData()->ErrorTrapPop();
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ }
+ if( m_bValid
+ && XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ WIN_PROTOCOLS ],
+ 0, 0,
+ False,
+ XA_ATOM,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && aRealType == XA_ATOM
+ && nFormat == 32
+ )
+ {
+ if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ // collect supported protocols
+ if( XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ WIN_PROTOCOLS ],
+ 0, nBytesLeft/4,
+ False,
+ XA_ATOM,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && pProperty
+ )
+ {
+ Atom* pAtoms = reinterpret_cast<Atom*>(pProperty);
+ char** pAtomNames = static_cast<char**>(alloca( sizeof(char*)*nItems ));
+ if( XGetAtomNames( m_pDisplay, pAtoms, nItems, pAtomNames ) )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", "supported protocols:");
+#endif
+ for( unsigned long i = 0; i < nItems; i++ )
+ {
+ // #i80971# protect against invalid atoms
+ if( pAtomNames[i] == nullptr )
+ continue;
+
+ WMAdaptorProtocol aSearch;
+ aSearch.pProtocol = pAtomNames[i];
+ WMAdaptorProtocol* pMatch = static_cast<WMAdaptorProtocol*>(
+ bsearch( &aSearch,
+ aProtocolTab,
+ SAL_N_ELEMENTS( aProtocolTab ),
+ sizeof( struct WMAdaptorProtocol ),
+ compareProtocol ));
+ if( pMatch )
+ {
+ m_aWMAtoms[ pMatch->nProtocol ] = pAtoms[ i ];
+ if( pMatch->nProtocol == WIN_LAYER )
+ m_bEnableAlwaysOnTopWorks = true;
+ }
+ if( strncmp( "_ICEWM_TRAY", pAtomNames[i], 11 ) == 0 )
+ {
+ m_aWMName = "IceWM";
+ m_nWinGravity = NorthWestGravity;
+ m_nInitWinGravity = NorthWestGravity;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.app", " "
+ << pAtomNames[i]
+ << (((pMatch) && (pMatch->nProtocol != -1)) ?
+ "" : " (unsupported)"));
+#endif
+ XFree( pAtomNames[i] );
+ }
+ }
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+
+ // get number of desktops
+ if( m_aWMAtoms[ WIN_WORKSPACE_COUNT ]
+ && XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ WIN_WORKSPACE_COUNT ],
+ 0, 1,
+ False,
+ XA_CARDINAL,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && pProperty
+ )
+ {
+ m_nDesktops = *reinterpret_cast<long*>(pProperty);
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+}
+
+/*
+ * getNetWmName()
+ */
+bool WMAdaptor::getNetWmName()
+{
+ Atom aRealType = None;
+ int nFormat = 8;
+ unsigned long nItems = 0;
+ unsigned long nBytesLeft = 0;
+ unsigned char* pProperty = nullptr;
+ bool bNetWM = false;
+
+ if( m_aWMAtoms[ NET_SUPPORTING_WM_CHECK ] && m_aWMAtoms[ NET_WM_NAME ] )
+ {
+ if( XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ NET_SUPPORTING_WM_CHECK ],
+ 0, 1,
+ False,
+ XA_WINDOW,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && aRealType == XA_WINDOW
+ && nFormat == 32
+ && nItems != 0
+ )
+ {
+ ::Window aWMChild = *reinterpret_cast< ::Window* >(pProperty);
+ XFree( pProperty );
+ pProperty = nullptr;
+ GetGenericUnixSalData()->ErrorTrapPush();
+ if( XGetWindowProperty( m_pDisplay,
+ aWMChild,
+ m_aWMAtoms[ NET_SUPPORTING_WM_CHECK ],
+ 0, 1,
+ False,
+ XA_WINDOW,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && aRealType == XA_WINDOW
+ && nFormat == 32
+ && nItems != 0 )
+ {
+ if ( ! GetGenericUnixSalData()->ErrorTrapPop( false ) )
+ {
+ GetGenericUnixSalData()->ErrorTrapPush();
+ ::Window aCheckWindow = *reinterpret_cast< ::Window* >(pProperty);
+ XFree( pProperty );
+ pProperty = nullptr;
+ if( aCheckWindow == aWMChild )
+ {
+ bNetWM = true;
+ // get name of WM
+ m_aWMAtoms[ UTF8_STRING ] = XInternAtom( m_pDisplay, "UTF8_STRING", False );
+ if( XGetWindowProperty( m_pDisplay,
+ aWMChild,
+ m_aWMAtoms[ NET_WM_NAME ],
+ 0, 256,
+ False,
+ AnyPropertyType, /* m_aWMAtoms[ UTF8_STRING ],*/
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && nItems != 0
+ )
+ {
+ if (aRealType == m_aWMAtoms[ UTF8_STRING ])
+ m_aWMName = OUString( reinterpret_cast<char*>(pProperty), nItems, RTL_TEXTENCODING_UTF8 );
+ else if (aRealType == XA_STRING)
+ m_aWMName = OUString( reinterpret_cast<char*>(pProperty), nItems, RTL_TEXTENCODING_ISO_8859_1 );
+
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+
+ // if this is metacity, check for version to enable a legacy workaround
+ if( m_aWMName == "Metacity" )
+ {
+ int nVersionMajor = 0, nVersionMinor = 0;
+ Atom nVersionAtom = XInternAtom( m_pDisplay, "_METACITY_VERSION", True );
+ if( nVersionAtom )
+ {
+ if( XGetWindowProperty( m_pDisplay,
+ aWMChild,
+ nVersionAtom,
+ 0, 256,
+ False,
+ m_aWMAtoms[ UTF8_STRING ],
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && nItems != 0
+ )
+ {
+ OUString aMetaVersion( reinterpret_cast<char*>(pProperty), nItems, RTL_TEXTENCODING_UTF8 );
+ sal_Int32 nIdx {0};
+ nVersionMajor = o3tl::toInt32(o3tl::getToken(aMetaVersion, 0, '.', nIdx));
+ nVersionMinor = o3tl::toInt32(o3tl::getToken(aMetaVersion, 0, '.', nIdx));
+ }
+ if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ }
+ if( nVersionMajor < 2 || (nVersionMajor == 2 && nVersionMinor < 12) )
+ m_bLegacyPartialFullscreen = true;
+ }
+ }
+ }
+ else
+ {
+ if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ GetGenericUnixSalData()->ErrorTrapPush();
+ }
+ }
+
+ GetGenericUnixSalData()->ErrorTrapPop();
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ }
+ return bNetWM;
+}
+
+bool WMAdaptor::getWMshouldSwitchWorkspace() const
+{
+ if( ! m_bWMshouldSwitchWorkspaceInit )
+ {
+ WMAdaptor * pWMA = const_cast<WMAdaptor*>(this);
+
+ pWMA->m_bWMshouldSwitchWorkspace = true;
+ vcl::SettingsConfigItem* pItem = vcl::SettingsConfigItem::get();
+ OUString aSetting( pItem->getValue( "WM",
+ "ShouldSwitchWorkspace" ) );
+ if( aSetting.isEmpty() )
+ {
+ if( m_aWMName == "awesome" )
+ {
+ pWMA->m_bWMshouldSwitchWorkspace = false;
+ }
+ }
+ else
+ pWMA->m_bWMshouldSwitchWorkspace = aSetting.toBoolean();
+ pWMA->m_bWMshouldSwitchWorkspaceInit = true;
+ }
+ return m_bWMshouldSwitchWorkspace;
+}
+
+/*
+ * WMAdaptor::isValid()
+ */
+bool WMAdaptor::isValid() const
+{
+ return true;
+}
+
+/*
+ * NetWMAdaptor::isValid()
+ */
+bool NetWMAdaptor::isValid() const
+{
+ // some necessary sanity checks; there are WMs out there
+ // which implement some of the WM hints spec without
+ // real functionality
+ return
+ m_aWMAtoms[ NET_SUPPORTED ]
+ && m_aWMAtoms[ NET_SUPPORTING_WM_CHECK ]
+ && m_aWMAtoms[ NET_WM_NAME ]
+ && m_aWMAtoms[ NET_WM_WINDOW_TYPE_NORMAL ]
+ && m_aWMAtoms[ NET_WM_WINDOW_TYPE_DIALOG ]
+ ;
+}
+
+/*
+ * GnomeWMAdaptor::isValid()
+ */
+bool GnomeWMAdaptor::isValid() const
+{
+ return m_bValid;
+}
+
+/*
+ * WMAdaptor::initAtoms
+ */
+
+void WMAdaptor::initAtoms()
+{
+ // get basic atoms
+ for(const WMAdaptorProtocol & i : aAtomTab)
+ m_aWMAtoms[ i.nProtocol ] = XInternAtom( m_pDisplay, i.pProtocol, False );
+ m_aWMAtoms[ NET_SUPPORTING_WM_CHECK ] = XInternAtom( m_pDisplay, "_NET_SUPPORTING_WM_CHECK", True );
+ m_aWMAtoms[ NET_WM_NAME ] = XInternAtom( m_pDisplay, "_NET_WM_NAME", True );
+}
+
+/*
+ * NetWMAdaptor::initAtoms
+ */
+
+void NetWMAdaptor::initAtoms()
+{
+ WMAdaptor::initAtoms();
+
+ m_aWMAtoms[ NET_SUPPORTED ] = XInternAtom( m_pDisplay, "_NET_SUPPORTED", True );
+}
+
+/*
+ * GnomeWMAdaptor::initAtoms
+ */
+
+void GnomeWMAdaptor::initAtoms()
+{
+ WMAdaptor::initAtoms();
+
+ m_aWMAtoms[ WIN_PROTOCOLS ] = XInternAtom( m_pDisplay, "_WIN_PROTOCOLS", True );
+ m_aWMAtoms[ WIN_SUPPORTING_WM_CHECK ] = XInternAtom( m_pDisplay, "_WIN_SUPPORTING_WM_CHECK", True );
+}
+
+/*
+ * WMAdaptor::setWMName
+ * sets WM_NAME
+ * WM_ICON_NAME
+ */
+
+void WMAdaptor::setWMName( X11SalFrame* pFrame, const OUString& rWMName ) const
+{
+ OString aTitle(OUStringToOString(rWMName,
+ osl_getThreadTextEncoding()));
+
+ OString aWMLocale;
+ rtl_Locale* pLocale = nullptr;
+ osl_getProcessLocale( &pLocale );
+ if( pLocale )
+ {
+ OUString aLocaleString(
+ LanguageTag( *pLocale).getGlibcLocaleString( std::u16string_view()));
+ aWMLocale = OUStringToOString( aLocaleString, RTL_TEXTENCODING_ISO_8859_1 );
+ }
+ else
+ {
+ static const char* pLang = getenv( "LANG" );
+ aWMLocale = pLang ? pLang : "C";
+ }
+
+ char* pT = const_cast<char*>(aTitle.getStr());
+ XTextProperty aProp = { nullptr, None, 0, 0 };
+ XmbTextListToTextProperty( m_pDisplay,
+ &pT,
+ 1,
+ XStdICCTextStyle,
+ &aProp );
+
+ unsigned char const * pData = aProp.nitems ? aProp.value : reinterpret_cast<unsigned char const *>(aTitle.getStr());
+ Atom nType = aProp.nitems ? aProp.encoding : XA_STRING;
+ int nFormat = aProp.nitems ? aProp.format : 8;
+ int nBytes = aProp.nitems ? aProp.nitems : aTitle.getLength();
+ const SystemEnvData* pEnv = pFrame->GetSystemData();
+ XChangeProperty( m_pDisplay,
+ static_cast<::Window>(pEnv->aShellWindow),
+ XA_WM_NAME,
+ nType,
+ nFormat,
+ PropModeReplace,
+ pData,
+ nBytes );
+ XChangeProperty( m_pDisplay,
+ static_cast<::Window>(pEnv->aShellWindow),
+ XA_WM_ICON_NAME,
+ nType,
+ nFormat,
+ PropModeReplace,
+ pData,
+ nBytes );
+ XChangeProperty( m_pDisplay,
+ static_cast<::Window>(pEnv->aShellWindow),
+ m_aWMAtoms[ WM_LOCALE_NAME ],
+ XA_STRING,
+ 8,
+ PropModeReplace,
+ reinterpret_cast<unsigned char const *>(aWMLocale.getStr()),
+ aWMLocale.getLength() );
+ if (aProp.value != nullptr)
+ XFree( aProp.value );
+}
+
+/*
+ * NetWMAdaptor::setWMName
+ * sets WM_NAME
+ * _NET_WM_NAME
+ * WM_ICON_NAME
+ * _NET_WM_ICON_NAME
+ */
+void NetWMAdaptor::setWMName( X11SalFrame* pFrame, const OUString& rWMName ) const
+{
+ WMAdaptor::setWMName( pFrame, rWMName );
+
+ OString aTitle(OUStringToOString(rWMName, RTL_TEXTENCODING_UTF8));
+ const SystemEnvData* pEnv = pFrame->GetSystemData();
+ if( m_aWMAtoms[ NET_WM_NAME ] )
+ XChangeProperty( m_pDisplay,
+ static_cast<::Window>(pEnv->aShellWindow),
+ m_aWMAtoms[ NET_WM_NAME ],
+ m_aWMAtoms[ UTF8_STRING ],
+ 8,
+ PropModeReplace,
+ reinterpret_cast<unsigned char const *>(aTitle.getStr()),
+ aTitle.getLength() );
+ if( m_aWMAtoms[ NET_WM_ICON_NAME ] )
+ XChangeProperty( m_pDisplay,
+ static_cast<::Window>(pEnv->aShellWindow),
+ m_aWMAtoms[ NET_WM_ICON_NAME ],
+ m_aWMAtoms[ UTF8_STRING ],
+ 8,
+ PropModeReplace,
+ reinterpret_cast<unsigned char const *>(aTitle.getStr()),
+ aTitle.getLength() );
+}
+
+/*
+ * NetWMAdaptor::setNetWMState
+ * sets _NET_WM_STATE
+ */
+void NetWMAdaptor::setNetWMState( X11SalFrame* pFrame ) const
+{
+ if( !(m_aWMAtoms[ NET_WM_STATE ]) )
+ return;
+
+ Atom aStateAtoms[ 10 ];
+ int nStateAtoms = 0;
+
+ // set NET_WM_STATE_MODAL
+ if( pFrame->mbMaximizedVert
+ && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ] )
+ aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ];
+ if( pFrame->mbMaximizedHorz
+ && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ] )
+ aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ];
+ if( pFrame->bAlwaysOnTop_ && m_aWMAtoms[ NET_WM_STATE_STAYS_ON_TOP ] )
+ aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_STAYS_ON_TOP ];
+ if( pFrame->mbFullScreen && m_aWMAtoms[ NET_WM_STATE_FULLSCREEN ] )
+ aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_FULLSCREEN ];
+ if( pFrame->meWindowType == WMWindowType::Utility && m_aWMAtoms[ NET_WM_STATE_SKIP_TASKBAR ] )
+ aStateAtoms[ nStateAtoms++ ] = m_aWMAtoms[ NET_WM_STATE_SKIP_TASKBAR ];
+
+ if( nStateAtoms )
+ {
+ XChangeProperty( m_pDisplay,
+ pFrame->GetShellWindow(),
+ m_aWMAtoms[ NET_WM_STATE ],
+ XA_ATOM,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(aStateAtoms),
+ nStateAtoms
+ );
+ }
+ else
+ XDeleteProperty( m_pDisplay,
+ pFrame->GetShellWindow(),
+ m_aWMAtoms[ NET_WM_STATE ] );
+ if( !pFrame->mbMaximizedHorz
+ || !pFrame->mbMaximizedVert
+ || ( pFrame->nStyle_ & SalFrameStyleFlags::SIZEABLE ) )
+ return;
+
+ /*
+ * for maximizing use NorthWestGravity (including decoration)
+ */
+ XSizeHints hints;
+ tools::Long supplied;
+ bool bHint = false;
+ if( XGetWMNormalHints( m_pDisplay,
+ pFrame->GetShellWindow(),
+ &hints,
+ &supplied ) )
+ {
+ bHint = true;
+ hints.flags |= PWinGravity;
+ hints.win_gravity = NorthWestGravity;
+ XSetWMNormalHints( m_pDisplay,
+ pFrame->GetShellWindow(),
+ &hints );
+ XSync( m_pDisplay, False );
+ }
+
+ // SetPosSize necessary to set width/height, min/max w/h
+ sal_Int32 nCurrent = 0;
+ /*
+ * get current desktop here if work areas have different size
+ * (does this happen on any platform ?)
+ */
+ if( ! m_bEqualWorkAreas )
+ {
+ nCurrent = getCurrentWorkArea();
+ if( nCurrent < 0 )
+ nCurrent = 0;
+ }
+ tools::Rectangle aPosSize = m_aWMWorkAreas[nCurrent];
+ const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() );
+ aPosSize = tools::Rectangle( Point( aPosSize.Left() + rGeom.nLeftDecoration,
+ aPosSize.Top() + rGeom.nTopDecoration ),
+ Size( aPosSize.GetWidth()
+ - rGeom.nLeftDecoration
+ - rGeom.nRightDecoration,
+ aPosSize.GetHeight()
+ - rGeom.nTopDecoration
+ - rGeom.nBottomDecoration )
+ );
+ pFrame->SetPosSize( aPosSize );
+
+ /*
+ * reset gravity hint to static gravity
+ * (this should not move window according to ICCCM)
+ */
+ if( bHint && pFrame->nShowState_ != X11ShowState::Unknown )
+ {
+ hints.win_gravity = StaticGravity;
+ XSetWMNormalHints( m_pDisplay,
+ pFrame->GetShellWindow(),
+ &hints );
+ }
+}
+
+/*
+ * GnomeWMAdaptor::setNetWMState
+ * sets _WIN_STATE
+ */
+void GnomeWMAdaptor::setGnomeWMState( X11SalFrame* pFrame ) const
+{
+ if( !(m_aWMAtoms[ WIN_STATE ]) )
+ return;
+
+ sal_uInt32 nWinWMState = 0;
+
+ if( pFrame->mbMaximizedVert )
+ nWinWMState |= 1 << 2;
+ if( pFrame->mbMaximizedHorz )
+ nWinWMState |= 1 << 3;
+
+ XChangeProperty( m_pDisplay,
+ pFrame->GetShellWindow(),
+ m_aWMAtoms[ WIN_STATE ],
+ XA_CARDINAL,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(&nWinWMState),
+ 1
+ );
+ if( !pFrame->mbMaximizedHorz
+ || !pFrame->mbMaximizedVert
+ || ( pFrame->nStyle_ & SalFrameStyleFlags::SIZEABLE ) )
+ return;
+
+ /*
+ * for maximizing use NorthWestGravity (including decoration)
+ */
+ XSizeHints hints;
+ tools::Long supplied;
+ bool bHint = false;
+ if( XGetWMNormalHints( m_pDisplay,
+ pFrame->GetShellWindow(),
+ &hints,
+ &supplied ) )
+ {
+ bHint = true;
+ hints.flags |= PWinGravity;
+ hints.win_gravity = NorthWestGravity;
+ XSetWMNormalHints( m_pDisplay,
+ pFrame->GetShellWindow(),
+ &hints );
+ XSync( m_pDisplay, False );
+ }
+
+ // SetPosSize necessary to set width/height, min/max w/h
+ sal_Int32 nCurrent = 0;
+ /*
+ * get current desktop here if work areas have different size
+ * (does this happen on any platform ?)
+ */
+ if( ! m_bEqualWorkAreas )
+ {
+ nCurrent = getCurrentWorkArea();
+ if( nCurrent < 0 )
+ nCurrent = 0;
+ }
+ tools::Rectangle aPosSize = m_aWMWorkAreas[nCurrent];
+ const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() );
+ aPosSize = tools::Rectangle( Point( aPosSize.Left() + rGeom.nLeftDecoration,
+ aPosSize.Top() + rGeom.nTopDecoration ),
+ Size( aPosSize.GetWidth()
+ - rGeom.nLeftDecoration
+ - rGeom.nRightDecoration,
+ aPosSize.GetHeight()
+ - rGeom.nTopDecoration
+ - rGeom.nBottomDecoration )
+ );
+ pFrame->SetPosSize( aPosSize );
+
+ /*
+ * reset gravity hint to static gravity
+ * (this should not move window according to ICCCM)
+ */
+ if( bHint && pFrame->nShowState_ != X11ShowState::Unknown )
+ {
+ hints.win_gravity = StaticGravity;
+ XSetWMNormalHints( m_pDisplay,
+ pFrame->GetShellWindow(),
+ &hints );
+ }
+}
+
+/*
+ * WMAdaptor::setFrameDecoration
+ * sets _MOTIF_WM_HINTS
+ * WM_TRANSIENT_FOR
+ */
+
+void WMAdaptor::setFrameTypeAndDecoration( X11SalFrame* pFrame, WMWindowType eType, int nDecorationFlags, X11SalFrame* pReferenceFrame ) const
+{
+ pFrame->meWindowType = eType;
+
+ if( ! pFrame->mbFullScreen )
+ {
+ // set mwm hints
+ struct _mwmhints {
+ unsigned long flags, func, deco;
+ tools::Long input_mode;
+ unsigned long status;
+ } aHint;
+
+ aHint.flags = 15; /* flags for functions, decoration, input mode and status */
+ aHint.deco = 0;
+ aHint.func = 1 << 2;
+ aHint.status = 0;
+ aHint.input_mode = 0;
+
+ // evaluate decoration flags
+ if( nDecorationFlags & decoration_All )
+ {
+ aHint.deco = 1;
+ aHint.func = 1;
+ }
+ else
+ {
+ if( nDecorationFlags & decoration_Title )
+ aHint.deco |= 1 << 3;
+ if( nDecorationFlags & decoration_Border )
+ aHint.deco |= 1 << 1;
+ if( nDecorationFlags & decoration_Resize )
+ {
+ aHint.deco |= 1 << 2;
+ aHint.func |= 1 << 1;
+ }
+ if( nDecorationFlags & decoration_MinimizeBtn )
+ {
+ aHint.deco |= 1 << 5;
+ aHint.func |= 1 << 3;
+ }
+ if( nDecorationFlags & decoration_MaximizeBtn )
+ {
+ aHint.deco |= 1 << 6;
+ aHint.func |= 1 << 4;
+ }
+ if( nDecorationFlags & decoration_CloseBtn )
+ {
+ aHint.deco |= 1 << 4;
+ aHint.func |= 1 << 5;
+ }
+ }
+
+ // set the hint
+ XChangeProperty( m_pDisplay,
+ pFrame->GetShellWindow(),
+ m_aWMAtoms[ MOTIF_WM_HINTS ],
+ m_aWMAtoms[ MOTIF_WM_HINTS ],
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(&aHint),
+ 5 );
+ }
+
+ // set transientFor hint
+ /* #91030# dtwm will not map a dialogue if the transient
+ * window is iconified. This is deemed undesirable because
+ * message boxes do not get mapped, so use the root as transient
+ * instead.
+ */
+ if( pReferenceFrame )
+ {
+ XSetTransientForHint( m_pDisplay,
+ pFrame->GetShellWindow(),
+ pReferenceFrame->bMapped_ ?
+ pReferenceFrame->GetShellWindow() :
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() )
+ );
+ if( ! pReferenceFrame->bMapped_ )
+ pFrame->mbTransientForRoot = true;
+ }
+}
+
+/*
+ * NetWMAdaptor::setFrameDecoration
+ * sets _MOTIF_WM_HINTS
+ * _NET_WM_WINDOW_TYPE
+ * _NET_WM_STATE
+ * WM_TRANSIENT_FOR
+ */
+
+void NetWMAdaptor::setFrameTypeAndDecoration( X11SalFrame* pFrame, WMWindowType eType, int nDecorationFlags, X11SalFrame* pReferenceFrame ) const
+{
+ WMAdaptor::setFrameTypeAndDecoration( pFrame, eType, nDecorationFlags, pReferenceFrame );
+
+ setNetWMState( pFrame );
+
+ // set NET_WM_WINDOW_TYPE
+ if( m_aWMAtoms[ NET_WM_WINDOW_TYPE ] )
+ {
+ Atom aWindowTypes[4];
+ int nWindowTypes = 0;
+ switch( eType )
+ {
+ case WMWindowType::Utility:
+ aWindowTypes[nWindowTypes++] =
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_UTILITY ] ?
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_UTILITY ] :
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_DIALOG ];
+ break;
+ case WMWindowType::ModelessDialogue:
+ aWindowTypes[nWindowTypes++] =
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_DIALOG ];
+ break;
+ case WMWindowType::Splash:
+ aWindowTypes[nWindowTypes++] =
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_SPLASH ] ?
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_SPLASH ] :
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_NORMAL ];
+ break;
+ case WMWindowType::Toolbar:
+ if( m_aWMAtoms[ KDE_NET_WM_WINDOW_TYPE_OVERRIDE ] )
+ aWindowTypes[nWindowTypes++] = m_aWMAtoms[ KDE_NET_WM_WINDOW_TYPE_OVERRIDE ];
+ aWindowTypes[nWindowTypes++] =
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_TOOLBAR ] ?
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_TOOLBAR ] :
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_NORMAL];
+ break;
+ case WMWindowType::Dock:
+ aWindowTypes[nWindowTypes++] =
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_DOCK ] ?
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_DOCK ] :
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE_NORMAL];
+ break;
+ default:
+ aWindowTypes[nWindowTypes++] = m_aWMAtoms[ NET_WM_WINDOW_TYPE_NORMAL ];
+ break;
+ }
+ XChangeProperty( m_pDisplay,
+ pFrame->GetShellWindow(),
+ m_aWMAtoms[ NET_WM_WINDOW_TYPE ],
+ XA_ATOM,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(aWindowTypes),
+ nWindowTypes );
+ }
+ if( ( eType == WMWindowType::ModelessDialogue )
+ && ! pReferenceFrame )
+ {
+ XSetTransientForHint( m_pDisplay,
+ pFrame->GetShellWindow(),
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ) );
+ pFrame->mbTransientForRoot = true;
+ }
+}
+
+/*
+ * WMAdaptor::maximizeFrame
+ */
+
+void WMAdaptor::maximizeFrame( X11SalFrame* pFrame, bool bHorizontal, bool bVertical ) const
+{
+ pFrame->mbMaximizedVert = bVertical;
+ pFrame->mbMaximizedHorz = bHorizontal;
+
+ const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() );
+
+ // discard pending configure notifies for this frame
+ XSync( m_pDisplay, False );
+ XEvent aDiscard;
+ while( XCheckTypedWindowEvent( m_pDisplay,
+ pFrame->GetShellWindow(),
+ ConfigureNotify,
+ &aDiscard ) )
+ ;
+ while( XCheckTypedWindowEvent( m_pDisplay,
+ pFrame->GetWindow(),
+ ConfigureNotify,
+ &aDiscard ) )
+ ;
+
+ if( bHorizontal || bVertical )
+ {
+ Size aScreenSize( m_pSalDisplay->GetScreenSize( pFrame->GetScreenNumber() ) );
+ Point aTL( rGeom.nLeftDecoration, rGeom.nTopDecoration );
+ if( m_pSalDisplay->IsXinerama() )
+ {
+ Point aMed( aTL.X() + rGeom.nWidth/2, aTL.Y() + rGeom.nHeight/2 );
+ const std::vector< tools::Rectangle >& rScreens = m_pSalDisplay->GetXineramaScreens();
+ for(const auto & rScreen : rScreens)
+ if( rScreen.Contains( aMed ) )
+ {
+ aTL += rScreen.TopLeft();
+ aScreenSize = rScreen.GetSize();
+ break;
+ }
+ }
+ tools::Rectangle aTarget( aTL,
+ Size( aScreenSize.Width() - rGeom.nLeftDecoration - rGeom.nTopDecoration,
+ aScreenSize.Height() - rGeom.nTopDecoration - rGeom.nBottomDecoration )
+ );
+ if( ! bHorizontal )
+ {
+ aTarget.SetSize(
+ Size(
+ pFrame->maRestorePosSize.IsEmpty() ?
+ rGeom.nWidth : pFrame->maRestorePosSize.GetWidth(),
+ aTarget.GetHeight()
+ )
+ );
+ aTarget.SetLeft(
+ pFrame->maRestorePosSize.IsEmpty() ?
+ rGeom.nX : pFrame->maRestorePosSize.Left() );
+ }
+ else if( ! bVertical )
+ {
+ aTarget.SetSize(
+ Size(
+ aTarget.GetWidth(),
+ pFrame->maRestorePosSize.IsEmpty() ?
+ rGeom.nHeight : pFrame->maRestorePosSize.GetHeight()
+ )
+ );
+ aTarget.SetTop(
+ pFrame->maRestorePosSize.IsEmpty() ?
+ rGeom.nY : pFrame->maRestorePosSize.Top() );
+ }
+
+ tools::Rectangle aRestore( Point( rGeom.nX, rGeom.nY ), Size( rGeom.nWidth, rGeom.nHeight ) );
+ if( pFrame->bMapped_ )
+ {
+ XSetInputFocus( m_pDisplay,
+ pFrame->GetShellWindow(),
+ RevertToNone,
+ CurrentTime
+ );
+ }
+
+ if( pFrame->maRestorePosSize.IsEmpty() )
+ pFrame->maRestorePosSize = aRestore;
+
+ pFrame->SetPosSize( aTarget );
+ pFrame->nWidth_ = aTarget.GetWidth();
+ pFrame->nHeight_ = aTarget.GetHeight();
+ XRaiseWindow( m_pDisplay,
+ pFrame->GetShellWindow()
+ );
+ if( pFrame->GetStackingWindow() )
+ XRaiseWindow( m_pDisplay,
+ pFrame->GetStackingWindow()
+ );
+
+ }
+ else
+ {
+ pFrame->SetPosSize( pFrame->maRestorePosSize );
+ pFrame->maRestorePosSize = tools::Rectangle();
+ pFrame->nWidth_ = rGeom.nWidth;
+ pFrame->nHeight_ = rGeom.nHeight;
+ }
+}
+
+/*
+ * NetWMAdaptor::maximizeFrame
+ * changes _NET_WM_STATE by sending a client message
+ */
+
+void NetWMAdaptor::maximizeFrame( X11SalFrame* pFrame, bool bHorizontal, bool bVertical ) const
+{
+ pFrame->mbMaximizedVert = bVertical;
+ pFrame->mbMaximizedHorz = bHorizontal;
+
+ if( m_aWMAtoms[ NET_WM_STATE ]
+ && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ]
+ && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ]
+ && ( pFrame->nStyle_ & ~SalFrameStyleFlags::DEFAULT )
+ )
+ {
+ if( pFrame->bMapped_ )
+ {
+ // window already mapped, send WM a message
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.window = pFrame->GetShellWindow();
+ aEvent.xclient.message_type = m_aWMAtoms[ NET_WM_STATE ];
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = bHorizontal ? 1 : 0;
+ aEvent.xclient.data.l[1] = m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ];
+ aEvent.xclient.data.l[2] = bHorizontal == bVertical ? m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ] : 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &aEvent
+ );
+ if( bHorizontal != bVertical )
+ {
+ aEvent.xclient.data.l[0]= bVertical ? 1 : 0;
+ aEvent.xclient.data.l[1]= m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ];
+ aEvent.xclient.data.l[2]= 0;
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &aEvent
+ );
+ }
+ }
+ else
+ {
+ // window not mapped yet, set _NET_WM_STATE directly
+ setNetWMState( pFrame );
+ }
+ if( !bHorizontal && !bVertical )
+ pFrame->maRestorePosSize = tools::Rectangle();
+ else if( pFrame->maRestorePosSize.IsEmpty() )
+ {
+ const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() );
+ pFrame->maRestorePosSize =
+ tools::Rectangle( Point( rGeom.nX, rGeom.nY ), Size( rGeom.nWidth, rGeom.nHeight ) );
+ }
+ }
+ else
+ WMAdaptor::maximizeFrame( pFrame, bHorizontal, bVertical );
+}
+
+/*
+ * GnomeWMAdaptor::maximizeFrame
+ * changes _WIN_STATE by sending a client message
+ */
+
+void GnomeWMAdaptor::maximizeFrame( X11SalFrame* pFrame, bool bHorizontal, bool bVertical ) const
+{
+ pFrame->mbMaximizedVert = bVertical;
+ pFrame->mbMaximizedHorz = bHorizontal;
+
+ if( m_aWMAtoms[ WIN_STATE ]
+ && ( pFrame->nStyle_ & ~SalFrameStyleFlags::DEFAULT )
+ )
+ {
+ if( pFrame->bMapped_ )
+ {
+ // window already mapped, send WM a message
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.window = pFrame->GetShellWindow();
+ aEvent.xclient.message_type = m_aWMAtoms[ WIN_STATE ];
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = (1<<2)|(1<<3);
+ aEvent.xclient.data.l[1] =
+ (bVertical ? (1<<2) : 0)
+ | (bHorizontal ? (1<<3) : 0);
+ aEvent.xclient.data.l[2] = 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ),
+ False,
+ SubstructureNotifyMask,
+ &aEvent
+ );
+ }
+ else
+ // window not mapped yet, set _WIN_STATE directly
+ setGnomeWMState( pFrame );
+
+ if( !bHorizontal && !bVertical )
+ pFrame->maRestorePosSize = tools::Rectangle();
+ else if( pFrame->maRestorePosSize.IsEmpty() )
+ {
+ const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() );
+ pFrame->maRestorePosSize =
+ tools::Rectangle( Point( rGeom.nX, rGeom.nY ), Size( rGeom.nWidth, rGeom.nHeight ) );
+ }
+ }
+ else
+ WMAdaptor::maximizeFrame( pFrame, bHorizontal, bVertical );
+}
+
+/*
+ * WMAdaptor::enableAlwaysOnTop
+ */
+void WMAdaptor::enableAlwaysOnTop( X11SalFrame*, bool /*bEnable*/ ) const
+{
+}
+
+/*
+ * NetWMAdaptor::enableAlwaysOnTop
+ */
+void NetWMAdaptor::enableAlwaysOnTop( X11SalFrame* pFrame, bool bEnable ) const
+{
+ pFrame->bAlwaysOnTop_ = bEnable;
+ if( !(m_aWMAtoms[ NET_WM_STATE_STAYS_ON_TOP ]) )
+ return;
+
+ if( pFrame->bMapped_ )
+ {
+ // window already mapped, send WM a message
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.window = pFrame->GetShellWindow();
+ aEvent.xclient.message_type = m_aWMAtoms[ NET_WM_STATE ];
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = bEnable ? 1 : 0;
+ aEvent.xclient.data.l[1] = m_aWMAtoms[ NET_WM_STATE_STAYS_ON_TOP ];
+ aEvent.xclient.data.l[2] = 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &aEvent
+ );
+ }
+ else
+ setNetWMState( pFrame );
+}
+
+/*
+ * GnomeWMAdaptor::enableAlwaysOnTop
+ */
+void GnomeWMAdaptor::enableAlwaysOnTop( X11SalFrame* pFrame, bool bEnable ) const
+{
+ pFrame->bAlwaysOnTop_ = bEnable;
+ if( !(m_aWMAtoms[ WIN_LAYER ]) )
+ return;
+
+ if( pFrame->bMapped_ )
+ {
+ // window already mapped, send WM a message
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.window = pFrame->GetShellWindow();
+ aEvent.xclient.message_type = m_aWMAtoms[ WIN_LAYER ];
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = bEnable ? 6 : 4;
+ aEvent.xclient.data.l[1] = 0;
+ aEvent.xclient.data.l[2] = 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &aEvent
+ );
+ }
+ else
+ {
+ sal_uInt32 nNewLayer = bEnable ? 6 : 4;
+ XChangeProperty( m_pDisplay,
+ pFrame->GetShellWindow(),
+ m_aWMAtoms[ WIN_LAYER ],
+ XA_CARDINAL,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(&nNewLayer),
+ 1
+ );
+ }
+}
+
+/*
+ * WMAdaptor::changeReferenceFrame
+ */
+void WMAdaptor::changeReferenceFrame( X11SalFrame* pFrame, X11SalFrame const * pReferenceFrame ) const
+{
+ if( ( pFrame->nStyle_ & SalFrameStyleFlags::PLUG )
+ || pFrame->IsOverrideRedirect()
+ || pFrame->IsFloatGrabWindow()
+ )
+ return;
+
+ ::Window aTransient = pFrame->pDisplay_->GetRootWindow( pFrame->GetScreenNumber() );
+ pFrame->mbTransientForRoot = true;
+ if( pReferenceFrame )
+ {
+ aTransient = pReferenceFrame->GetShellWindow();
+ pFrame->mbTransientForRoot = false;
+ }
+ XSetTransientForHint( m_pDisplay,
+ pFrame->GetShellWindow(),
+ aTransient );
+}
+
+/*
+ * WMAdaptor::handlePropertyNotify
+ */
+int WMAdaptor::handlePropertyNotify( X11SalFrame*, XPropertyEvent* ) const
+{
+ return 0;
+}
+
+/*
+ * NetWMAdaptor::handlePropertyNotify
+ */
+int NetWMAdaptor::handlePropertyNotify( X11SalFrame* pFrame, XPropertyEvent* pEvent ) const
+{
+ int nHandled = 1;
+ if( pEvent->atom == m_aWMAtoms[ NET_WM_STATE ] )
+ {
+ pFrame->mbMaximizedHorz = pFrame->mbMaximizedVert = false;
+
+ if( pEvent->state == PropertyNewValue )
+ {
+ Atom nType, *pStates;
+ int nFormat;
+ unsigned long nItems, nBytesLeft;
+ unsigned char* pData = nullptr;
+ tools::Long nOffset = 0;
+ do
+ {
+ XGetWindowProperty( m_pDisplay,
+ pEvent->window,
+ m_aWMAtoms[ NET_WM_STATE ],
+ nOffset, 64,
+ False,
+ XA_ATOM,
+ &nType,
+ &nFormat,
+ &nItems, &nBytesLeft,
+ &pData );
+ if( pData )
+ {
+ if( nType == XA_ATOM && nFormat == 32 && nItems > 0 )
+ {
+ pStates = reinterpret_cast<Atom*>(pData);
+ for( unsigned long i = 0; i < nItems; i++ )
+ {
+ if( pStates[i] == m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ] && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_VERT ] )
+ pFrame->mbMaximizedVert = true;
+ else if( pStates[i] == m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ] && m_aWMAtoms[ NET_WM_STATE_MAXIMIZED_HORZ ] )
+ pFrame->mbMaximizedHorz = true;
+ }
+ }
+ XFree( pData );
+ pData = nullptr;
+ nOffset += nItems * nFormat / 32;
+ }
+ else
+ break;
+ } while( nBytesLeft > 0 );
+ }
+
+ if( ! (pFrame->mbMaximizedHorz || pFrame->mbMaximizedVert ) )
+ pFrame->maRestorePosSize = tools::Rectangle();
+ else
+ {
+ const SalFrameGeometry& rGeom = pFrame->GetUnmirroredGeometry();
+ // the current geometry may already be changed by the corresponding
+ // ConfigureNotify, but this cannot be helped
+ pFrame->maRestorePosSize =
+ tools::Rectangle( Point( rGeom.nX, rGeom.nY ),
+ Size( rGeom.nWidth, rGeom.nHeight ) );
+ }
+ }
+ else if( pEvent->atom == m_aWMAtoms[ NET_WM_DESKTOP ] )
+ {
+ pFrame->m_nWorkArea = getWindowWorkArea( pFrame->GetShellWindow() );
+ }
+ else
+ nHandled = 0;
+
+ return nHandled;
+}
+
+/*
+ * GnomeWMAdaptor::handlePropertyNotify
+ */
+int GnomeWMAdaptor::handlePropertyNotify( X11SalFrame* pFrame, XPropertyEvent* pEvent ) const
+{
+ int nHandled = 1;
+ if( pEvent->atom == m_aWMAtoms[ WIN_STATE ] )
+ {
+ pFrame->mbMaximizedHorz = pFrame->mbMaximizedVert = false;
+
+ if( pEvent->state == PropertyNewValue )
+ {
+ Atom nType;
+ int nFormat = 0;
+ unsigned long nItems = 0;
+ unsigned long nBytesLeft = 0;
+ unsigned char* pData = nullptr;
+ XGetWindowProperty( m_pDisplay,
+ pEvent->window,
+ m_aWMAtoms[ WIN_STATE ],
+ 0, 1,
+ False,
+ XA_CARDINAL,
+ &nType,
+ &nFormat,
+ &nItems, &nBytesLeft,
+ &pData );
+ if( pData )
+ {
+ if( nType == XA_CARDINAL && nFormat == 32 && nItems == 1 )
+ {
+ sal_uInt32 nWinState = *reinterpret_cast<sal_uInt32*>(pData);
+ if( nWinState & (1<<2) )
+ pFrame->mbMaximizedVert = true;
+ if( nWinState & (1<<3) )
+ pFrame->mbMaximizedHorz = true;
+ }
+ XFree( pData );
+ }
+ }
+
+ if( ! (pFrame->mbMaximizedHorz || pFrame->mbMaximizedVert ) )
+ pFrame->maRestorePosSize = tools::Rectangle();
+ else
+ {
+ const SalFrameGeometry& rGeom = pFrame->GetUnmirroredGeometry();
+ // the current geometry may already be changed by the corresponding
+ // ConfigureNotify, but this cannot be helped
+ pFrame->maRestorePosSize =
+ tools::Rectangle( Point( rGeom.nX, rGeom.nY ),
+ Size( rGeom.nWidth, rGeom.nHeight ) );
+ }
+ }
+ else if( pEvent->atom == m_aWMAtoms[ NET_WM_DESKTOP ] )
+ {
+ pFrame->m_nWorkArea = getWindowWorkArea( pFrame->GetShellWindow() );
+ }
+ else
+ nHandled = 0;
+
+ return nHandled;
+}
+
+/*
+ * WMAdaptor::showFullScreen
+ */
+void WMAdaptor::showFullScreen( X11SalFrame* pFrame, bool bFullScreen ) const
+{
+ pFrame->mbFullScreen = bFullScreen;
+ maximizeFrame( pFrame, bFullScreen, bFullScreen );
+}
+
+/*
+ * NetWMAdaptor::showFullScreen
+ */
+void NetWMAdaptor::showFullScreen( X11SalFrame* pFrame, bool bFullScreen ) const
+{
+ if( m_aWMAtoms[ NET_WM_STATE_FULLSCREEN ] )
+ {
+ pFrame->mbFullScreen = bFullScreen;
+ if( bFullScreen )
+ {
+ if( m_aWMAtoms[ MOTIF_WM_HINTS ] )
+ {
+ XDeleteProperty( m_pDisplay,
+ pFrame->GetShellWindow(),
+ m_aWMAtoms[ MOTIF_WM_HINTS ] );
+ }
+ }
+ if( pFrame->bMapped_ )
+ {
+ // window already mapped, send WM a message
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.window = pFrame->GetShellWindow();
+ aEvent.xclient.message_type = m_aWMAtoms[ NET_WM_STATE ];
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = bFullScreen ? 1 : 0;
+ aEvent.xclient.data.l[1] = m_aWMAtoms[ NET_WM_STATE_FULLSCREEN ];
+ aEvent.xclient.data.l[2] = 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &aEvent
+ );
+ }
+ else
+ {
+ // window not mapped yet, set _NET_WM_STATE directly
+ setNetWMState( pFrame );
+ }
+ // #i42750# guess size before resize event shows up
+ if( bFullScreen )
+ {
+ if( m_pSalDisplay->IsXinerama() )
+ {
+ ::Window aRoot, aChild;
+ int root_x = 0, root_y = 0, lx, ly;
+ unsigned int mask;
+ XQueryPointer( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ),
+ &aRoot, &aChild,
+ &root_x, &root_y, &lx, &ly, &mask );
+ const std::vector< tools::Rectangle >& rScreens = m_pSalDisplay->GetXineramaScreens();
+ Point aMousePoint( root_x, root_y );
+ for(const auto & rScreen : rScreens)
+ {
+ if( rScreen.Contains( aMousePoint ) )
+ {
+ pFrame->maGeometry.nX = rScreen.Left();
+ pFrame->maGeometry.nY = rScreen.Top();
+ pFrame->maGeometry.nWidth = rScreen.GetWidth();
+ pFrame->maGeometry.nHeight = rScreen.GetHeight();
+ break;
+ }
+ }
+ }
+ else
+ {
+ Size aSize = m_pSalDisplay->GetScreenSize( pFrame->GetScreenNumber() );
+ pFrame->maGeometry.nX = 0;
+ pFrame->maGeometry.nY = 0;
+ pFrame->maGeometry.nWidth = aSize.Width();
+ pFrame->maGeometry.nHeight = aSize.Height();
+ }
+ pFrame->CallCallback( SalEvent::MoveResize, nullptr );
+ }
+ }
+ else WMAdaptor::showFullScreen( pFrame, bFullScreen );
+}
+
+/*
+ * WMAdaptor::getCurrentWorkArea
+ */
+// FIXME: multiscreen case
+int WMAdaptor::getCurrentWorkArea() const
+{
+ int nCurrent = -1;
+ if( m_aWMAtoms[ NET_CURRENT_DESKTOP ] )
+ {
+ Atom aRealType = None;
+ int nFormat = 8;
+ unsigned long nItems = 0;
+ unsigned long nBytesLeft = 0;
+ unsigned char* pProperty = nullptr;
+ if( XGetWindowProperty( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ m_aWMAtoms[ NET_CURRENT_DESKTOP ],
+ 0, 1,
+ False,
+ XA_CARDINAL,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && pProperty
+ )
+ {
+ nCurrent = int(*reinterpret_cast<sal_Int32*>(pProperty));
+ XFree( pProperty );
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ }
+ return nCurrent;
+}
+
+/*
+ * WMAdaptor::getWindowWorkArea
+ */
+int WMAdaptor::getWindowWorkArea( ::Window aWindow ) const
+{
+ int nCurrent = -1;
+ if( m_aWMAtoms[ NET_WM_DESKTOP ] )
+ {
+ Atom aRealType = None;
+ int nFormat = 8;
+ unsigned long nItems = 0;
+ unsigned long nBytesLeft = 0;
+ unsigned char* pProperty = nullptr;
+ if( XGetWindowProperty( m_pDisplay,
+ aWindow,
+ m_aWMAtoms[ NET_WM_DESKTOP ],
+ 0, 1,
+ False,
+ XA_CARDINAL,
+ &aRealType,
+ &nFormat,
+ &nItems,
+ &nBytesLeft,
+ &pProperty ) == 0
+ && pProperty
+ )
+ {
+ nCurrent = int(*reinterpret_cast<sal_Int32*>(pProperty));
+ XFree( pProperty );
+ }
+ else if( pProperty )
+ {
+ XFree( pProperty );
+ pProperty = nullptr;
+ }
+ }
+ return nCurrent;
+}
+
+/*
+ * WMAdaptor::getCurrentWorkArea
+ */
+// fixme: multi screen case
+void WMAdaptor::switchToWorkArea( int nWorkArea ) const
+{
+ if( ! getWMshouldSwitchWorkspace() )
+ return;
+
+ if( !m_aWMAtoms[ NET_CURRENT_DESKTOP ] )
+ return;
+
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.window = m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() );
+ aEvent.xclient.message_type = m_aWMAtoms[ NET_CURRENT_DESKTOP ];
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = nWorkArea;
+ aEvent.xclient.data.l[1] = 0;
+ aEvent.xclient.data.l[2] = 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( m_pSalDisplay->GetDefaultXScreen() ),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &aEvent
+ );
+
+}
+
+/*
+ * WMAdaptor::frameIsMapping
+ */
+void WMAdaptor::frameIsMapping( X11SalFrame* ) const
+{
+}
+
+/*
+ * NetWMAdaptor::frameIsMapping
+ */
+void NetWMAdaptor::frameIsMapping( X11SalFrame* pFrame ) const
+{
+ setNetWMState( pFrame );
+}
+
+/*
+ * WMAdaptor::setUserTime
+ */
+void WMAdaptor::setUserTime( X11SalFrame*, tools::Long ) const
+{
+}
+
+/*
+ * NetWMAdaptor::setUserTime
+ */
+void NetWMAdaptor::setUserTime( X11SalFrame* i_pFrame, tools::Long i_nUserTime ) const
+{
+ if( m_aWMAtoms[NET_WM_USER_TIME] )
+ {
+ XChangeProperty( m_pDisplay,
+ i_pFrame->GetShellWindow(),
+ m_aWMAtoms[NET_WM_USER_TIME],
+ XA_CARDINAL,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(&i_nUserTime),
+ 1
+ );
+ }
+}
+
+/*
+ * WMAdaptor::setPID
+ */
+void WMAdaptor::setPID( X11SalFrame const * i_pFrame ) const
+{
+ if( !(m_aWMAtoms[NET_WM_PID]) )
+ return;
+
+ tools::Long nPID = static_cast<tools::Long>(getpid());
+ XChangeProperty( m_pDisplay,
+ i_pFrame->GetShellWindow(),
+ m_aWMAtoms[NET_WM_PID],
+ XA_CARDINAL,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(&nPID),
+ 1
+ );
+}
+
+/*
+* WMAdaptor::setClientMachine
+*/
+void WMAdaptor::setClientMachine( X11SalFrame const * i_pFrame ) const
+{
+ OString aWmClient( OUStringToOString( GetGenericUnixSalData()->GetHostname(), RTL_TEXTENCODING_ASCII_US ) );
+ XTextProperty aClientProp = { reinterpret_cast<unsigned char *>(const_cast<char *>(aWmClient.getStr())), XA_STRING, 8, sal::static_int_cast<unsigned long>( aWmClient.getLength() ) };
+ XSetWMClientMachine( m_pDisplay, i_pFrame->GetShellWindow(), &aClientProp );
+}
+
+void WMAdaptor::answerPing( X11SalFrame const * i_pFrame, XClientMessageEvent const * i_pEvent ) const
+{
+ if( !m_aWMAtoms[NET_WM_PING] ||
+ i_pEvent->message_type != m_aWMAtoms[ WM_PROTOCOLS ] ||
+ static_cast<Atom>(i_pEvent->data.l[0]) != m_aWMAtoms[ NET_WM_PING ] )
+ return;
+
+ XEvent aEvent;
+ aEvent.xclient = *i_pEvent;
+ aEvent.xclient.window = m_pSalDisplay->GetRootWindow( i_pFrame->GetScreenNumber() );
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( i_pFrame->GetScreenNumber() ),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &aEvent
+ );
+ XFlush( m_pDisplay );
+}
+
+void WMAdaptor::activateWindow( X11SalFrame const *pFrame, Time nTimestamp )
+{
+ if (!pFrame->bMapped_)
+ return;
+
+ XEvent aEvent;
+
+ aEvent.xclient.type = ClientMessage;
+ aEvent.xclient.window = pFrame->GetShellWindow();
+ aEvent.xclient.message_type = m_aWMAtoms[ NET_ACTIVE_WINDOW ];
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = 1;
+ aEvent.xclient.data.l[1] = nTimestamp;
+ aEvent.xclient.data.l[2] = None;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+
+ XSendEvent( m_pDisplay,
+ m_pSalDisplay->GetRootWindow( pFrame->GetScreenNumber() ),
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &aEvent );
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/desktopdetect/desktopdetector.cxx b/vcl/unx/generic/desktopdetect/desktopdetector.cxx
new file mode 100644
index 000000000..bad134fbd
--- /dev/null
+++ b/vcl/unx/generic/desktopdetect/desktopdetector.cxx
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <X11/Xlib.h>
+
+#include <unx/desktops.hxx>
+
+#include <rtl/bootstrap.hxx>
+#include <rtl/process.h>
+#include <osl/thread.h>
+
+#include <vclpluginapi.h>
+
+#include <string.h>
+#include <comphelper/string.hxx>
+
+static bool is_gnome_desktop( Display* pDisplay )
+{
+
+ // warning: these checks are coincidental, GNOME does not
+ // explicitly advertise itself
+ if ( getenv( "GNOME_DESKTOP_SESSION_ID" ) )
+ return true;
+
+ bool ret = false;
+
+ Atom nAtom1 = XInternAtom( pDisplay, "GNOME_SM_PROXY", True );
+ Atom nAtom2 = XInternAtom( pDisplay, "NAUTILUS_DESKTOP_WINDOW_ID", True );
+ if( nAtom1 || nAtom2 )
+ {
+ int nProperties = 0;
+ Atom* pProperties = XListProperties( pDisplay, DefaultRootWindow( pDisplay ), &nProperties );
+ if( pProperties && nProperties )
+ {
+ for( int i = 0; i < nProperties; i++ )
+ if( pProperties[ i ] == nAtom1 ||
+ pProperties[ i ] == nAtom2 )
+ {
+ ret = true;
+ break;
+ }
+ XFree( pProperties );
+ }
+ }
+ if (ret)
+ return true;
+
+ Atom nUTFAtom = XInternAtom( pDisplay, "UTF8_STRING", True );
+ Atom nNetWMNameAtom = XInternAtom( pDisplay, "_NET_WM_NAME", True );
+ if( nUTFAtom && nNetWMNameAtom )
+ {
+ // another, more expensive check: search for a gnome-panel
+ ::Window aRoot, aParent, *pChildren = nullptr;
+ unsigned int nChildren = 0;
+ XQueryTree( pDisplay, DefaultRootWindow( pDisplay ),
+ &aRoot, &aParent, &pChildren, &nChildren );
+ if( pChildren && nChildren )
+ {
+ for( unsigned int i = 0; i < nChildren && ! ret; i++ )
+ {
+ Atom nType = None;
+ int nFormat = 0;
+ unsigned long nItems = 0, nBytes = 0;
+ unsigned char* pProp = nullptr;
+ XGetWindowProperty( pDisplay,
+ pChildren[i],
+ nNetWMNameAtom,
+ 0, 8,
+ False,
+ nUTFAtom,
+ &nType,
+ &nFormat,
+ &nItems,
+ &nBytes,
+ &pProp );
+ if( pProp && nType == nUTFAtom )
+ {
+ OString aWMName( reinterpret_cast<char*>(pProp) );
+ if (
+ (aWMName.equalsIgnoreAsciiCase("gnome-shell")) ||
+ (aWMName.equalsIgnoreAsciiCase("gnome-panel"))
+ )
+ {
+ ret = true;
+ }
+ }
+ if( pProp )
+ XFree( pProp );
+ }
+ XFree( pChildren );
+ }
+ }
+
+ return ret;
+}
+
+static bool is_plasma5_desktop()
+{
+ static const char* pFullVersion = getenv("KDE_FULL_SESSION");
+ static const char* pSessionVersion = getenv("KDE_SESSION_VERSION");
+ return pFullVersion && pSessionVersion && (0 == strcmp(pSessionVersion, "5"));
+}
+
+extern "C"
+{
+
+DESKTOP_DETECTOR_PUBLIC DesktopType get_desktop_environment()
+{
+ static const char *pOverride = getenv( "OOO_FORCE_DESKTOP" );
+
+ if ( pOverride && *pOverride )
+ {
+ OString aOver( pOverride );
+
+ if ( aOver.equalsIgnoreAsciiCase( "lxqt" ) )
+ return DESKTOP_LXQT;
+ if (aOver.equalsIgnoreAsciiCase("plasma5") || aOver.equalsIgnoreAsciiCase("plasma"))
+ return DESKTOP_PLASMA5;
+ if ( aOver.equalsIgnoreAsciiCase( "gnome" ) )
+ return DESKTOP_GNOME;
+ if ( aOver.equalsIgnoreAsciiCase( "gnome-wayland" ) )
+ return DESKTOP_GNOME;
+ if ( aOver.equalsIgnoreAsciiCase( "unity" ) )
+ return DESKTOP_UNITY;
+ if ( aOver.equalsIgnoreAsciiCase( "xfce" ) )
+ return DESKTOP_XFCE;
+ if ( aOver.equalsIgnoreAsciiCase( "mate" ) )
+ return DESKTOP_MATE;
+ if ( aOver.equalsIgnoreAsciiCase( "none" ) )
+ return DESKTOP_UNKNOWN;
+ }
+
+ OUString plugin;
+ rtl::Bootstrap::get("SAL_USE_VCLPLUGIN", plugin);
+
+ if (plugin == "svp")
+ return DESKTOP_NONE;
+
+ const char *pDesktop = getenv( "XDG_CURRENT_DESKTOP" );
+ if ( pDesktop )
+ {
+ OString aCurrentDesktop( pDesktop, strlen( pDesktop ) );
+
+ //it may be separated by colon ( e.g. unity:unity7:ubuntu )
+ std::vector<OUString> aSplitCurrentDesktop = comphelper::string::split(
+ OStringToOUString( aCurrentDesktop, RTL_TEXTENCODING_UTF8), ':');
+ for (const auto& rCurrentDesktopStr : aSplitCurrentDesktop)
+ {
+ if ( rCurrentDesktopStr.equalsIgnoreAsciiCase( "unity" ) )
+ return DESKTOP_UNITY;
+ else if ( rCurrentDesktopStr.equalsIgnoreAsciiCase( "gnome" ) )
+ return DESKTOP_GNOME;
+ else if ( rCurrentDesktopStr.equalsIgnoreAsciiCase( "lxqt" ) )
+ return DESKTOP_LXQT;
+ }
+ }
+
+ const char *pSession = getenv( "DESKTOP_SESSION" );
+ OString aDesktopSession;
+ if ( pSession )
+ aDesktopSession = OString( pSession, strlen( pSession ) );
+
+ // fast environment variable checks
+ if ( aDesktopSession.equalsIgnoreAsciiCase( "gnome" ) )
+ return DESKTOP_GNOME;
+ else if ( aDesktopSession.equalsIgnoreAsciiCase( "gnome-wayland" ) )
+ return DESKTOP_GNOME;
+ else if ( aDesktopSession.equalsIgnoreAsciiCase( "mate" ) )
+ return DESKTOP_MATE;
+ else if ( aDesktopSession.equalsIgnoreAsciiCase( "xfce" ) )
+ return DESKTOP_XFCE;
+ else if ( aDesktopSession.equalsIgnoreAsciiCase( "lxqt" ) )
+ return DESKTOP_LXQT;
+
+ if (is_plasma5_desktop())
+ return DESKTOP_PLASMA5;
+
+ // tdf#121275 if we still can't tell, and WAYLAND_DISPLAY
+ // is set, default to gtk3
+ const char* pWaylandStr = getenv("WAYLAND_DISPLAY");
+ if (pWaylandStr && *pWaylandStr)
+ return DESKTOP_GNOME;
+
+ // these guys can be slower, with X property fetches,
+ // round-trips etc. and so are done later.
+
+ // get display to connect to
+ const char* pDisplayStr = getenv( "DISPLAY" );
+
+ int nParams = rtl_getAppCommandArgCount();
+ OUString aParam;
+ OString aBParm;
+ for( int i = 0; i < nParams; i++ )
+ {
+ rtl_getAppCommandArg( i, &aParam.pData );
+ if( i < nParams-1 && (aParam == "-display" || aParam == "--display" ) )
+ {
+ rtl_getAppCommandArg( i+1, &aParam.pData );
+ aBParm = OUStringToOString( aParam, osl_getThreadTextEncoding() );
+ pDisplayStr = aBParm.getStr();
+ break;
+ }
+ }
+
+ // no server at all
+ if( ! pDisplayStr || !*pDisplayStr )
+ return DESKTOP_NONE;
+
+
+ /* #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();
+
+ Display* pDisplay = XOpenDisplay( pDisplayStr );
+ if( pDisplay == nullptr )
+ return DESKTOP_NONE;
+
+ DesktopType ret;
+ if ( is_gnome_desktop( pDisplay ) )
+ ret = DESKTOP_GNOME;
+ else
+ ret = DESKTOP_UNKNOWN;
+
+ XCloseDisplay( pDisplay );
+
+ return ret;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_clipboard.cxx b/vcl/unx/generic/dtrans/X11_clipboard.cxx
new file mode 100644
index 000000000..9b50eff8d
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_clipboard.cxx
@@ -0,0 +1,231 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <X11/Xatom.h>
+#include "X11_clipboard.hxx"
+#include "X11_transferable.hxx"
+#include <com/sun/star/datatransfer/clipboard/RenderingCapabilities.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <rtl/ref.hxx>
+#include <sal/log.hxx>
+
+#if OSL_DEBUG_LEVEL > 1
+#include <stdio.h>
+#endif
+
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::datatransfer::clipboard;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::awt;
+using namespace cppu;
+using namespace osl;
+using namespace x11;
+
+X11Clipboard::X11Clipboard( SelectionManager& rManager, Atom aSelection ) :
+ ::cppu::WeakComponentImplHelper<
+ css::datatransfer::clipboard::XSystemClipboard,
+ css::lang::XServiceInfo
+ >( rManager.getMutex() ),
+
+ m_xSelectionManager( &rManager ),
+ m_aSelection( aSelection )
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "creating instance of X11Clipboard (this="
+ << this << ").");
+#endif
+}
+
+css::uno::Reference<css::datatransfer::clipboard::XClipboard>
+X11Clipboard::create( SelectionManager& rManager, Atom aSelection )
+{
+ rtl::Reference<X11Clipboard> cb(new X11Clipboard(rManager, aSelection));
+ if( aSelection != None )
+ {
+ rManager.registerHandler(aSelection, *cb);
+ }
+ else
+ {
+ rManager.registerHandler(XA_PRIMARY, *cb);
+ rManager.registerHandler(rManager.getAtom("CLIPBOARD"), *cb);
+ }
+ return cb;
+}
+
+X11Clipboard::~X11Clipboard()
+{
+ MutexGuard aGuard( *Mutex::getGlobalMutex() );
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "shutting down instance of X11Clipboard (this="
+ << this
+ << ", Selection=\""
+ << m_xSelectionManager->getString( m_aSelection )
+ << "\").");
+#endif
+
+ if( m_aSelection != None )
+ m_xSelectionManager->deregisterHandler( m_aSelection );
+ else
+ {
+ m_xSelectionManager->deregisterHandler( XA_PRIMARY );
+ m_xSelectionManager->deregisterHandler( m_xSelectionManager->getAtom( "CLIPBOARD" ) );
+ }
+}
+
+void X11Clipboard::fireChangedContentsEvent()
+{
+ ClearableMutexGuard aGuard( m_xSelectionManager->getMutex() );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "X11Clipboard::fireChangedContentsEvent for "
+ << m_xSelectionManager->getString( m_aSelection )
+ << " (" << m_aListeners.size() << " listeners).");
+#endif
+ ::std::vector< Reference< XClipboardListener > > listeners( m_aListeners );
+ aGuard.clear();
+
+ ClipboardEvent aEvent( static_cast<OWeakObject*>(this), m_aContents);
+ for (auto const& listener : listeners)
+ {
+ if( listener.is() )
+ listener->changedContents(aEvent);
+ }
+}
+
+void X11Clipboard::clearContents()
+{
+ ClearableMutexGuard aGuard(m_xSelectionManager->getMutex());
+ // protect against deletion during outside call
+ Reference< XClipboard > xThis( static_cast<XClipboard*>(this));
+ // copy member references on stack so they can be called
+ // without having the mutex
+ Reference< XClipboardOwner > xOwner( m_aOwner );
+ Reference< XTransferable > xKeepAlive( m_aContents );
+ // clear members
+ m_aOwner.clear();
+ m_aContents.clear();
+
+ // release the mutex
+ aGuard.clear();
+
+ // inform previous owner of lost ownership
+ if ( xOwner.is() )
+ xOwner->lostOwnership(xThis, m_aContents);
+}
+
+Reference< XTransferable > SAL_CALL X11Clipboard::getContents()
+{
+ MutexGuard aGuard(m_xSelectionManager->getMutex());
+
+ if( ! m_aContents.is() )
+ m_aContents = new X11Transferable( SelectionManager::get(), m_aSelection );
+ return m_aContents;
+}
+
+void SAL_CALL X11Clipboard::setContents(
+ const Reference< XTransferable >& xTrans,
+ const Reference< XClipboardOwner >& xClipboardOwner )
+{
+ // remember old values for callbacks before setting the new ones.
+ ClearableMutexGuard aGuard(m_xSelectionManager->getMutex());
+
+ Reference< XClipboardOwner > oldOwner( m_aOwner );
+ m_aOwner = xClipboardOwner;
+
+ Reference< XTransferable > oldContents( m_aContents );
+ m_aContents = xTrans;
+
+ aGuard.clear();
+
+ // for now request ownership for both selections
+ if( m_aSelection != None )
+ m_xSelectionManager->requestOwnership( m_aSelection );
+ else
+ {
+ m_xSelectionManager->requestOwnership( XA_PRIMARY );
+ m_xSelectionManager->requestOwnership( m_xSelectionManager->getAtom( "CLIPBOARD" ) );
+ }
+
+ // notify old owner on loss of ownership
+ if( oldOwner.is() )
+ oldOwner->lostOwnership(static_cast < XClipboard * > (this), oldContents);
+
+ // notify all listeners on content changes
+ fireChangedContentsEvent();
+}
+
+OUString SAL_CALL X11Clipboard::getName()
+{
+ return m_xSelectionManager->getString( m_aSelection );
+}
+
+sal_Int8 SAL_CALL X11Clipboard::getRenderingCapabilities()
+{
+ return RenderingCapabilities::Delayed;
+}
+
+void SAL_CALL X11Clipboard::addClipboardListener( const Reference< XClipboardListener >& listener )
+{
+ MutexGuard aGuard( m_xSelectionManager->getMutex() );
+ m_aListeners.push_back( listener );
+}
+
+void SAL_CALL X11Clipboard::removeClipboardListener( const Reference< XClipboardListener >& listener )
+{
+ MutexGuard aGuard( m_xSelectionManager->getMutex() );
+ m_aListeners.erase( std::remove(m_aListeners.begin(), m_aListeners.end(), listener), m_aListeners.end() );
+}
+
+Reference< XTransferable > X11Clipboard::getTransferable()
+{
+ return getContents();
+}
+
+void X11Clipboard::clearTransferable()
+{
+ clearContents();
+}
+
+void X11Clipboard::fireContentsChanged()
+{
+ fireChangedContentsEvent();
+}
+
+Reference< XInterface > X11Clipboard::getReference() noexcept
+{
+ return Reference< XInterface >( static_cast< OWeakObject* >(this) );
+}
+
+OUString SAL_CALL X11Clipboard::getImplementationName( )
+{
+ return X11_CLIPBOARD_IMPLEMENTATION_NAME;
+}
+
+sal_Bool SAL_CALL X11Clipboard::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SAL_CALL X11Clipboard::getSupportedServiceNames( )
+{
+ return X11Clipboard_getSupportedServiceNames();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_clipboard.hxx b/vcl/unx/generic/dtrans/X11_clipboard.hxx
new file mode 100644
index 000000000..6a2ab732a
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_clipboard.hxx
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "X11_selection.hxx"
+
+#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
+#include <cppuhelper/compbase.hxx>
+
+inline constexpr OUStringLiteral X11_CLIPBOARD_IMPLEMENTATION_NAME = u"com.sun.star.datatransfer.X11ClipboardSupport";
+
+namespace x11 {
+
+ class X11Clipboard :
+ public ::cppu::WeakComponentImplHelper <
+ css::datatransfer::clipboard::XSystemClipboard,
+ css::lang::XServiceInfo
+ >,
+ public SelectionAdaptor
+ {
+ css::uno::Reference< css::datatransfer::XTransferable > m_aContents;
+ css::uno::Reference< css::datatransfer::clipboard::XClipboardOwner > m_aOwner;
+
+ rtl::Reference<SelectionManager> m_xSelectionManager;
+ ::std::vector< css::uno::Reference< css::datatransfer::clipboard::XClipboardListener > > m_aListeners;
+ Atom m_aSelection;
+
+ X11Clipboard( SelectionManager& rManager, Atom aSelection );
+
+ friend class SelectionManager;
+
+ void fireChangedContentsEvent();
+ void clearContents();
+
+ public:
+
+ static css::uno::Reference<css::datatransfer::clipboard::XClipboard>
+ create( SelectionManager& rManager, Atom aSelection );
+
+ virtual ~X11Clipboard() override;
+
+ /*
+ * XServiceInfo
+ */
+
+ virtual OUString SAL_CALL getImplementationName( ) override;
+
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override;
+
+ /*
+ * XClipboard
+ */
+
+ virtual css::uno::Reference< css::datatransfer::XTransferable > SAL_CALL getContents() override;
+
+ virtual void SAL_CALL setContents(
+ const css::uno::Reference< css::datatransfer::XTransferable >& xTrans,
+ const css::uno::Reference< css::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner ) override;
+
+ virtual OUString SAL_CALL getName() override;
+
+ /*
+ * XClipboardEx
+ */
+
+ virtual sal_Int8 SAL_CALL getRenderingCapabilities() override;
+
+ /*
+ * XClipboardNotifier
+ */
+ virtual void SAL_CALL addClipboardListener(
+ const css::uno::Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
+
+ virtual void SAL_CALL removeClipboardListener(
+ const css::uno::Reference< css::datatransfer::clipboard::XClipboardListener >& listener ) override;
+
+ /*
+ * SelectionAdaptor
+ */
+ virtual css::uno::Reference< css::datatransfer::XTransferable > getTransferable() override;
+ virtual void clearTransferable() override;
+ virtual void fireContentsChanged() override;
+ virtual css::uno::Reference< css::uno::XInterface > getReference() noexcept override;
+ };
+
+ css::uno::Sequence< OUString > X11Clipboard_getSupportedServiceNames();
+ css::uno::Reference< css::uno::XInterface > SAL_CALL X11Clipboard_createInstance(
+ const css::uno::Reference< css::lang::XMultiServiceFactory > & xMultiServiceFactory);
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_dndcontext.cxx b/vcl/unx/generic/dtrans/X11_dndcontext.cxx
new file mode 100644
index 000000000..638c47387
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_dndcontext.cxx
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "X11_dndcontext.hxx"
+#include "X11_selection.hxx"
+
+using namespace cppu;
+using namespace x11;
+
+/*
+ * DropTargetDropContext
+ */
+
+DropTargetDropContext::DropTargetDropContext(
+ ::Window aDropWindow,
+ SelectionManager& rManager ) :
+ m_aDropWindow( aDropWindow ),
+ m_xManager( &rManager )
+{
+}
+
+DropTargetDropContext::~DropTargetDropContext()
+{
+}
+
+void DropTargetDropContext::acceptDrop( sal_Int8 dragOperation )
+{
+ m_xManager->accept( dragOperation, m_aDropWindow );
+}
+
+void DropTargetDropContext::rejectDrop()
+{
+ m_xManager->reject( m_aDropWindow );
+}
+
+void DropTargetDropContext::dropComplete( sal_Bool success )
+{
+ m_xManager->dropComplete( success, m_aDropWindow );
+}
+
+/*
+ * DropTargetDragContext
+ */
+
+DropTargetDragContext::DropTargetDragContext(
+ ::Window aDropWindow,
+ SelectionManager& rManager ) :
+ m_aDropWindow( aDropWindow ),
+ m_xManager( &rManager )
+{
+}
+
+DropTargetDragContext::~DropTargetDragContext()
+{
+}
+
+void DropTargetDragContext::acceptDrag( sal_Int8 dragOperation )
+{
+ m_xManager->accept( dragOperation, m_aDropWindow );
+}
+
+void DropTargetDragContext::rejectDrag()
+{
+ m_xManager->reject( m_aDropWindow );
+}
+
+/*
+ * DragSourceContext
+ */
+
+DragSourceContext::DragSourceContext(
+ ::Window aDropWindow,
+ SelectionManager& rManager ) :
+ m_aDropWindow( aDropWindow ),
+ m_xManager( &rManager )
+{
+}
+
+DragSourceContext::~DragSourceContext()
+{
+}
+
+sal_Int32 DragSourceContext::getCurrentCursor()
+{
+ return m_xManager->getCurrentCursor();
+}
+
+void DragSourceContext::setCursor( sal_Int32 cursorId )
+{
+ m_xManager->setCursor( cursorId, m_aDropWindow );
+}
+
+void DragSourceContext::setImage( sal_Int32 )
+{
+}
+
+void DragSourceContext::transferablesFlavorsChanged()
+{
+ m_xManager->transferablesFlavorsChanged();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_dndcontext.hxx b/vcl/unx/generic/dtrans/X11_dndcontext.hxx
new file mode 100644
index 000000000..71283ea64
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_dndcontext.hxx
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/datatransfer/dnd/XDragSourceContext.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTargetDropContext.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTargetDragContext.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <rtl/ref.hxx>
+
+#include <X11/X.h>
+
+namespace x11 {
+
+ class SelectionManager;
+
+ class DropTargetDropContext :
+ public ::cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDropContext>
+ {
+ ::Window m_aDropWindow;
+ rtl::Reference<SelectionManager> m_xManager;
+ public:
+ DropTargetDropContext( ::Window, SelectionManager& );
+ virtual ~DropTargetDropContext() override;
+
+ // XDropTargetDropContext
+ virtual void SAL_CALL acceptDrop( sal_Int8 dragOperation ) override;
+ virtual void SAL_CALL rejectDrop() override;
+ virtual void SAL_CALL dropComplete( sal_Bool success ) override;
+ };
+
+ class DropTargetDragContext :
+ public ::cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDragContext>
+ {
+ ::Window m_aDropWindow;
+ rtl::Reference<SelectionManager> m_xManager;
+ public:
+ DropTargetDragContext( ::Window, SelectionManager& );
+ virtual ~DropTargetDragContext() override;
+
+ // XDropTargetDragContext
+ virtual void SAL_CALL acceptDrag( sal_Int8 dragOperation ) override;
+ virtual void SAL_CALL rejectDrag() override;
+ };
+
+ class DragSourceContext :
+ public ::cppu::WeakImplHelper<css::datatransfer::dnd::XDragSourceContext>
+ {
+ ::Window m_aDropWindow;
+ rtl::Reference<SelectionManager> m_xManager;
+ public:
+ DragSourceContext( ::Window, SelectionManager& );
+ virtual ~DragSourceContext() override;
+
+ // XDragSourceContext
+ virtual sal_Int32 SAL_CALL getCurrentCursor() override;
+ virtual void SAL_CALL setCursor( sal_Int32 cursorId ) override;
+ virtual void SAL_CALL setImage( sal_Int32 imageId ) override;
+ virtual void SAL_CALL transferablesFlavorsChanged() override;
+ };
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_droptarget.cxx b/vcl/unx/generic/dtrans/X11_droptarget.cxx
new file mode 100644
index 000000000..7ce291902
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_droptarget.cxx
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cppuhelper/supportsservice.hxx>
+#include "X11_selection.hxx"
+
+using namespace x11;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::awt;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::datatransfer::dnd;
+
+DropTarget::DropTarget() :
+ ::cppu::WeakComponentImplHelper<
+ XDropTarget,
+ XInitialization,
+ XServiceInfo
+ >( m_aMutex ),
+ m_bActive( false ),
+ m_nDefaultActions( 0 ),
+ m_aTargetWindow( None )
+{
+}
+
+DropTarget::~DropTarget()
+{
+ if( m_xSelectionManager.is() )
+ m_xSelectionManager->deregisterDropTarget( m_aTargetWindow );
+}
+
+void DropTarget::initialize( const Sequence< Any >& arguments )
+{
+ if( arguments.getLength() <= 1 )
+ return;
+
+ OUString aDisplayName;
+ Reference< XDisplayConnection > xConn;
+ arguments.getConstArray()[0] >>= xConn;
+ if( xConn.is() )
+ {
+ Any aIdentifier;
+ aIdentifier >>= aDisplayName;
+ }
+
+ m_xSelectionManager = &SelectionManager::get( aDisplayName );
+ m_xSelectionManager->initialize( arguments );
+
+ if( m_xSelectionManager->getDisplay() ) // #136582# sanity check
+ {
+ sal_IntPtr aWindow = None;
+ arguments.getConstArray()[1] >>= aWindow;
+ m_xSelectionManager->registerDropTarget( aWindow, this );
+ m_aTargetWindow = aWindow;
+ m_bActive = true;
+ }
+}
+
+void DropTarget::addDropTargetListener( const Reference< XDropTargetListener >& xListener )
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
+
+ m_aListeners.push_back( xListener );
+}
+
+void DropTarget::removeDropTargetListener( const Reference< XDropTargetListener >& xListener )
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
+
+ m_aListeners.erase( std::remove(m_aListeners.begin(), m_aListeners.end(), xListener), m_aListeners.end() );
+}
+
+sal_Bool DropTarget::isActive()
+{
+ return m_bActive;
+}
+
+void DropTarget::setActive( sal_Bool active )
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
+
+ m_bActive = active;
+}
+
+sal_Int8 DropTarget::getDefaultActions()
+{
+ return m_nDefaultActions;
+}
+
+void DropTarget::setDefaultActions( sal_Int8 actions )
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
+
+ m_nDefaultActions = actions;
+}
+
+void DropTarget::drop( const DropTargetDropEvent& dtde ) noexcept
+{
+ osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
+ std::vector< Reference< XDropTargetListener > > aListeners( m_aListeners );
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ {
+ listener->drop(dtde);
+ }
+}
+
+void DropTarget::dragEnter( const DropTargetDragEnterEvent& dtde ) noexcept
+{
+ osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
+ std::vector< Reference< XDropTargetListener > > aListeners( m_aListeners );
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ {
+ listener->dragEnter(dtde);
+ }
+}
+
+void DropTarget::dragExit( const DropTargetEvent& dte ) noexcept
+{
+ osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
+ std::vector< Reference< XDropTargetListener > > aListeners( m_aListeners );
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ {
+ listener->dragExit(dte);
+ }
+}
+
+void DropTarget::dragOver( const DropTargetDragEvent& dtde ) noexcept
+{
+ osl::ClearableGuard< ::osl::Mutex > aGuard( m_aMutex );
+ std::vector< Reference< XDropTargetListener > > aListeners( m_aListeners );
+ aGuard.clear();
+
+ for (auto const& listener : aListeners)
+ {
+ listener->dragOver(dtde);
+ }
+}
+
+// XServiceInfo
+OUString DropTarget::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.XdndDropTarget";
+}
+
+sal_Bool DropTarget::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > DropTarget::getSupportedServiceNames()
+{
+ return Xdnd_dropTarget_getSupportedServiceNames();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_selection.cxx b/vcl/unx/generic/dtrans/X11_selection.cxx
new file mode 100644
index 000000000..d35b496bf
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_selection.cxx
@@ -0,0 +1,4156 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <cstdlib>
+
+#include <unx/saldisp.hxx>
+
+#include <unistd.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/XKBlib.h>
+#include <X11/Xatom.h>
+#include <X11/keysym.h>
+
+#include <poll.h>
+
+#include <sal/macros.h>
+
+#include "X11_selection.hxx"
+#include "X11_clipboard.hxx"
+#include "X11_transferable.hxx"
+#include "X11_dndcontext.hxx"
+#include "bmp.hxx"
+
+#include <vcl/svapp.hxx>
+#include <o3tl/string_view.hxx>
+
+// pointer bitmaps
+#include "copydata_curs.h"
+#include "copydata_mask.h"
+#include "movedata_curs.h"
+#include "movedata_mask.h"
+#include "linkdata_curs.h"
+#include "linkdata_mask.h"
+#include "nodrop_curs.h"
+#include "nodrop_mask.h"
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/awt/MouseEvent.hpp>
+#include <com/sun/star/awt/MouseButton.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <rtl/tencinfo.h>
+#include <rtl/ustrbuf.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/solarmutex.hxx>
+
+#include <cppuhelper/supportsservice.hxx>
+#include <algorithm>
+
+constexpr auto DRAG_EVENT_MASK = ButtonPressMask |
+ ButtonReleaseMask |
+ PointerMotionMask |
+ EnterWindowMask |
+ LeaveWindowMask;
+
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::datatransfer::dnd;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::awt;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::frame;
+using namespace cppu;
+
+using namespace x11;
+
+// stubs to satisfy solaris compiler's rather rigid linking warning
+extern "C"
+{
+ static void call_SelectionManager_run( void * pMgr )
+ {
+ SelectionManager::run( pMgr );
+ }
+
+ static void call_SelectionManager_runDragExecute( void * pMgr )
+ {
+ osl_setThreadName("SelectionManager::runDragExecute()");
+ SelectionManager::runDragExecute( pMgr );
+ }
+}
+
+const tools::Long nXdndProtocolRevision = 5;
+
+namespace {
+
+// mapping between mime types (or what the office thinks of mime types)
+// and X convention types
+struct NativeTypeEntry
+{
+ Atom nAtom;
+ const char* pType; // Mime encoding on our side
+ const char* pNativeType; // string corresponding to nAtom for the case of nAtom being uninitialized
+ int nFormat; // the corresponding format
+};
+
+}
+
+// the convention for Xdnd is mime types as specified by the corresponding
+// RFC's with the addition that text/plain without charset tag contains iso8859-1
+// sadly some applications (e.g. gtk) do not honor the mimetype only rule,
+// so for compatibility add UTF8_STRING
+static NativeTypeEntry aXdndConversionTab[] =
+{
+ { 0, "text/plain;charset=iso8859-1", "text/plain", 8 },
+ { 0, "text/plain;charset=utf-8", "UTF8_STRING", 8 }
+};
+
+// for clipboard and primary selections there is only a convention for text
+// that the encoding name of the text is taken as type in all capitalized letters
+static NativeTypeEntry aNativeConversionTab[] =
+{
+ { 0, "text/plain;charset=utf-16", "ISO10646-1", 16 },
+ { 0, "text/plain;charset=utf-8", "UTF8_STRING", 8 },
+ { 0, "text/plain;charset=utf-8", "UTF-8", 8 },
+ { 0, "text/plain;charset=utf-8", "text/plain;charset=UTF-8", 8 },
+ // ISO encodings
+ { 0, "text/plain;charset=iso8859-2", "ISO8859-2", 8 },
+ { 0, "text/plain;charset=iso8859-3", "ISO8859-3", 8 },
+ { 0, "text/plain;charset=iso8859-4", "ISO8859-4", 8 },
+ { 0, "text/plain;charset=iso8859-5", "ISO8859-5", 8 },
+ { 0, "text/plain;charset=iso8859-6", "ISO8859-6", 8 },
+ { 0, "text/plain;charset=iso8859-7", "ISO8859-7", 8 },
+ { 0, "text/plain;charset=iso8859-8", "ISO8859-8", 8 },
+ { 0, "text/plain;charset=iso8859-9", "ISO8859-9", 8 },
+ { 0, "text/plain;charset=iso8859-10", "ISO8859-10", 8 },
+ { 0, "text/plain;charset=iso8859-13", "ISO8859-13", 8 },
+ { 0, "text/plain;charset=iso8859-14", "ISO8859-14", 8 },
+ { 0, "text/plain;charset=iso8859-15", "ISO8859-15", 8 },
+ // asian encodings
+ { 0, "text/plain;charset=jisx0201.1976-0", "JISX0201.1976-0", 8 },
+ { 0, "text/plain;charset=jisx0208.1983-0", "JISX0208.1983-0", 8 },
+ { 0, "text/plain;charset=jisx0208.1990-0", "JISX0208.1990-0", 8 },
+ { 0, "text/plain;charset=jisx0212.1990-0", "JISX0212.1990-0", 8 },
+ { 0, "text/plain;charset=gb2312.1980-0", "GB2312.1980-0", 8 },
+ { 0, "text/plain;charset=ksc5601.1992-0", "KSC5601.1992-0", 8 },
+ // eastern european encodings
+ { 0, "text/plain;charset=koi8-r", "KOI8-R", 8 },
+ { 0, "text/plain;charset=koi8-u", "KOI8-U", 8 },
+ // String (== iso8859-1)
+ { XA_STRING, "text/plain;charset=iso8859-1", "STRING", 8 },
+ // special for compound text
+ { 0, "text/plain;charset=compound_text", "COMPOUND_TEXT", 8 },
+
+ // PIXMAP
+ { XA_PIXMAP, "image/bmp", "PIXMAP", 32 }
+};
+
+rtl_TextEncoding x11::getTextPlainEncoding( const OUString& rMimeType )
+{
+ rtl_TextEncoding aEncoding = RTL_TEXTENCODING_DONTKNOW;
+ OUString aMimeType( rMimeType.toAsciiLowerCase() );
+ sal_Int32 nIndex = 0;
+ if( o3tl::getToken(aMimeType, 0, ';', nIndex ) == u"text/plain" )
+ {
+ if( aMimeType.getLength() == 10 ) // only "text/plain"
+ aEncoding = RTL_TEXTENCODING_ISO_8859_1;
+ else
+ {
+ while( nIndex != -1 )
+ {
+ OUString aToken = aMimeType.getToken( 0, ';', nIndex );
+ sal_Int32 nPos = 0;
+ if( o3tl::getToken(aToken, 0, '=', nPos ) == u"charset" )
+ {
+ OString aEncToken = OUStringToOString( o3tl::getToken(aToken, 0, '=', nPos ), RTL_TEXTENCODING_ISO_8859_1 );
+ aEncoding = rtl_getTextEncodingFromUnixCharset( aEncToken.getStr() );
+ if( aEncoding == RTL_TEXTENCODING_DONTKNOW )
+ {
+ if( aEncToken.equalsIgnoreAsciiCase( "utf-8" ) )
+ aEncoding = RTL_TEXTENCODING_UTF8;
+ }
+ if( aEncoding != RTL_TEXTENCODING_DONTKNOW )
+ break;
+ }
+ }
+ }
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN_IF(aEncoding == RTL_TEXTENCODING_DONTKNOW,
+ "vcl.unx.dtrans", "getTextPlainEncoding( "
+ << rMimeType << " ) failed.");
+#endif
+ return aEncoding;
+}
+
+std::unordered_map< OUString, SelectionManager* >& SelectionManager::getInstances()
+{
+ static std::unordered_map< OUString, SelectionManager* > aInstances;
+ return aInstances;
+}
+
+SelectionManager::SelectionManager() :
+ m_nIncrementalThreshold( 15*1024 ),
+ m_pDisplay( nullptr ),
+ m_aThread( nullptr ),
+ m_aDragExecuteThread( nullptr ),
+ m_aWindow( None ),
+ m_nSelectionTimeout( 0 ),
+ m_nSelectionTimestamp( CurrentTime ),
+ m_bDropEnterSent( true ),
+ m_aCurrentDropWindow( None ),
+ m_nDropTime( None ),
+ m_nLastDropAction( 0 ),
+ m_nLastX( 0 ),
+ m_nLastY( 0 ),
+ m_bDropWaitingForCompletion( false ),
+ m_aDropWindow( None ),
+ m_aDropProxy( None ),
+ m_aDragSourceWindow( None ),
+ m_nLastDragX( 0 ),
+ m_nLastDragY( 0 ),
+ m_nNoPosX( 0 ),
+ m_nNoPosY( 0 ),
+ m_nNoPosWidth( 0 ),
+ m_nNoPosHeight( 0 ),
+ m_nDragButton( 0 ),
+ m_nUserDragAction( 0 ),
+ m_nTargetAcceptAction( 0 ),
+ m_nSourceActions( 0 ),
+ m_bLastDropAccepted( false ),
+ m_bDropSuccess( false ),
+ m_bDropSent( false ),
+ m_nDropTimeout( 0 ),
+ m_bWaitingForPrimaryConversion( false ),
+ m_aMoveCursor( None ),
+ m_aCopyCursor( None ),
+ m_aLinkCursor( None ),
+ m_aNoneCursor( None ),
+ m_aCurrentCursor( None ),
+ m_nCurrentProtocolVersion( nXdndProtocolRevision ),
+ m_nTARGETSAtom( None ),
+ m_nTIMESTAMPAtom( None ),
+ m_nTEXTAtom( None ),
+ m_nINCRAtom( None ),
+ m_nCOMPOUNDAtom( None ),
+ m_nMULTIPLEAtom( None ),
+ m_nImageBmpAtom( None ),
+ m_nXdndAware( None ),
+ m_nXdndEnter( None ),
+ m_nXdndLeave( None ),
+ m_nXdndPosition( None ),
+ m_nXdndStatus( None ),
+ m_nXdndDrop( None ),
+ m_nXdndFinished( None ),
+ m_nXdndSelection( None ),
+ m_nXdndTypeList( None ),
+ m_nXdndProxy( None ),
+ m_nXdndActionCopy( None ),
+ m_nXdndActionMove( None ),
+ m_nXdndActionLink( None ),
+ m_nXdndActionAsk( None ),
+ m_bShutDown( false )
+{
+ memset(&m_aDropEnterEvent, 0, sizeof(m_aDropEnterEvent));
+ m_EndThreadPipe[0] = 0;
+ m_EndThreadPipe[1] = 0;
+ m_aDragRunning.reset();
+}
+
+Cursor SelectionManager::createCursor( const unsigned char* pPointerData, const unsigned char* pMaskData, int width, int height, int hotX, int hotY )
+{
+ Pixmap aPointer;
+ Pixmap aMask;
+ XColor aBlack, aWhite;
+
+ aBlack.pixel = BlackPixel( m_pDisplay, 0 );
+ aBlack.red = aBlack.green = aBlack.blue = 0;
+ aBlack.flags = DoRed | DoGreen | DoBlue;
+
+ aWhite.pixel = WhitePixel( m_pDisplay, 0 );
+ aWhite.red = aWhite.green = aWhite.blue = 0xffff;
+ aWhite.flags = DoRed | DoGreen | DoBlue;
+
+ aPointer =
+ XCreateBitmapFromData( m_pDisplay,
+ m_aWindow,
+ reinterpret_cast<const char*>(pPointerData),
+ width,
+ height );
+ aMask
+ = XCreateBitmapFromData( m_pDisplay,
+ m_aWindow,
+ reinterpret_cast<const char*>(pMaskData),
+ width,
+ height );
+ Cursor aCursor =
+ XCreatePixmapCursor( m_pDisplay, aPointer, aMask,
+ &aBlack, &aWhite,
+ hotX,
+ hotY );
+ XFreePixmap( m_pDisplay, aPointer );
+ XFreePixmap( m_pDisplay, aMask );
+
+ return aCursor;
+}
+
+void SelectionManager::initialize( const Sequence< Any >& arguments )
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if( ! m_xDisplayConnection.is() )
+ {
+ /*
+ * first argument must be a css::awt::XDisplayConnection
+ * from this we will get the XEvents of the vcl event loop by
+ * registering us as XEventHandler on it.
+ *
+ * implementor's note:
+ * FIXME:
+ * finally the clipboard and XDND service is back in the module it belongs
+ * now cleanup and sharing of resources with the normal vcl event loop
+ * needs to be added. The display used would be that of the normal event loop
+ * and synchronization should be done via the SolarMutex.
+ */
+ if( arguments.hasElements() )
+ arguments.getConstArray()[0] >>= m_xDisplayConnection;
+ if( ! m_xDisplayConnection.is() )
+ {
+ }
+ else
+ m_xDisplayConnection->addEventHandler( Any(), this, ~0 );
+ }
+
+ if( m_pDisplay )
+ return;
+
+ OUString aUDisplay;
+ if( m_xDisplayConnection.is() )
+ {
+ Any aIdentifier = m_xDisplayConnection->getIdentifier();
+ aIdentifier >>= aUDisplay;
+ }
+
+ OString aDisplayName( OUStringToOString( aUDisplay, RTL_TEXTENCODING_ISO_8859_1 ) );
+
+ m_pDisplay = XOpenDisplay( aDisplayName.isEmpty() ? nullptr : aDisplayName.getStr());
+
+ if( !m_pDisplay )
+ return;
+
+#ifdef SYNCHRONIZE
+ XSynchronize( m_pDisplay, True );
+#endif
+ // special targets
+ m_nTARGETSAtom = getAtom( "TARGETS" );
+ m_nTIMESTAMPAtom = getAtom( "TIMESTAMP" );
+ m_nTEXTAtom = getAtom( "TEXT" );
+ m_nINCRAtom = getAtom( "INCR" );
+ m_nCOMPOUNDAtom = getAtom( "COMPOUND_TEXT" );
+ m_nMULTIPLEAtom = getAtom( "MULTIPLE" );
+ m_nImageBmpAtom = getAtom( "image/bmp" );
+
+ // Atoms for Xdnd protocol
+ m_nXdndAware = getAtom( "XdndAware" );
+ m_nXdndEnter = getAtom( "XdndEnter" );
+ m_nXdndLeave = getAtom( "XdndLeave" );
+ m_nXdndPosition = getAtom( "XdndPosition" );
+ m_nXdndStatus = getAtom( "XdndStatus" );
+ m_nXdndDrop = getAtom( "XdndDrop" );
+ m_nXdndFinished = getAtom( "XdndFinished" );
+ m_nXdndSelection = getAtom( "XdndSelection" );
+ m_nXdndTypeList = getAtom( "XdndTypeList" );
+ m_nXdndProxy = getAtom( "XdndProxy" );
+ m_nXdndActionCopy = getAtom( "XdndActionCopy" );
+ m_nXdndActionMove = getAtom( "XdndActionMove" );
+ m_nXdndActionLink = getAtom( "XdndActionLink" );
+ m_nXdndActionAsk = getAtom( "XdndActionAsk" );
+
+ // initialize map with member none
+ m_aAtomToString[ 0 ]= "None";
+ m_aAtomToString[ XA_PRIMARY ] = "PRIMARY";
+
+ // create a (invisible) message window
+ m_aWindow = XCreateSimpleWindow( m_pDisplay, DefaultRootWindow( m_pDisplay ),
+ 10, 10, 10, 10, 0, 0, 1 );
+
+ // initialize threshold for incremental transfers
+ // ICCCM says it should be smaller that the max request size
+ // which in turn is guaranteed to be at least 16k bytes
+ m_nIncrementalThreshold = XMaxRequestSize( m_pDisplay ) - 1024;
+
+ if( !m_aWindow )
+ return;
+
+ // initialize default cursors
+ m_aMoveCursor = createCursor( movedata_curs_bits,
+ movedata_mask_bits,
+ movedata_curs_width,
+ movedata_curs_height,
+ movedata_curs_x_hot,
+ movedata_curs_y_hot );
+ m_aCopyCursor = createCursor( copydata_curs_bits,
+ copydata_mask_bits,
+ copydata_curs_width,
+ copydata_curs_height,
+ copydata_curs_x_hot,
+ copydata_curs_y_hot );
+ m_aLinkCursor = createCursor( linkdata_curs_bits,
+ linkdata_mask_bits,
+ linkdata_curs_width,
+ linkdata_curs_height,
+ linkdata_curs_x_hot,
+ linkdata_curs_y_hot );
+ m_aNoneCursor = createCursor( nodrop_curs_bits,
+ nodrop_mask_bits,
+ nodrop_curs_width,
+ nodrop_curs_height,
+ nodrop_curs_x_hot,
+ nodrop_curs_y_hot );
+
+ // just interested in SelectionClear/Notify/Request and PropertyChange
+ XSelectInput( m_pDisplay, m_aWindow, PropertyChangeMask );
+ // create the transferable for Drag operations
+ m_xDropTransferable = new X11Transferable( *this, m_nXdndSelection );
+ registerHandler( m_nXdndSelection, *this );
+
+ m_aThread = osl_createSuspendedThread( call_SelectionManager_run, this );
+ if( m_aThread )
+ osl_resumeThread( m_aThread );
+#if OSL_DEBUG_LEVEL > 1
+ else
+ SAL_WARN("vcl.unx.dtrans", "SelectionManager::initialize: "
+ << "creation of dispatch thread failed !.");
+#endif
+
+ if (pipe(m_EndThreadPipe) != 0) {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.unx.dtrans", "Failed to create endThreadPipe.");
+#endif
+ m_EndThreadPipe[0] = m_EndThreadPipe[1] = 0;
+ }
+}
+
+SelectionManager::~SelectionManager()
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "SelectionManager::~SelectionManager ("
+ << (m_pDisplay ? DisplayString(m_pDisplay) : "no display")
+ << ").");
+#endif
+ {
+ osl::MutexGuard aGuard( *osl::Mutex::getGlobalMutex() );
+
+ auto it = std::find_if(getInstances().begin(), getInstances().end(),
+ [&](const std::pair< OUString, SelectionManager* >& rInstance) { return rInstance.second == this; });
+ if( it != getInstances().end() )
+ getInstances().erase( it );
+ }
+
+ if( m_aThread )
+ {
+ osl_terminateThread( m_aThread );
+ osl_joinWithThread( m_aThread );
+ osl_destroyThread( m_aThread );
+ }
+
+ if( m_aDragExecuteThread )
+ {
+ osl_terminateThread( m_aDragExecuteThread );
+ osl_joinWithThread( m_aDragExecuteThread );
+ m_aDragExecuteThread = nullptr;
+ // thread handle is freed in dragDoDispatch()
+ }
+
+ osl::MutexGuard aGuard(m_aMutex);
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "shutting down SelectionManager.");
+#endif
+
+ if( !m_pDisplay )
+ return;
+
+ deregisterHandler( m_nXdndSelection );
+ // destroy message window
+ if( m_aWindow )
+ XDestroyWindow( m_pDisplay, m_aWindow );
+ // release cursors
+ if (m_aMoveCursor != None)
+ XFreeCursor(m_pDisplay, m_aMoveCursor);
+ if (m_aCopyCursor != None)
+ XFreeCursor(m_pDisplay, m_aCopyCursor);
+ if (m_aLinkCursor != None)
+ XFreeCursor(m_pDisplay, m_aLinkCursor);
+ if (m_aNoneCursor != None)
+ XFreeCursor(m_pDisplay, m_aNoneCursor);
+
+ // paranoia setting, the drag thread should have
+ // done that already
+ XUngrabPointer( m_pDisplay, CurrentTime );
+ XUngrabKeyboard( m_pDisplay, CurrentTime );
+
+ XCloseDisplay( m_pDisplay );
+}
+
+SelectionAdaptor* SelectionManager::getAdaptor( Atom selection )
+{
+ std::unordered_map< Atom, Selection* >::iterator it =
+ m_aSelections.find( selection );
+ return it != m_aSelections.end() ? it->second->m_pAdaptor : nullptr;
+}
+
+OUString SelectionManager::convertFromCompound( const char* pText, int nLen )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+ OUStringBuffer aRet;
+ if( nLen < 0 )
+ nLen = strlen( pText );
+
+ char** pTextList = nullptr;
+ int nTexts = 0;
+
+ XTextProperty aProp;
+ aProp.value = reinterpret_cast<unsigned char *>(const_cast<char *>(pText));
+ aProp.encoding = m_nCOMPOUNDAtom;
+ aProp.format = 8;
+ aProp.nitems = nLen;
+ XmbTextPropertyToTextList( m_pDisplay,
+ &aProp,
+ &pTextList,
+ &nTexts );
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ for( int i = 0; i < nTexts; i++ )
+ aRet.append(OStringToOUString( pTextList[i], aEncoding ));
+
+ if( pTextList )
+ XFreeStringList( pTextList );
+
+ return aRet.makeStringAndClear();
+}
+
+OString SelectionManager::convertToCompound( const OUString& rText )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+ XTextProperty aProp;
+ aProp.value = nullptr;
+ aProp.encoding = XA_STRING;
+ aProp.format = 8;
+ aProp.nitems = 0;
+
+ OString aRet( rText.getStr(), rText.getLength(), osl_getThreadTextEncoding() );
+ char* pT = const_cast<char*>(aRet.getStr());
+
+ XmbTextListToTextProperty( m_pDisplay,
+ &pT,
+ 1,
+ XCompoundTextStyle,
+ &aProp );
+ if( aProp.value )
+ {
+ aRet = reinterpret_cast<char*>(aProp.value);
+ XFree( aProp.value );
+#ifdef __sun
+ /*
+ * for currently unknown reasons XmbTextListToTextProperty on Solaris returns
+ * no data in ISO8859-n encodings (at least for n = 1, 15)
+ * in these encodings the directly converted text does the
+ * trick, also.
+ */
+ if( aRet.isEmpty() && !rText.isEmpty() )
+ aRet = OUStringToOString( rText, osl_getThreadTextEncoding() );
+#endif
+ }
+ else
+ aRet.clear();
+
+ return aRet;
+}
+
+bool SelectionManager::convertData(
+ const css::uno::Reference< XTransferable >& xTransferable,
+ Atom nType,
+ Atom nSelection,
+ int& rFormat,
+ Sequence< sal_Int8 >& rData )
+{
+ bool bSuccess = false;
+
+ if( ! xTransferable.is() )
+ return bSuccess;
+
+ try
+ {
+
+ DataFlavor aFlavor;
+ aFlavor.MimeType = convertTypeFromNative( nType, nSelection, rFormat );
+
+ sal_Int32 nIndex = 0;
+ if( o3tl::getToken(aFlavor.MimeType, 0, ';', nIndex ) == u"text/plain" )
+ {
+ if( o3tl::getToken(aFlavor.MimeType, 0, ';', nIndex ) == u"charset=utf-16" )
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ else
+ aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+ }
+ else
+ aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ if( xTransferable->isDataFlavorSupported( aFlavor ) )
+ {
+ Any aValue( xTransferable->getTransferData( aFlavor ) );
+ if( aValue.getValueTypeClass() == TypeClass_STRING )
+ {
+ OUString aString;
+ aValue >>= aString;
+ rData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aString.getStr()), aString.getLength() * sizeof( sal_Unicode ) );
+ bSuccess = true;
+ }
+ else if( aValue.getValueType() == cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ {
+ aValue >>= rData;
+ bSuccess = true;
+ }
+ }
+ else if( aFlavor.MimeType.startsWith("text/plain") )
+ {
+ rtl_TextEncoding aEncoding = RTL_TEXTENCODING_DONTKNOW;
+ bool bCompoundText = false;
+ if( nType == m_nCOMPOUNDAtom )
+ bCompoundText = true;
+ else
+ aEncoding = getTextPlainEncoding( aFlavor.MimeType );
+ if( aEncoding != RTL_TEXTENCODING_DONTKNOW || bCompoundText )
+ {
+ aFlavor.MimeType = "text/plain;charset=utf-16";
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ if( xTransferable->isDataFlavorSupported( aFlavor ) )
+ {
+ Any aValue( xTransferable->getTransferData( aFlavor ) );
+ OUString aString;
+ aValue >>= aString;
+ OString aByteString( bCompoundText ? convertToCompound( aString ) : OUStringToOString( aString, aEncoding ) );
+ rData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aByteString.getStr()), aByteString.getLength() * sizeof( char ) );
+ bSuccess = true;
+ }
+ }
+ }
+ }
+ // various exceptions possible ... which all lead to a failed conversion
+ // so simplify here to a catch all
+ catch(...)
+ {
+ }
+
+ return bSuccess;
+}
+
+SelectionManager& SelectionManager::get( const OUString& rDisplayName )
+{
+ osl::MutexGuard aGuard( *osl::Mutex::getGlobalMutex() );
+
+ OUString aDisplayName( rDisplayName );
+ if( aDisplayName.isEmpty() )
+ if (auto const env = getenv( "DISPLAY" )) {
+ aDisplayName = OStringToOUString( env, RTL_TEXTENCODING_ISO_8859_1 );
+ }
+ SelectionManager* pInstance = nullptr;
+
+ std::unordered_map< OUString, SelectionManager* >::iterator it = getInstances().find( aDisplayName );
+ if( it != getInstances().end() )
+ pInstance = it->second;
+ else pInstance = getInstances()[ aDisplayName ] = new SelectionManager();
+
+ return *pInstance;
+}
+
+OUString SelectionManager::getString( Atom aAtom )
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if( m_aAtomToString.find( aAtom ) == m_aAtomToString.end() )
+ {
+ char* pAtom = m_pDisplay ? XGetAtomName( m_pDisplay, aAtom ) : nullptr;
+ if( ! pAtom )
+ return OUString();
+ OUString aString( OStringToOUString( pAtom, RTL_TEXTENCODING_ISO_8859_1 ) );
+ XFree( pAtom );
+ m_aStringToAtom[ aString ] = aAtom;
+ m_aAtomToString[ aAtom ] = aString;
+ }
+ return m_aAtomToString[ aAtom ];
+}
+
+Atom SelectionManager::getAtom( const OUString& rString )
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if( m_aStringToAtom.find( rString ) == m_aStringToAtom.end() )
+ {
+ static Atom nNoDisplayAtoms = 1;
+ Atom aAtom = m_pDisplay ? XInternAtom( m_pDisplay, OUStringToOString( rString, RTL_TEXTENCODING_ISO_8859_1 ).getStr(), False ) : nNoDisplayAtoms++;
+ m_aStringToAtom[ rString ] = aAtom;
+ m_aAtomToString[ aAtom ] = rString;
+ }
+ return m_aStringToAtom[ rString ];
+}
+
+bool SelectionManager::requestOwnership( Atom selection )
+{
+ bool bSuccess = false;
+ if( m_pDisplay && m_aWindow )
+ {
+ osl::MutexGuard aGuard(m_aMutex);
+
+ SelectionAdaptor* pAdaptor = getAdaptor( selection );
+ if( pAdaptor )
+ {
+ XSetSelectionOwner( m_pDisplay, selection, m_aWindow, CurrentTime );
+ if( XGetSelectionOwner( m_pDisplay, selection ) == m_aWindow )
+ bSuccess = true;
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans",
+ (bSuccess ? "acquired" : "failed to acquire")
+ << " ownership for selection "
+ << getString( selection ));
+#endif
+
+ Selection* pSel = m_aSelections[ selection ];
+ pSel->m_bOwner = bSuccess;
+ delete pSel->m_pPixmap;
+ pSel->m_pPixmap = nullptr;
+ pSel->m_nOrigTimestamp = m_nSelectionTimestamp;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ SAL_WARN("vcl.unx.dtrans", "no adaptor for selection "
+ << getString( selection ));
+
+ if( pAdaptor->getTransferable().is() )
+ {
+ Sequence< DataFlavor > aTypes = pAdaptor->getTransferable()->getTransferDataFlavors();
+ for( int i = 0; i < aTypes.getLength(); i++ )
+ {
+ SAL_INFO("vcl.unx.dtrans", " " << aTypes.getConstArray()[i].MimeType);
+ }
+ }
+#endif
+ }
+ return bSuccess;
+}
+
+void SelectionManager::convertTypeToNative( const OUString& rType, Atom selection, int& rFormat, ::std::list< Atom >& rConversions, bool bPushFront )
+{
+ NativeTypeEntry* pTab = selection == m_nXdndSelection ? aXdndConversionTab : aNativeConversionTab;
+ int nTabEntries = selection == m_nXdndSelection ? SAL_N_ELEMENTS(aXdndConversionTab) : SAL_N_ELEMENTS(aNativeConversionTab);
+
+ OString aType( OUStringToOString( rType, RTL_TEXTENCODING_ISO_8859_1 ) );
+ SAL_INFO( "vcl.unx.dtrans", "convertTypeToNative " << aType );
+ rFormat = 0;
+ for( int i = 0; i < nTabEntries; i++ )
+ {
+ if( aType.equalsIgnoreAsciiCase( pTab[i].pType ) )
+ {
+ if( ! pTab[i].nAtom )
+ pTab[i].nAtom = getAtom( OStringToOUString( pTab[i].pNativeType, RTL_TEXTENCODING_ISO_8859_1 ) );
+ rFormat = pTab[i].nFormat;
+ if( bPushFront )
+ rConversions.push_front( pTab[i].nAtom );
+ else
+ rConversions.push_back( pTab[i].nAtom );
+ if( pTab[i].nFormat == XA_PIXMAP )
+ {
+ if( bPushFront )
+ {
+ rConversions.push_front( XA_VISUALID );
+ rConversions.push_front( XA_COLORMAP );
+ }
+ else
+ {
+ rConversions.push_back( XA_VISUALID );
+ rConversions.push_back( XA_COLORMAP );
+ }
+ }
+ }
+ }
+ if( ! rFormat )
+ rFormat = 8; // byte buffer
+ if( bPushFront )
+ rConversions.push_front( getAtom( rType ) );
+ else
+ rConversions.push_back( getAtom( rType ) );
+};
+
+void SelectionManager::getNativeTypeList( const Sequence< DataFlavor >& rTypes, std::list< Atom >& rOutTypeList, Atom targetselection )
+{
+ rOutTypeList.clear();
+
+ int nFormat;
+ bool bHaveText = false;
+ for( const auto& rFlavor : rTypes )
+ {
+ if( rFlavor.MimeType.startsWith("text/plain"))
+ bHaveText = true;
+ else
+ convertTypeToNative( rFlavor.MimeType, targetselection, nFormat, rOutTypeList );
+ }
+ if( bHaveText )
+ {
+ if( targetselection != m_nXdndSelection )
+ {
+ // only mimetypes should go into Xdnd type list
+ rOutTypeList.push_front( XA_STRING );
+ rOutTypeList.push_front( m_nCOMPOUNDAtom );
+ }
+ convertTypeToNative( "text/plain;charset=utf-8", targetselection, nFormat, rOutTypeList, true );
+ }
+ if( targetselection != m_nXdndSelection )
+ rOutTypeList.push_back( m_nMULTIPLEAtom );
+}
+
+OUString SelectionManager::convertTypeFromNative( Atom nType, Atom selection, int& rFormat )
+{
+ NativeTypeEntry* pTab = (selection == m_nXdndSelection) ? aXdndConversionTab : aNativeConversionTab;
+ int nTabEntries = (selection == m_nXdndSelection) ? SAL_N_ELEMENTS(aXdndConversionTab) : SAL_N_ELEMENTS(aNativeConversionTab);
+
+ for( int i = 0; i < nTabEntries; i++ )
+ {
+ if( ! pTab[i].nAtom )
+ pTab[i].nAtom = getAtom( OStringToOUString( pTab[i].pNativeType, RTL_TEXTENCODING_ISO_8859_1 ) );
+ if( nType == pTab[i].nAtom )
+ {
+ rFormat = pTab[i].nFormat;
+ return OStringToOUString( pTab[i].pType, RTL_TEXTENCODING_ISO_8859_1 );
+ }
+ }
+ rFormat = 8;
+ return getString( nType );
+}
+
+bool SelectionManager::getPasteData( Atom selection, Atom type, Sequence< sal_Int8 >& rData )
+{
+ osl::ResettableMutexGuard aGuard(m_aMutex);
+ std::unordered_map< Atom, Selection* >::iterator it;
+ bool bSuccess = false;
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "getPasteData( " << getString( selection )
+ << ", native: " << getString( type ) << " ).");
+#endif
+
+ if( ! m_pDisplay )
+ return false;
+
+ it = m_aSelections.find( selection );
+ if( it == m_aSelections.end() )
+ return false;
+
+ ::Window aSelectionOwner = XGetSelectionOwner( m_pDisplay, selection );
+ if( aSelectionOwner == None )
+ return false;
+ if( aSelectionOwner == m_aWindow )
+ {
+ // probably bad timing led us here
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.unx.dtrans", "Innere Nabelschau.");
+#endif
+ return false;
+ }
+
+ // ICCCM recommends to destroy property before convert request unless
+ // parameters are transported; we do only in case of MULTIPLE,
+ // so destroy property unless target is MULTIPLE
+ if( type != m_nMULTIPLEAtom )
+ XDeleteProperty( m_pDisplay, m_aWindow, selection );
+
+ XConvertSelection( m_pDisplay, selection, type, selection, m_aWindow, selection == m_nXdndSelection ? m_nDropTime : CurrentTime );
+ it->second->m_eState = Selection::WaitingForResponse;
+ it->second->m_aRequestedType = type;
+ it->second->m_aData = Sequence< sal_Int8 >();
+ it->second->m_aDataArrived.reset();
+ // really start the request; if we don't flush the
+ // queue the request won't leave it because there are no more
+ // X calls after this until the data arrived or timeout
+ XFlush( m_pDisplay );
+
+ // do a reschedule
+ struct timeval tv_last, tv_current;
+ gettimeofday( &tv_last, nullptr );
+ tv_current = tv_last;
+
+ XEvent aEvent;
+ do
+ {
+ bool bAdjustTime = false;
+ {
+ bool bHandle = false;
+
+ if( XCheckTypedEvent( m_pDisplay,
+ PropertyNotify,
+ &aEvent
+ ) )
+ {
+ bHandle = true;
+ if( aEvent.xproperty.window == m_aWindow
+ && aEvent.xproperty.atom == selection )
+ bAdjustTime = true;
+ }
+ else if( XCheckTypedEvent( m_pDisplay,
+ SelectionClear,
+ &aEvent
+ ) )
+ {
+ bHandle = true;
+ }
+ else if( XCheckTypedEvent( m_pDisplay,
+ SelectionRequest,
+ &aEvent
+ ) )
+ {
+ bHandle = true;
+ }
+ else if( XCheckTypedEvent( m_pDisplay,
+ SelectionNotify,
+ &aEvent
+ ) )
+ {
+ bHandle = true;
+ if( aEvent.xselection.selection == selection
+ && ( aEvent.xselection.requestor == m_aWindow ||
+ aEvent.xselection.requestor == m_aCurrentDropWindow )
+ )
+ bAdjustTime = true;
+ }
+ else
+ {
+ aGuard.clear();
+ osl::Thread::wait(std::chrono::milliseconds(100));
+ aGuard.reset();
+ }
+ if( bHandle )
+ {
+ aGuard.clear();
+ handleXEvent( aEvent );
+ aGuard.reset();
+ }
+ }
+ gettimeofday( &tv_current, nullptr );
+ if( bAdjustTime )
+ tv_last = tv_current;
+ } while( ! it->second->m_aDataArrived.check() && (tv_current.tv_sec - tv_last.tv_sec) < getSelectionTimeout() );
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN_IF((tv_current.tv_sec - tv_last.tv_sec) > getSelectionTimeout(),
+ "vcl.unx.dtrans", "timed out.");
+#endif
+
+ if( it->second->m_aDataArrived.check() &&
+ it->second->m_aData.getLength() )
+ {
+ rData = it->second->m_aData;
+ bSuccess = true;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ SAL_WARN("vcl.unx.dtrans", "conversion unsuccessful.");
+#endif
+ return bSuccess;
+}
+
+bool SelectionManager::getPasteData( Atom selection, const OUString& rType, Sequence< sal_Int8 >& rData )
+{
+ bool bSuccess = false;
+
+ std::unordered_map< Atom, Selection* >::iterator it;
+ {
+ osl::MutexGuard aGuard(m_aMutex);
+
+ it = m_aSelections.find( selection );
+ if( it == m_aSelections.end() )
+ return false;
+ }
+
+ if( it->second->m_aTypes.getLength() == 0 )
+ {
+ Sequence< DataFlavor > aFlavors;
+ getPasteDataTypes( selection, aFlavors );
+ if( it->second->m_aTypes.getLength() == 0 )
+ return false;
+ }
+
+ const Sequence< DataFlavor >& rTypes( it->second->m_aTypes );
+ const std::vector< Atom >& rNativeTypes( it->second->m_aNativeTypes );
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "getPasteData( \""
+ << getString( selection )
+ << "\", \""
+ << rType << "\" ).");
+#endif
+
+ if( rType == "text/plain;charset=utf-16" )
+ {
+ // lets see if we have UTF16 else try to find something convertible
+ if( it->second->m_aTypes.getLength() && ! it->second->m_bHaveUTF16 )
+ {
+ Sequence< sal_Int8 > aData;
+ if( it->second->m_aUTF8Type != None &&
+ getPasteData( selection,
+ it->second->m_aUTF8Type,
+ aData )
+ )
+ {
+ OUString aRet( reinterpret_cast<const char*>(aData.getConstArray()), aData.getLength(), RTL_TEXTENCODING_UTF8 );
+ rData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aRet.getStr()), (aRet.getLength()+1)*sizeof( sal_Unicode ) );
+ bSuccess = true;
+ }
+ else if( it->second->m_bHaveCompound &&
+ getPasteData( selection,
+ m_nCOMPOUNDAtom,
+ aData )
+ )
+ {
+ OUString aRet( convertFromCompound( reinterpret_cast<const char*>(aData.getConstArray()), aData.getLength() ) );
+ rData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aRet.getStr()), (aRet.getLength()+1)*sizeof( sal_Unicode ) );
+ bSuccess = true;
+ }
+ else
+ {
+ for( int i = 0; i < rTypes.getLength(); i++ )
+ {
+ rtl_TextEncoding aEncoding = getTextPlainEncoding( rTypes.getConstArray()[i].MimeType );
+ if( aEncoding != RTL_TEXTENCODING_DONTKNOW &&
+ aEncoding != RTL_TEXTENCODING_UNICODE &&
+ getPasteData( selection,
+ rNativeTypes[i],
+ aData )
+ )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "using \""
+ << rTypes.getConstArray()[i].MimeType
+ << "\" instead of \""
+ << rType
+ << "\".");
+#endif
+
+ OString aConvert( reinterpret_cast<char const *>(aData.getConstArray()), aData.getLength() );
+ OUString aUTF( OStringToOUString( aConvert, aEncoding ) );
+ rData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8 const *>(aUTF.getStr()), (aUTF.getLength()+1)*sizeof( sal_Unicode ) );
+ bSuccess = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ else if( rType == "image/bmp" )
+ {
+ // #i83376# try if someone has the data in image/bmp already before
+ // doing the PIXMAP stuff (e.g. the Gimp has this)
+ bSuccess = getPasteData( selection, m_nImageBmpAtom, rData );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO_IF(bSuccess, "vcl.unx.dtrans",
+ "got " << (int) rData.getLength() << " bytes of image/bmp.");
+#endif
+ if( ! bSuccess )
+ {
+ Pixmap aPixmap = None;
+ Colormap aColormap = None;
+
+ // prepare property for MULTIPLE request
+ Sequence< sal_Int8 > aData;
+ Atom const pTypes[4] = { XA_PIXMAP, XA_PIXMAP, XA_COLORMAP, XA_COLORMAP };
+ {
+ osl::MutexGuard aGuard(m_aMutex);
+
+ XChangeProperty( m_pDisplay,
+ m_aWindow,
+ selection,
+ XA_ATOM,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char const *>(pTypes),
+ 4 );
+ }
+
+ // try MULTIPLE request
+ if( getPasteData( selection, m_nMULTIPLEAtom, aData ) )
+ {
+ Atom* pReturnedTypes = reinterpret_cast<Atom*>(aData.getArray());
+ if( pReturnedTypes[0] == XA_PIXMAP && pReturnedTypes[1] == XA_PIXMAP )
+ {
+ osl::MutexGuard aGuard(m_aMutex);
+
+ Atom type = None;
+ int format = 0;
+ unsigned long nItems = 0;
+ unsigned long nBytes = 0;
+ unsigned char* pReturn = nullptr;
+ XGetWindowProperty( m_pDisplay, m_aWindow, XA_PIXMAP, 0, 1, True, XA_PIXMAP, &type, &format, &nItems, &nBytes, &pReturn );
+ if( pReturn )
+ {
+ if( type == XA_PIXMAP )
+ aPixmap = *reinterpret_cast<Pixmap*>(pReturn);
+ XFree( pReturn );
+ pReturn = nullptr;
+ if( pReturnedTypes[2] == XA_COLORMAP && pReturnedTypes[3] == XA_COLORMAP )
+ {
+ XGetWindowProperty( m_pDisplay, m_aWindow, XA_COLORMAP, 0, 1, True, XA_COLORMAP, &type, &format, &nItems, &nBytes, &pReturn );
+ if( pReturn )
+ {
+ if( type == XA_COLORMAP )
+ aColormap = *reinterpret_cast<Colormap*>(pReturn);
+ XFree( pReturn );
+ }
+ }
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ {
+ SAL_WARN("vcl.unx.dtrans", "could not get PIXMAP property: type="
+ << getString( type )
+ << ", format=" << format
+ << ", items=" << nItems
+ << ", bytes=" << nBytes
+ << ", ret=0x" << pReturn);
+ }
+#endif
+ }
+ }
+
+ if( aPixmap == None )
+ {
+ // perhaps two normal requests will work
+ if( getPasteData( selection, XA_PIXMAP, aData ) )
+ {
+ aPixmap = *reinterpret_cast<Pixmap*>(aData.getArray());
+ if( aColormap == None && getPasteData( selection, XA_COLORMAP, aData ) )
+ aColormap = *reinterpret_cast<Colormap*>(aData.getArray());
+ }
+ }
+
+ // convert data if possible
+ if( aPixmap != None )
+ {
+ osl::MutexGuard aGuard(m_aMutex);
+
+ sal_Int32 nOutSize = 0;
+ sal_uInt8* pBytes = X11_getBmpFromPixmap( m_pDisplay, aPixmap, aColormap, nOutSize );
+ if( pBytes )
+ {
+ if( nOutSize )
+ {
+ rData = Sequence< sal_Int8 >( nOutSize );
+ memcpy( rData.getArray(), pBytes, nOutSize );
+ bSuccess = true;
+ }
+ std::free( pBytes );
+ }
+ }
+ }
+ }
+
+ if( ! bSuccess )
+ {
+ int nFormat;
+ ::std::list< Atom > aTypes;
+ convertTypeToNative( rType, selection, nFormat, aTypes );
+ Atom nSelectedType = None;
+ for (auto const& type : aTypes)
+ {
+ for( auto const & nativeType: rNativeTypes )
+ if(nativeType == type)
+ {
+ nSelectedType = type;
+ if (nSelectedType != None)
+ break;
+ }
+ }
+ if( nSelectedType != None )
+ bSuccess = getPasteData( selection, nSelectedType, rData );
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "getPasteData for selection "
+ << getString( selection )
+ << " and data type "
+ << rType
+ << " returns "
+ << ( bSuccess ? "true" : "false")
+ << ", returned sequence has length "
+ << rData.getLength() << ".");
+#endif
+ return bSuccess;
+}
+
+bool SelectionManager::getPasteDataTypes( Atom selection, Sequence< DataFlavor >& rTypes )
+{
+ std::unordered_map< Atom, Selection* >::iterator it;
+ {
+ osl::MutexGuard aGuard(m_aMutex);
+
+ it = m_aSelections.find( selection );
+ if( it != m_aSelections.end() &&
+ it->second->m_aTypes.getLength() &&
+ std::abs( it->second->m_nLastTimestamp - time( nullptr ) ) < 2
+ )
+ {
+ rTypes = it->second->m_aTypes;
+ return true;
+ }
+ }
+
+ bool bSuccess = false;
+ bool bHaveUTF16 = false;
+ Atom aUTF8Type = None;
+ bool bHaveCompound = false;
+ Sequence< sal_Int8 > aAtoms;
+
+ if( selection == m_nXdndSelection )
+ {
+ // xdnd sends first three types with XdndEnter
+ // if more than three types are supported then the XDndTypeList
+ // property on the source window is used
+ if( m_aDropEnterEvent.data.l[0] && m_aCurrentDropWindow )
+ {
+ if( m_aDropEnterEvent.data.l[1] & 1 )
+ {
+ const unsigned int atomcount = 256;
+ // more than three types; look in property
+ osl::MutexGuard aGuard(m_aMutex);
+
+ Atom nType;
+ int nFormat;
+ unsigned long nItems, nBytes;
+ unsigned char* pBytes = nullptr;
+
+ XGetWindowProperty( m_pDisplay, m_aDropEnterEvent.data.l[0],
+ m_nXdndTypeList, 0, atomcount, False,
+ XA_ATOM,
+ &nType, &nFormat, &nItems, &nBytes, &pBytes );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "have "
+ << nItems
+ << " data types in XdndTypeList.");
+#endif
+ if( nItems == atomcount && nBytes > 0 )
+ {
+ // wow ... more than 256 types !
+ aAtoms.realloc( sizeof( Atom )*atomcount+nBytes );
+ memcpy( aAtoms.getArray(), pBytes, sizeof( Atom )*atomcount );
+ XFree( pBytes );
+ pBytes = nullptr;
+ XGetWindowProperty( m_pDisplay, m_aDropEnterEvent.data.l[0],
+ m_nXdndTypeList, atomcount, nBytes/sizeof(Atom),
+ False, XA_ATOM,
+ &nType, &nFormat, &nItems, &nBytes, &pBytes );
+ {
+ memcpy( aAtoms.getArray()+atomcount*sizeof(Atom), pBytes, nItems*sizeof(Atom) );
+ XFree( pBytes );
+ }
+ }
+ else
+ {
+ aAtoms.realloc( sizeof(Atom)*nItems );
+ memcpy( aAtoms.getArray(), pBytes, nItems*sizeof(Atom) );
+ XFree( pBytes );
+ }
+ }
+ else
+ {
+ // one to three types
+ int n = 0, i;
+ for( i = 0; i < 3; i++ )
+ if( m_aDropEnterEvent.data.l[2+i] )
+ n++;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "have "
+ << n
+ << " data types in XdndEnter.");
+#endif
+ aAtoms.realloc( sizeof(Atom)*n );
+ for( i = 0, n = 0; i < 3; i++ )
+ if( m_aDropEnterEvent.data.l[2+i] )
+ reinterpret_cast<Atom*>(aAtoms.getArray())[n++] = m_aDropEnterEvent.data.l[2+i];
+ }
+ }
+ }
+ // get data of type TARGETS
+ else if( ! getPasteData( selection, m_nTARGETSAtom, aAtoms ) )
+ aAtoms = Sequence< sal_Int8 >();
+
+ std::vector< Atom > aNativeTypes;
+ if( aAtoms.hasElements() )
+ {
+ sal_Int32 nAtoms = aAtoms.getLength() / sizeof(Atom);
+ Atom* pAtoms = reinterpret_cast<Atom*>(aAtoms.getArray());
+ rTypes.realloc( nAtoms );
+ aNativeTypes.resize( nAtoms );
+ DataFlavor* pFlavors = rTypes.getArray();
+ sal_Int32 nNativeTypesIndex = 0;
+ bool bHaveText = false;
+ while( nAtoms-- )
+ {
+ SAL_INFO_IF(*pAtoms && *pAtoms < 0x01000000, "vcl.unx.dtrans",
+ "getPasteDataTypes: available: \"" << getString(*pAtoms) << "\"");
+ if( *pAtoms == m_nCOMPOUNDAtom )
+ bHaveText = bHaveCompound = true;
+ else if( *pAtoms && *pAtoms < 0x01000000 )
+ {
+ int nFormat;
+ pFlavors->MimeType = convertTypeFromNative( *pAtoms, selection, nFormat );
+ pFlavors->DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+ sal_Int32 nIndex = 0;
+ if( o3tl::getToken(pFlavors->MimeType, 0, ';', nIndex ) == u"text/plain" )
+ {
+ std::u16string_view aToken(o3tl::getToken(pFlavors->MimeType, 0, ';', nIndex ));
+ // omit text/plain;charset=unicode since it is not well defined
+ if( aToken == u"charset=unicode" )
+ {
+ pAtoms++;
+ continue;
+ }
+ bHaveText = true;
+ if( aToken == u"charset=utf-16" )
+ {
+ bHaveUTF16 = true;
+ pFlavors->DataType = cppu::UnoType<OUString>::get();
+ }
+ else if( aToken == u"charset=utf-8" )
+ {
+ aUTF8Type = *pAtoms;
+ }
+ }
+ pFlavors++;
+ aNativeTypes[ nNativeTypesIndex ] = *pAtoms;
+ nNativeTypesIndex++;
+ }
+ pAtoms++;
+ }
+ if( (pFlavors - rTypes.getArray()) < rTypes.getLength() )
+ rTypes.realloc(pFlavors - rTypes.getArray());
+ bSuccess = rTypes.hasElements();
+ if( bHaveText && ! bHaveUTF16 )
+ {
+ int i = 0;
+
+ int nNewFlavors = rTypes.getLength()+1;
+ Sequence< DataFlavor > aTemp( nNewFlavors );
+ for( i = 0; i < nNewFlavors-1; i++ )
+ aTemp.getArray()[i+1] = rTypes.getConstArray()[i];
+ aTemp.getArray()[0].MimeType = "text/plain;charset=utf-16";
+ aTemp.getArray()[0].DataType = cppu::UnoType<OUString>::get();
+ rTypes = aTemp;
+
+ std::vector< Atom > aNativeTemp( nNewFlavors );
+ for( i = 0; i < nNewFlavors-1; i++ )
+ aNativeTemp[ i + 1 ] = aNativeTypes[ i ];
+ aNativeTemp[0] = None;
+ aNativeTypes = aNativeTemp;
+ }
+ }
+
+ {
+ osl::MutexGuard aGuard(m_aMutex);
+
+ it = m_aSelections.find( selection );
+ if( it != m_aSelections.end() )
+ {
+ if( bSuccess )
+ {
+ it->second->m_aTypes = rTypes;
+ it->second->m_aNativeTypes = aNativeTypes;
+ it->second->m_nLastTimestamp = time( nullptr );
+ it->second->m_bHaveUTF16 = bHaveUTF16;
+ it->second->m_aUTF8Type = aUTF8Type;
+ it->second->m_bHaveCompound = bHaveCompound;
+ }
+ else
+ {
+ it->second->m_aTypes = Sequence< DataFlavor >();
+ it->second->m_aNativeTypes = std::vector< Atom >();
+ it->second->m_nLastTimestamp = 0;
+ it->second->m_bHaveUTF16 = false;
+ it->second->m_aUTF8Type = None;
+ it->second->m_bHaveCompound = false;
+ }
+ }
+ }
+
+#if OSL_DEBUG_LEVEL > 1
+ {
+ SAL_INFO("vcl.unx.dtrans", "SelectionManager::getPasteDataTypes( "
+ << getString( selection )
+ << " ) = "
+ << (bSuccess ? "true" : "false"));
+ for( int i = 0; i < rTypes.getLength(); i++ )
+ SAL_INFO("vcl.unx.dtrans", "type: " << rTypes.getConstArray()[i].MimeType);
+ }
+#endif
+
+ return bSuccess;
+}
+
+PixmapHolder* SelectionManager::getPixmapHolder( Atom selection )
+{
+ std::unordered_map< Atom, Selection* >::const_iterator it = m_aSelections.find( selection );
+ if( it == m_aSelections.end() )
+ return nullptr;
+ if( ! it->second->m_pPixmap )
+ it->second->m_pPixmap = new PixmapHolder( m_pDisplay );
+ return it->second->m_pPixmap;
+}
+
+static std::size_t GetTrueFormatSize(int nFormat)
+{
+ // http://mail.gnome.org/archives/wm-spec-list/2003-March/msg00067.html
+ return nFormat == 32 ? sizeof(long) : nFormat/8;
+}
+
+bool SelectionManager::sendData( SelectionAdaptor* pAdaptor,
+ ::Window requestor,
+ Atom target,
+ Atom property,
+ Atom selection )
+{
+ osl::ResettableMutexGuard aGuard( m_aMutex );
+
+ // handle targets related to image/bmp
+ if( target == XA_COLORMAP || target == XA_PIXMAP || target == XA_BITMAP || target == XA_VISUALID )
+ {
+ PixmapHolder* pPixmap = getPixmapHolder( selection );
+ if( ! pPixmap ) return false;
+ XID nValue = None;
+
+ // handle colormap request
+ if( target == XA_COLORMAP )
+ nValue = static_cast<XID>(pPixmap->getColormap());
+ else if( target == XA_VISUALID )
+ nValue = static_cast<XID>(pPixmap->getVisualID());
+ else if( target == XA_PIXMAP || target == XA_BITMAP )
+ {
+ nValue = static_cast<XID>(pPixmap->getPixmap());
+ if( nValue == None )
+ {
+ // first conversion
+ Sequence< sal_Int8 > aData;
+ int nFormat;
+ aGuard.clear();
+ bool bConverted = convertData( pAdaptor->getTransferable(), target, selection, nFormat, aData );
+ aGuard.reset();
+ if( bConverted )
+ {
+ // get pixmap again since clearing the guard could have invalidated
+ // the pixmap in another thread
+ pPixmap = getPixmapHolder( selection );
+ // conversion succeeded, so aData contains image/bmp now
+ if( pPixmap->needsConversion( reinterpret_cast<const sal_uInt8*>(aData.getConstArray()) ) )
+ {
+ SAL_INFO( "vcl.unx.dtrans", "trying bitmap conversion" );
+ int depth = pPixmap->getDepth();
+ aGuard.clear();
+ aData = convertBitmapDepth(aData, depth);
+ aGuard.reset();
+ }
+ // get pixmap again since clearing the guard could have invalidated
+ // the pixmap in another thread
+ pPixmap = getPixmapHolder( selection );
+ nValue = static_cast<XID>(pPixmap->setBitmapData( reinterpret_cast<const sal_uInt8*>(aData.getConstArray()) ));
+ }
+ if( nValue == None )
+ return false;
+ }
+ if( target == XA_BITMAP )
+ nValue = static_cast<XID>(pPixmap->getBitmap());
+ }
+
+ XChangeProperty( m_pDisplay,
+ requestor,
+ property,
+ target,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<const unsigned char*>(&nValue),
+ 1);
+ return true;
+ }
+
+ /*
+ * special target TEXT allows us to transfer
+ * the data in an encoding of our choice
+ * COMPOUND_TEXT will work with most applications
+ */
+ if( target == m_nTEXTAtom )
+ target = m_nCOMPOUNDAtom;
+
+ Sequence< sal_Int8 > aData;
+ int nFormat;
+ aGuard.clear();
+ bool bConverted = convertData( pAdaptor->getTransferable(), target, selection, nFormat, aData );
+ aGuard.reset();
+ if( bConverted )
+ {
+ // conversion succeeded
+ if( aData.getLength() > m_nIncrementalThreshold )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "using INCR protocol.");
+ std::unordered_map< ::Window, std::unordered_map< Atom, IncrementalTransfer > >::const_iterator win_it = m_aIncrementals.find( requestor );
+ if( win_it != m_aIncrementals.end() )
+ {
+ std::unordered_map< Atom, IncrementalTransfer >::const_iterator inc_it = win_it->second.find( property );
+ if( inc_it != win_it->second.end() )
+ {
+ const IncrementalTransfer& rInc = inc_it->second;
+ SAL_INFO("vcl.unx.dtrans", "premature end and new start for INCR transfer for window "
+ << std::showbase << std::hex
+ << rInc.m_aRequestor
+ << ", property "
+ << getString( rInc.m_aProperty )
+ << ", type "
+ << getString( rInc.m_aTarget ));
+ }
+ }
+#endif
+
+ // insert IncrementalTransfer
+ IncrementalTransfer& rInc = m_aIncrementals[ requestor ][ property ];
+ rInc.m_aData = aData;
+ rInc.m_nBufferPos = 0;
+ rInc.m_aRequestor = requestor;
+ rInc.m_aProperty = property;
+ rInc.m_aTarget = target;
+ rInc.m_nFormat = nFormat;
+ rInc.m_nTransferStartTime = time( nullptr );
+
+ // use incr protocol, signal start to requestor
+ tools::Long nMinSize = m_nIncrementalThreshold;
+ XSelectInput( m_pDisplay, requestor, PropertyChangeMask );
+ XChangeProperty( m_pDisplay, requestor, property,
+ m_nINCRAtom, 32, PropModeReplace, reinterpret_cast<unsigned char*>(&nMinSize), 1 );
+ XFlush( m_pDisplay );
+ }
+ else
+ {
+ std::size_t nUnitSize = GetTrueFormatSize(nFormat);
+ XChangeProperty( m_pDisplay,
+ requestor,
+ property,
+ target,
+ nFormat,
+ PropModeReplace,
+ reinterpret_cast<const unsigned char*>(aData.getConstArray()),
+ aData.getLength()/nUnitSize );
+ }
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ SAL_WARN("vcl.unx.dtrans", "convertData failed for type: "
+ << convertTypeFromNative( target, selection, nFormat ));
+#endif
+ return bConverted;
+}
+
+bool SelectionManager::handleSelectionRequest( XSelectionRequestEvent& rRequest )
+{
+ osl::ResettableMutexGuard aGuard( m_aMutex );
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "handleSelectionRequest for selection "
+ << getString( rRequest.selection )
+ << " and target "
+ << getString( rRequest.target ));
+#endif
+
+ XEvent aNotify;
+
+ aNotify.type = SelectionNotify;
+ aNotify.xselection.display = rRequest.display;
+ aNotify.xselection.send_event = True;
+ aNotify.xselection.requestor = rRequest.requestor;
+ aNotify.xselection.selection = rRequest.selection;
+ aNotify.xselection.time = rRequest.time;
+ aNotify.xselection.target = rRequest.target;
+ aNotify.xselection.property = None;
+
+ SelectionAdaptor* pAdaptor = getAdaptor( rRequest.selection );
+ // ensure that we still own that selection
+ if( pAdaptor &&
+ XGetSelectionOwner( m_pDisplay, rRequest.selection ) == m_aWindow )
+ {
+ css::uno::Reference< XTransferable > xTrans( pAdaptor->getTransferable() );
+ if( rRequest.target == m_nTARGETSAtom )
+ {
+ // someone requests our types
+ if( xTrans.is() )
+ {
+ aGuard.clear();
+ Sequence< DataFlavor > aFlavors = xTrans->getTransferDataFlavors();
+ aGuard.reset();
+
+ ::std::list< Atom > aConversions;
+ getNativeTypeList( aFlavors, aConversions, rRequest.selection );
+
+ int i, nTypes = aConversions.size();
+ Atom* pTypes = static_cast<Atom*>(alloca( nTypes * sizeof( Atom ) ));
+ std::list< Atom >::const_iterator it;
+ for( i = 0, it = aConversions.begin(); i < nTypes; i++, ++it )
+ pTypes[i] = *it;
+ XChangeProperty( m_pDisplay, rRequest.requestor, rRequest.property,
+ XA_ATOM, 32, PropModeReplace, reinterpret_cast<unsigned char*>(pTypes), nTypes );
+ aNotify.xselection.property = rRequest.property;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "sending type list:");
+ for( int k = 0; k < nTypes; k++ )
+ SAL_INFO("vcl.unx.dtrans", " "
+ << (pTypes[k] ? XGetAtomName( m_pDisplay, pTypes[k] ) :
+ "<None>"));
+#endif
+ }
+ }
+ else if( rRequest.target == m_nTIMESTAMPAtom )
+ {
+ tools::Long nTimeStamp = static_cast<tools::Long>(m_aSelections[rRequest.selection]->m_nOrigTimestamp);
+ XChangeProperty( m_pDisplay, rRequest.requestor, rRequest.property,
+ XA_INTEGER, 32, PropModeReplace, reinterpret_cast<unsigned char*>(&nTimeStamp), 1 );
+ aNotify.xselection.property = rRequest.property;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "sending timestamp: " << (int)nTimeStamp);
+#endif
+ }
+ else
+ {
+ bool bEventSuccess = false;
+ if( rRequest.target == m_nMULTIPLEAtom )
+ {
+ // get all targets
+ Atom nType = None;
+ int nFormat = 0;
+ unsigned long nItems = 0, nBytes = 0;
+ unsigned char* pData = nullptr;
+
+ // get number of atoms
+ XGetWindowProperty( m_pDisplay,
+ rRequest.requestor,
+ rRequest.property,
+ 0, 0,
+ False,
+ AnyPropertyType,
+ &nType, &nFormat,
+ &nItems, &nBytes,
+ &pData );
+ if( nFormat == 32 && nBytes/4 )
+ {
+ if( pData ) // ?? should not happen
+ {
+ XFree( pData );
+ pData = nullptr;
+ }
+ XGetWindowProperty( m_pDisplay,
+ rRequest.requestor,
+ rRequest.property,
+ 0, nBytes/4,
+ False,
+ nType,
+ &nType, &nFormat,
+ &nItems, &nBytes,
+ &pData );
+ if( pData && nItems )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "found "
+ << nItems
+ << " atoms in MULTIPLE request.");
+#endif
+ bEventSuccess = true;
+ bool bResetAtoms = false;
+ Atom* pAtoms = reinterpret_cast<Atom*>(pData);
+ aGuard.clear();
+ for( unsigned long i = 0; i < nItems; i += 2 )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ std::ostringstream oss;
+ oss << " "
+ << getString( pAtoms[i] )
+ << " => "
+ << getString( pAtoms[i+1] )
+ << ": ";
+#endif
+
+ bool bSuccess = sendData( pAdaptor, rRequest.requestor, pAtoms[i], pAtoms[i+1], rRequest.selection );
+#if OSL_DEBUG_LEVEL > 1
+ oss << (bSuccess ? "succeeded" : "failed");
+ SAL_INFO("vcl.unx.dtrans", oss.str());
+#endif
+ if( ! bSuccess )
+ {
+ pAtoms[i] = None;
+ bResetAtoms = true;
+ }
+ }
+ aGuard.reset();
+ if( bResetAtoms )
+ XChangeProperty( m_pDisplay,
+ rRequest.requestor,
+ rRequest.property,
+ XA_ATOM,
+ 32,
+ PropModeReplace,
+ pData,
+ nBytes/4 );
+ }
+ if( pData )
+ XFree( pData );
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ {
+ std::ostringstream oss;
+ oss << "could not get type list from \""
+ << getString( rRequest.property )
+ << "\" of type \""
+ << getString( nType )
+ << "\" on requestor "
+ << std::showbase << std::hex
+ << rRequest.requestor
+ << ", requestor has properties:";
+
+ int nProps = 0;
+ Atom* pProps = XListProperties( m_pDisplay, rRequest.requestor, &nProps );
+ if( pProps )
+ {
+ for( int i = 0; i < nProps; i++ )
+ oss << " \"" << getString( pProps[i]) << "\"";
+ XFree( pProps );
+ }
+ SAL_INFO("vcl.unx.dtrans", oss.str());
+ }
+#endif
+ }
+ else
+ {
+ aGuard.clear();
+ bEventSuccess = sendData( pAdaptor, rRequest.requestor, rRequest.target, rRequest.property, rRequest.selection );
+ aGuard.reset();
+ }
+ if( bEventSuccess )
+ {
+ aNotify.xselection.target = rRequest.target;
+ aNotify.xselection.property = rRequest.property;
+ }
+ }
+ aGuard.clear();
+ xTrans.clear();
+ aGuard.reset();
+ }
+ XSendEvent( m_pDisplay, rRequest.requestor, False, 0, &aNotify );
+
+ if( rRequest.selection == XA_PRIMARY &&
+ m_bWaitingForPrimaryConversion &&
+ m_xDragSourceListener.is() )
+ {
+ DragSourceDropEvent dsde;
+ dsde.Source = static_cast< OWeakObject* >(this);
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ if( aNotify.xselection.property != None )
+ {
+ dsde.DropAction = DNDConstants::ACTION_COPY;
+ dsde.DropSuccess = true;
+ }
+ else
+ {
+ dsde.DropAction = DNDConstants::ACTION_NONE;
+ dsde.DropSuccess = false;
+ }
+ css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener );
+ m_xDragSourceListener.clear();
+ aGuard.clear();
+ if( xListener.is() )
+ xListener->dragDropEnd( dsde );
+ }
+
+ // we handled the event in any case by answering
+ return true;
+}
+
+bool SelectionManager::handleReceivePropertyNotify( XPropertyEvent const & rNotify )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+ // data we requested arrived
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "handleReceivePropertyNotify for property "
+ << getString( rNotify.atom ));
+#endif
+ bool bHandled = false;
+
+ std::unordered_map< Atom, Selection* >::iterator it =
+ m_aSelections.find( rNotify.atom );
+ if( it != m_aSelections.end() &&
+ rNotify.state == PropertyNewValue &&
+ ( it->second->m_eState == Selection::WaitingForResponse ||
+ it->second->m_eState == Selection::WaitingForData ||
+ it->second->m_eState == Selection::IncrementalTransfer
+ )
+ )
+ {
+ // MULTIPLE requests are only complete after selection notify
+ if( it->second->m_aRequestedType == m_nMULTIPLEAtom &&
+ ( it->second->m_eState == Selection::WaitingForResponse ||
+ it->second->m_eState == Selection::WaitingForData ) )
+ return false;
+
+ bHandled = true;
+
+ Atom nType = None;
+ int nFormat = 0;
+ unsigned long nItems = 0, nBytes = 0;
+ unsigned char* pData = nullptr;
+
+ // get type and length
+ XGetWindowProperty( m_pDisplay,
+ rNotify.window,
+ rNotify.atom,
+ 0, 0,
+ False,
+ AnyPropertyType,
+ &nType, &nFormat,
+ &nItems, &nBytes,
+ &pData );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "found "
+ << nBytes
+ << " bytes data of type "
+ << getString( nType )
+ << " and format "
+ << nFormat
+ << ", items = "
+ << nItems);
+#endif
+ if( pData )
+ {
+ XFree( pData );
+ pData = nullptr;
+ }
+
+ if( nType == m_nINCRAtom )
+ {
+ // start data transfer
+ XDeleteProperty( m_pDisplay, rNotify.window, rNotify.atom );
+ it->second->m_eState = Selection::IncrementalTransfer;
+ }
+ else if( nType != None )
+ {
+ XGetWindowProperty( m_pDisplay,
+ rNotify.window,
+ rNotify.atom,
+ 0, nBytes/4 +1,
+ True,
+ nType,
+ &nType, &nFormat,
+ &nItems, &nBytes,
+ &pData );
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "read "
+ << nItems
+ << " items data of type "
+ << getString( nType )
+ << " and format "
+ << nFormat
+ << ", "
+ << nBytes
+ << " bytes left in property.");
+#endif
+
+ std::size_t nUnitSize = GetTrueFormatSize(nFormat);
+
+ if( it->second->m_eState == Selection::WaitingForData ||
+ it->second->m_eState == Selection::WaitingForResponse )
+ {
+ // copy data
+ it->second->m_aData = Sequence< sal_Int8 >( reinterpret_cast<sal_Int8*>(pData), nItems*nUnitSize );
+ it->second->m_eState = Selection::Inactive;
+ it->second->m_aDataArrived.set();
+ }
+ else if( it->second->m_eState == Selection::IncrementalTransfer )
+ {
+ if( nItems )
+ {
+ // append data
+ Sequence< sal_Int8 > aData( it->second->m_aData.getLength() + nItems*nUnitSize );
+ memcpy( aData.getArray(), it->second->m_aData.getArray(), it->second->m_aData.getLength() );
+ memcpy( aData.getArray() + it->second->m_aData.getLength(), pData, nItems*nUnitSize );
+ it->second->m_aData = aData;
+ }
+ else
+ {
+ it->second->m_eState = Selection::Inactive;
+ it->second->m_aDataArrived.set();
+ }
+ }
+ if( pData )
+ XFree( pData );
+ }
+ else if( it->second->m_eState == Selection::IncrementalTransfer )
+ {
+ it->second->m_eState = Selection::Inactive;
+ it->second->m_aDataArrived.set();
+ }
+ }
+ return bHandled;
+}
+
+bool SelectionManager::handleSendPropertyNotify( XPropertyEvent const & rNotify )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+
+ // ready for next part of an IncrementalTransfer
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "handleSendPropertyNotify for property "
+ << getString( rNotify.atom )
+ << " ("
+ << (rNotify.state == PropertyNewValue ?
+ "new value" :
+ (rNotify.state == PropertyDelete ?
+ "deleted" :
+ "unknown"))
+ << ").");
+#endif
+
+ bool bHandled = false;
+ // feed incrementals
+ if( rNotify.state == PropertyDelete )
+ {
+ auto it = m_aIncrementals.find( rNotify.window );
+ if( it != m_aIncrementals.end() )
+ {
+ bHandled = true;
+ int nCurrentTime = time( nullptr );
+ // throw out aborted transfers
+ std::vector< Atom > aTimeouts;
+ for (auto const& incrementalTransfer : it->second)
+ {
+ if( (nCurrentTime - incrementalTransfer.second.m_nTransferStartTime) > (getSelectionTimeout()+2) )
+ {
+ aTimeouts.push_back( incrementalTransfer.first );
+#if OSL_DEBUG_LEVEL > 1
+ const IncrementalTransfer& rInc = incrementalTransfer.second;
+ SAL_INFO("vcl.unx.dtrans",
+ "timeout on INCR transfer for window "
+ << std::showbase << std::hex
+ << rInc.m_aRequestor
+ << ", property "
+ << getString( rInc.m_aProperty )
+ << ", type "
+ << getString( rInc.m_aTarget ));
+#endif
+ }
+ }
+
+ for (auto const& timeout : aTimeouts)
+ {
+ // transfer broken, might even be a new client with the
+ // same window id
+ it->second.erase( timeout );
+ }
+ aTimeouts.clear();
+
+ auto inc_it = it->second.find( rNotify.atom );
+ if( inc_it != it->second.end() )
+ {
+ IncrementalTransfer& rInc = inc_it->second;
+
+ int nBytes = rInc.m_aData.getLength() - rInc.m_nBufferPos;
+ nBytes = std::min(nBytes, m_nIncrementalThreshold);
+ if( nBytes < 0 ) // sanity check
+ nBytes = 0;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "pushing "
+ << nBytes
+ << " bytes: \""
+ << std::setw(std::min(nBytes, 32))
+ << ((const unsigned char*)
+ rInc.m_aData.getConstArray()+rInc.m_nBufferPos)
+ << "\"...");
+#endif
+ std::size_t nUnitSize = GetTrueFormatSize(rInc.m_nFormat);
+
+ XChangeProperty( m_pDisplay,
+ rInc.m_aRequestor,
+ rInc.m_aProperty,
+ rInc.m_aTarget,
+ rInc.m_nFormat,
+ PropModeReplace,
+ reinterpret_cast<const unsigned char*>(rInc.m_aData.getConstArray())+rInc.m_nBufferPos,
+ nBytes/nUnitSize );
+ rInc.m_nBufferPos += nBytes;
+ rInc.m_nTransferStartTime = nCurrentTime;
+
+ if( nBytes == 0 ) // transfer finished
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "finished INCR transfer for "
+ << "window "
+ << std::showbase << std::hex
+ << rInc.m_aRequestor
+ << ", property "
+ << getString( rInc.m_aProperty )
+ << ", type "
+ << getString( rInc.m_aTarget ));
+#endif
+ it->second.erase( inc_it );
+ }
+
+ }
+ // eventually clean up the hash map
+ if( it->second.empty() )
+ m_aIncrementals.erase( it );
+ }
+ }
+ return bHandled;
+}
+
+bool SelectionManager::handleSelectionNotify( XSelectionEvent const & rNotify )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+
+ bool bHandled = false;
+
+ // notification about success/failure of one of our conversion requests
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "handleSelectionNotify for selection "
+ << getString( rNotify.selection )
+ << " and property "
+ << (rNotify.property ? getString( rNotify.property ) : "None")
+ << " ("
+ << std::showbase << std::hex
+ << rNotify.property
+ << ").");
+ SAL_WARN_IF(rNotify.requestor != m_aWindow &&
+ rNotify.requestor != m_aCurrentDropWindow,
+ "vcl.unx.dtrans", "selection notify for unknown window "
+ << std::showbase << std::hex
+ << rNotify.requestor);
+#endif
+ std::unordered_map< Atom, Selection* >::iterator it =
+ m_aSelections.find( rNotify.selection );
+ if (
+ (rNotify.requestor == m_aWindow || rNotify.requestor == m_aCurrentDropWindow) &&
+ it != m_aSelections.end() &&
+ (
+ (it->second->m_eState == Selection::WaitingForResponse) ||
+ (it->second->m_eState == Selection::WaitingForData)
+ )
+ )
+ {
+ bHandled = true;
+ if( it->second->m_aRequestedType == m_nMULTIPLEAtom )
+ {
+ Atom nType = None;
+ int nFormat = 0;
+ unsigned long nItems = 0, nBytes = 0;
+ unsigned char* pData = nullptr;
+
+ // get type and length
+ XGetWindowProperty( m_pDisplay,
+ rNotify.requestor,
+ rNotify.property,
+ 0, 256,
+ False,
+ AnyPropertyType,
+ &nType, &nFormat,
+ &nItems, &nBytes,
+ &pData );
+ if( nBytes ) // HUGE request !!!
+ {
+ if( pData )
+ XFree( pData );
+ XGetWindowProperty( m_pDisplay,
+ rNotify.requestor,
+ rNotify.property,
+ 0, 256+(nBytes+3)/4,
+ False,
+ AnyPropertyType,
+ &nType, &nFormat,
+ &nItems, &nBytes,
+ &pData );
+ }
+ it->second->m_eState = Selection::Inactive;
+ std::size_t nUnitSize = GetTrueFormatSize(nFormat);
+ it->second->m_aData = Sequence< sal_Int8 >(reinterpret_cast<sal_Int8*>(pData), nItems * nUnitSize);
+ it->second->m_aDataArrived.set();
+ if( pData )
+ XFree( pData );
+ }
+ // WaitingForData can actually happen; some
+ // applications (e.g. cmdtool on Solaris) first send
+ // a success and then cancel it. Weird !
+ else if( rNotify.property == None )
+ {
+ // conversion failed, stop transfer
+ it->second->m_eState = Selection::Inactive;
+ it->second->m_aData = Sequence< sal_Int8 >();
+ it->second->m_aDataArrived.set();
+ }
+ // get the bytes, by INCR if necessary
+ else
+ it->second->m_eState = Selection::WaitingForData;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else if( it != m_aSelections.end() )
+ SAL_WARN("vcl.unx.dtrans", "selection in state " << it->second->m_eState);
+#endif
+ return bHandled;
+}
+
+bool SelectionManager::handleDropEvent( XClientMessageEvent const & rMessage )
+{
+ osl::ResettableMutexGuard aGuard(m_aMutex);
+
+ // handle drop related events
+ ::Window aSource = rMessage.data.l[0];
+ ::Window aTarget = rMessage.window;
+
+ bool bHandled = false;
+
+ std::unordered_map< ::Window, DropTargetEntry >::iterator it =
+ m_aDropTargets.find( aTarget );
+
+#if OSL_DEBUG_LEVEL > 1
+ if( rMessage.message_type == m_nXdndEnter ||
+ rMessage.message_type == m_nXdndLeave ||
+ rMessage.message_type == m_nXdndDrop ||
+ rMessage.message_type == m_nXdndPosition )
+ {
+ std::ostringstream oss;
+ oss << "got drop event "
+ << getString( rMessage.message_type )
+ << ", ";
+
+ if( it == m_aDropTargets.end() )
+ oss << "but no target found.";
+ else if( ! it->second.m_pTarget->m_bActive )
+ oss << "but target is inactive.";
+ else if( m_aDropEnterEvent.data.l[0] != None && (::Window)m_aDropEnterEvent.data.l[0] != aSource )
+ oss << "but source "
+ << std::showbase << std::hex
+ << aSource
+ << " is unknown (expected "
+ << m_aDropEnterEvent.data.l[0]
+ << " or 0).";
+ else
+ oss << "processing.";
+ SAL_INFO("vcl.unx.dtrans", oss.str());
+ }
+#endif
+
+ if( it != m_aDropTargets.end() && it->second.m_pTarget->m_bActive &&
+ m_bDropWaitingForCompletion && m_aDropEnterEvent.data.l[0] )
+ {
+ bHandled = true;
+ OSL_FAIL( "someone forgot to call dropComplete ?" );
+ // some listener forgot to call dropComplete in the last operation
+ // let us end it now and accept the new enter event
+ aGuard.clear();
+ dropComplete( false, m_aCurrentDropWindow );
+ aGuard.reset();
+ }
+
+ if( it != m_aDropTargets.end() &&
+ it->second.m_pTarget->m_bActive &&
+ ( m_aDropEnterEvent.data.l[0] == None || ::Window(m_aDropEnterEvent.data.l[0]) == aSource )
+ )
+ {
+ if( rMessage.message_type == m_nXdndEnter )
+ {
+ bHandled = true;
+ m_aDropEnterEvent = rMessage;
+ m_bDropEnterSent = false;
+ m_aCurrentDropWindow = aTarget;
+ m_nCurrentProtocolVersion = m_aDropEnterEvent.data.l[1] >> 24;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "received XdndEnter on "
+ << std::showbase << std::hex
+ << aTarget);
+#endif
+ }
+ else if(
+ rMessage.message_type == m_nXdndPosition &&
+ aSource == ::Window(m_aDropEnterEvent.data.l[0])
+ )
+ {
+ bHandled = true;
+ m_nDropTime = m_nCurrentProtocolVersion > 0 ? rMessage.data.l[3] : CurrentTime;
+
+ ::Window aChild;
+ XTranslateCoordinates( m_pDisplay,
+ it->second.m_aRootWindow,
+ it->first,
+ rMessage.data.l[2] >> 16,
+ rMessage.data.l[2] & 0xffff,
+ &m_nLastX, &m_nLastY,
+ &aChild );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "received XdndPosition on "
+ << std::showbase << std::hex
+ << aTarget
+ << " ("
+ << std::dec
+ << m_nLastX
+ << ", "
+ << m_nLastY
+ << ").");
+#endif
+ DropTargetDragEnterEvent aEvent;
+ aEvent.Source = static_cast< XDropTarget* >(it->second.m_pTarget);
+ aEvent.Context = new DropTargetDragContext( m_aCurrentDropWindow, *this );
+ aEvent.LocationX = m_nLastX;
+ aEvent.LocationY = m_nLastY;
+ aEvent.SourceActions = m_nSourceActions;
+ if( m_nCurrentProtocolVersion < 2 )
+ aEvent.DropAction = DNDConstants::ACTION_COPY;
+ else if( Atom(rMessage.data.l[4]) == m_nXdndActionCopy )
+ aEvent.DropAction = DNDConstants::ACTION_COPY;
+ else if( Atom(rMessage.data.l[4]) == m_nXdndActionMove )
+ aEvent.DropAction = DNDConstants::ACTION_MOVE;
+ else if( Atom(rMessage.data.l[4]) == m_nXdndActionLink )
+ aEvent.DropAction = DNDConstants::ACTION_LINK;
+ else if( Atom(rMessage.data.l[4]) == m_nXdndActionAsk )
+ // currently no interface to implement ask
+ aEvent.DropAction = ~0;
+ else
+ aEvent.DropAction = DNDConstants::ACTION_NONE;
+
+ m_nLastDropAction = aEvent.DropAction;
+ if( ! m_bDropEnterSent )
+ {
+ m_bDropEnterSent = true;
+ aEvent.SupportedDataFlavors = m_xDropTransferable->getTransferDataFlavors();
+ aGuard.clear();
+ it->second->dragEnter( aEvent );
+ }
+ else
+ {
+ aGuard.clear();
+ it->second->dragOver( aEvent );
+ }
+ }
+ else if(
+ rMessage.message_type == m_nXdndLeave &&
+ aSource == ::Window(m_aDropEnterEvent.data.l[0])
+ )
+ {
+ bHandled = true;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "received XdndLeave on "
+ << std::showbase << std::hex
+ << aTarget);
+#endif
+ DropTargetEvent aEvent;
+ aEvent.Source = static_cast< XDropTarget* >(it->second.m_pTarget);
+ m_aDropEnterEvent.data.l[0] = None;
+ if( m_aCurrentDropWindow == aTarget )
+ m_aCurrentDropWindow = None;
+ m_nCurrentProtocolVersion = nXdndProtocolRevision;
+ aGuard.clear();
+ it->second->dragExit( aEvent );
+ }
+ else if(
+ rMessage.message_type == m_nXdndDrop &&
+ aSource == ::Window(m_aDropEnterEvent.data.l[0])
+ )
+ {
+ bHandled = true;
+ m_nDropTime = m_nCurrentProtocolVersion > 0 ? rMessage.data.l[2] : CurrentTime;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "received XdndDrop on "
+ << std::showbase << std::hex
+ << aTarget
+ << " ("
+ << m_nLastX
+ << ", "
+ << m_nLastY
+ << ").");
+#endif
+ if( m_bLastDropAccepted )
+ {
+ DropTargetDropEvent aEvent;
+ aEvent.Source = static_cast< XDropTarget* >(it->second.m_pTarget);
+ aEvent.Context = new DropTargetDropContext( m_aCurrentDropWindow, *this );
+ aEvent.LocationX = m_nLastX;
+ aEvent.LocationY = m_nLastY;
+ aEvent.DropAction = m_nLastDropAction;
+ // there is nothing corresponding to source supported actions
+ // every source can do link, copy and move
+ aEvent.SourceActions= m_nLastDropAction;
+ aEvent.Transferable = m_xDropTransferable;
+
+ m_bDropWaitingForCompletion = true;
+ aGuard.clear();
+ it->second->drop( aEvent );
+ }
+ else
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "XdndDrop canceled due to "
+ << "m_bLastDropAccepted = false." );
+#endif
+ DropTargetEvent aEvent;
+ aEvent.Source = static_cast< XDropTarget* >(it->second.m_pTarget);
+ aGuard.clear();
+ it->second->dragExit( aEvent );
+ // reset the drop status, notify source
+ dropComplete( false, m_aCurrentDropWindow );
+ }
+ }
+ }
+ return bHandled;
+}
+
+/*
+ * methods for XDropTargetDropContext
+ */
+
+void SelectionManager::dropComplete( bool bSuccess, ::Window aDropWindow )
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ if( aDropWindow == m_aCurrentDropWindow )
+ {
+ if( m_xDragSourceListener.is() )
+ {
+ DragSourceDropEvent dsde;
+ dsde.Source = static_cast< OWeakObject* >(this);
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = getUserDragAction();
+ dsde.DropSuccess = bSuccess;
+ css::uno::Reference< XDragSourceListener > xListener = m_xDragSourceListener;
+ m_xDragSourceListener.clear();
+
+ aGuard.clear();
+ xListener->dragDropEnd( dsde );
+ }
+ else if( m_aDropEnterEvent.data.l[0] && m_aCurrentDropWindow )
+ {
+ XEvent aEvent;
+ aEvent.xclient.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.window = m_aDropEnterEvent.data.l[0];
+ aEvent.xclient.message_type = m_nXdndFinished;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = m_aCurrentDropWindow;
+ aEvent.xclient.data.l[1] = bSuccess ? 1 : 0;
+ aEvent.xclient.data.l[2] = 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+ if( bSuccess )
+ {
+ if( m_nLastDropAction & DNDConstants::ACTION_MOVE )
+ aEvent.xclient.data.l[2] = m_nXdndActionMove;
+ else if( m_nLastDropAction & DNDConstants::ACTION_COPY )
+ aEvent.xclient.data.l[2] = m_nXdndActionCopy;
+ else if( m_nLastDropAction & DNDConstants::ACTION_LINK )
+ aEvent.xclient.data.l[2] = m_nXdndActionLink;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "Sending XdndFinished to "
+ << std::showbase << std::hex
+ << m_aDropEnterEvent.data.l[0]);
+#endif
+ XSendEvent( m_pDisplay, m_aDropEnterEvent.data.l[0],
+ False, NoEventMask, & aEvent );
+
+ m_aDropEnterEvent.data.l[0] = None;
+ m_aCurrentDropWindow = None;
+ m_nCurrentProtocolVersion = nXdndProtocolRevision;
+ }
+ m_bDropWaitingForCompletion = false;
+ }
+ else
+ OSL_FAIL( "dropComplete from invalid DropTargetDropContext" );
+}
+
+/*
+ * methods for XDropTargetDragContext
+ */
+
+void SelectionManager::sendDragStatus( Atom nDropAction )
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ if( m_xDragSourceListener.is() )
+ {
+ sal_Int8 nNewDragAction;
+ if( nDropAction == m_nXdndActionMove )
+ nNewDragAction = DNDConstants::ACTION_MOVE;
+ else if( nDropAction == m_nXdndActionCopy )
+ nNewDragAction = DNDConstants::ACTION_COPY;
+ else if( nDropAction == m_nXdndActionLink )
+ nNewDragAction = DNDConstants::ACTION_LINK;
+ else
+ nNewDragAction = DNDConstants::ACTION_NONE;
+ nNewDragAction &= m_nSourceActions;
+
+ if( nNewDragAction != m_nTargetAcceptAction )
+ {
+ setCursor( getDefaultCursor( nNewDragAction ), m_aDropWindow );
+ m_nTargetAcceptAction = nNewDragAction;
+ }
+
+ DragSourceDragEvent dsde;
+ dsde.Source = static_cast< OWeakObject* >(this);
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = m_nSourceActions;
+ dsde.UserAction = getUserDragAction();
+
+ css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener );
+ // caution: do not change anything after this
+ aGuard.clear();
+ if( xListener.is() )
+ xListener->dragOver( dsde );
+ }
+ else if( m_aDropEnterEvent.data.l[0] && m_aCurrentDropWindow )
+ {
+ XEvent aEvent;
+ aEvent.xclient.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.window = m_aDropEnterEvent.data.l[0];
+ aEvent.xclient.message_type = m_nXdndStatus;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = m_aCurrentDropWindow;
+ aEvent.xclient.data.l[1] = 2;
+ if( nDropAction == m_nXdndActionMove ||
+ nDropAction == m_nXdndActionLink ||
+ nDropAction == m_nXdndActionCopy )
+ aEvent.xclient.data.l[1] |= 1;
+ aEvent.xclient.data.l[2] = 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = m_nCurrentProtocolVersion > 1 ? nDropAction : 0;
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "Sending XdndStatus to "
+ << std::showbase << std::hex
+ << m_aDropEnterEvent.data.l[0]
+ << " with action "
+ << getString( nDropAction ));
+#endif
+
+ XSendEvent( m_pDisplay, m_aDropEnterEvent.data.l[0],
+ False, NoEventMask, & aEvent );
+ XFlush( m_pDisplay );
+ }
+}
+
+sal_Int8 SelectionManager::getUserDragAction() const
+{
+ return (m_nTargetAcceptAction != DNDConstants::ACTION_DEFAULT) ? m_nTargetAcceptAction : m_nUserDragAction;
+}
+
+bool SelectionManager::updateDragAction( int modifierState )
+{
+ bool bRet = false;
+
+ sal_Int8 nNewDropAction = DNDConstants::ACTION_MOVE;
+ if( ( modifierState & ShiftMask ) && ! ( modifierState & ControlMask ) )
+ nNewDropAction = DNDConstants::ACTION_MOVE;
+ else if( ( modifierState & ControlMask ) && ! ( modifierState & ShiftMask ) )
+ nNewDropAction = DNDConstants::ACTION_COPY;
+ else if( ( modifierState & ShiftMask ) && ( modifierState & ControlMask ) )
+ nNewDropAction = DNDConstants::ACTION_LINK;
+ if( m_nCurrentProtocolVersion < 0 && m_aDropWindow != None )
+ nNewDropAction = DNDConstants::ACTION_COPY;
+ nNewDropAction &= m_nSourceActions;
+
+ if( ! ( modifierState & ( ControlMask | ShiftMask ) ) )
+ {
+ if( ! nNewDropAction )
+ {
+ // default to an action so the user does not have to press
+ // keys explicitly
+ if( m_nSourceActions & DNDConstants::ACTION_MOVE )
+ nNewDropAction = DNDConstants::ACTION_MOVE;
+ else if( m_nSourceActions & DNDConstants::ACTION_COPY )
+ nNewDropAction = DNDConstants::ACTION_COPY;
+ else if( m_nSourceActions & DNDConstants::ACTION_LINK )
+ nNewDropAction = DNDConstants::ACTION_LINK;
+ }
+ nNewDropAction |= DNDConstants::ACTION_DEFAULT;
+ }
+
+ if( nNewDropAction != m_nUserDragAction || m_nTargetAcceptAction != DNDConstants::ACTION_DEFAULT )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "updateDragAction: "
+ << std::hex
+ << (int)m_nUserDragAction
+ << " -> "
+ << (int)nNewDropAction);
+#endif
+ bRet = true;
+ m_nUserDragAction = nNewDropAction;
+
+ DragSourceDragEvent dsde;
+ dsde.Source = static_cast< OWeakObject* >(this);
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = m_nUserDragAction;
+ dsde.UserAction = m_nUserDragAction;
+ m_nTargetAcceptAction = DNDConstants::ACTION_DEFAULT; // invalidate last accept
+ m_xDragSourceListener->dropActionChanged( dsde );
+ }
+ return bRet;
+}
+
+void SelectionManager::sendDropPosition( bool bForce, Time eventTime )
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ if( m_bDropSent )
+ return;
+
+ std::unordered_map< ::Window, DropTargetEntry >::const_iterator it =
+ m_aDropTargets.find( m_aDropWindow );
+ if( it != m_aDropTargets.end() )
+ {
+ if( it->second.m_pTarget->m_bActive )
+ {
+ int x, y;
+ ::Window aChild;
+ XTranslateCoordinates( m_pDisplay, it->second.m_aRootWindow, m_aDropWindow, m_nLastDragX, m_nLastDragY, &x, &y, &aChild );
+ DropTargetDragEvent dtde;
+ dtde.Source = static_cast< OWeakObject* >(it->second.m_pTarget );
+ dtde.Context = new DropTargetDragContext( m_aCurrentDropWindow, *this );
+ dtde.LocationX = x;
+ dtde.LocationY = y;
+ dtde.DropAction = getUserDragAction();
+ dtde.SourceActions = m_nSourceActions;
+ aGuard.clear();
+ it->second->dragOver( dtde );
+ }
+ }
+ else if( bForce ||
+
+ m_nLastDragX < m_nNoPosX || m_nLastDragX >= m_nNoPosX+m_nNoPosWidth ||
+ m_nLastDragY < m_nNoPosY || m_nLastDragY >= m_nNoPosY+m_nNoPosHeight
+ )
+ {
+ // send XdndPosition
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.message_type = m_nXdndPosition;
+ aEvent.xclient.window = m_aDropWindow;
+ aEvent.xclient.data.l[0] = m_aWindow;
+ aEvent.xclient.data.l[1] = 0;
+ aEvent.xclient.data.l[2] = m_nLastDragX << 16 | (m_nLastDragY&0xffff);
+ aEvent.xclient.data.l[3] = eventTime;
+
+ if( m_nUserDragAction & DNDConstants::ACTION_COPY )
+ aEvent.xclient.data.l[4]=m_nXdndActionCopy;
+ else if( m_nUserDragAction & DNDConstants::ACTION_MOVE )
+ aEvent.xclient.data.l[4]=m_nXdndActionMove;
+ else if( m_nUserDragAction & DNDConstants::ACTION_LINK )
+ aEvent.xclient.data.l[4]=m_nXdndActionLink;
+ else
+ aEvent.xclient.data.l[4]=m_nXdndActionCopy;
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+ m_nNoPosX = m_nNoPosY = m_nNoPosWidth = m_nNoPosHeight = 0;
+ }
+}
+
+bool SelectionManager::handleDragEvent( XEvent const & rMessage )
+{
+ if( ! m_xDragSourceListener.is() )
+ return false;
+
+ osl::ResettableMutexGuard aGuard(m_aMutex);
+
+ bool bHandled = false;
+
+ // for shortcut
+ std::unordered_map< ::Window, DropTargetEntry >::const_iterator it =
+ m_aDropTargets.find( m_aDropWindow );
+
+#if OSL_DEBUG_LEVEL > 1
+ switch( rMessage.type )
+ {
+ case ClientMessage:
+ SAL_INFO("vcl.unx.dtrans", "handleDragEvent: "
+ << getString( rMessage.xclient.message_type ));
+ break;
+ case MotionNotify:
+ break;
+ case EnterNotify:
+ SAL_INFO("vcl.unx.dtrans", "handleDragEvent: EnterNotify.");
+ break;
+ case LeaveNotify:
+ SAL_INFO("vcl.unx.dtrans", "handleDragEvent: LeaveNotify.");
+ break;
+ case ButtonPress:
+ SAL_INFO("vcl.unx.dtrans", "handleDragEvent: ButtonPress "
+ << rMessage.xbutton.button
+ << " (m_nDragButton = "
+ << m_nDragButton
+ << ").");
+ break;
+ case ButtonRelease:
+ SAL_INFO("vcl.unx.dtrans", "handleDragEvent: ButtonRelease "
+ << rMessage.xbutton.button
+ << " (m_nDragButton = "
+ << m_nDragButton
+ << ").");
+ break;
+ case KeyPress:
+ SAL_INFO("vcl.unx.dtrans", "handleDragEvent: KeyPress.");
+ break;
+ case KeyRelease:
+ SAL_INFO("vcl.unx.dtrans", "handleDragEvent: KeyRelease.");
+ break;
+ default:
+ SAL_INFO("vcl.unx.dtrans", "handleDragEvent: <unknown type "
+ << rMessage.type
+ << ">.");
+ break;
+ }
+#endif
+
+ // handle drag related events
+ if( rMessage.type == ClientMessage )
+ {
+ if( rMessage.xclient.message_type == m_nXdndStatus && Atom(rMessage.xclient.data.l[0]) == m_aDropWindow )
+ {
+ bHandled = true;
+ DragSourceDragEvent dsde;
+ dsde.Source = static_cast< OWeakObject* >(this);
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >( this );
+ dsde.UserAction = getUserDragAction();
+ dsde.DropAction = DNDConstants::ACTION_NONE;
+ m_bDropSuccess = (rMessage.xclient.data.l[1] & 1) != 0;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "status drop action: accept = "
+ << (m_bDropSuccess ? "true" : "false")
+ << ", "
+ << getString( rMessage.xclient.data.l[4] ));
+#endif
+ if( rMessage.xclient.data.l[1] & 1 )
+ {
+ if( m_nCurrentProtocolVersion > 1 )
+ {
+ if( Atom(rMessage.xclient.data.l[4]) == m_nXdndActionCopy )
+ dsde.DropAction = DNDConstants::ACTION_COPY;
+ else if( Atom(rMessage.xclient.data.l[4]) == m_nXdndActionMove )
+ dsde.DropAction = DNDConstants::ACTION_MOVE;
+ else if( Atom(rMessage.xclient.data.l[4]) == m_nXdndActionLink )
+ dsde.DropAction = DNDConstants::ACTION_LINK;
+ }
+ else
+ dsde.DropAction = DNDConstants::ACTION_COPY;
+ }
+ m_nTargetAcceptAction = dsde.DropAction;
+
+ if( ! ( rMessage.xclient.data.l[1] & 2 ) )
+ {
+ m_nNoPosX = rMessage.xclient.data.l[2] >> 16;
+ m_nNoPosY = rMessage.xclient.data.l[2] & 0xffff;
+ m_nNoPosWidth = rMessage.xclient.data.l[3] >> 16;
+ m_nNoPosHeight = rMessage.xclient.data.l[3] & 0xffff;
+ }
+ else
+ m_nNoPosX = m_nNoPosY = m_nNoPosWidth = m_nNoPosHeight = 0;
+
+ setCursor( getDefaultCursor( dsde.DropAction ), m_aDropWindow );
+ aGuard.clear();
+ m_xDragSourceListener->dragOver( dsde );
+ }
+ else if( rMessage.xclient.message_type == m_nXdndFinished && m_aDropWindow == Atom(rMessage.xclient.data.l[0]) )
+ {
+ bHandled = true;
+ // notify the listener
+ DragSourceDropEvent dsde;
+ dsde.Source = static_cast< OWeakObject* >(this);
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = m_nTargetAcceptAction;
+ dsde.DropSuccess = m_bDropSuccess;
+ css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener );
+ m_xDragSourceListener.clear();
+ aGuard.clear();
+ xListener->dragDropEnd( dsde );
+ }
+ }
+ else if( rMessage.type == MotionNotify ||
+ rMessage.type == EnterNotify || rMessage.type == LeaveNotify
+ )
+ {
+ bHandled = true;
+ bool bForce = false;
+ int root_x = rMessage.type == MotionNotify ? rMessage.xmotion.x_root : rMessage.xcrossing.x_root;
+ int root_y = rMessage.type == MotionNotify ? rMessage.xmotion.y_root : rMessage.xcrossing.y_root;
+ ::Window root = rMessage.type == MotionNotify ? rMessage.xmotion.root : rMessage.xcrossing.root;
+
+ aGuard.clear();
+ if( rMessage.type == MotionNotify )
+ {
+ bForce = updateDragAction( rMessage.xmotion.state );
+ }
+ updateDragWindow( root_x, root_y, root );
+ aGuard.reset();
+
+ if( m_nCurrentProtocolVersion >= 0 && m_aDropProxy != None )
+ {
+ aGuard.clear();
+ sendDropPosition( bForce, rMessage.type == MotionNotify ? rMessage.xmotion.time : rMessage.xcrossing.time );
+ }
+ }
+ else if( rMessage.type == KeyPress || rMessage.type == KeyRelease )
+ {
+ bHandled = true;
+ KeySym aKey = XkbKeycodeToKeysym( m_pDisplay, rMessage.xkey.keycode, 0, 0 );
+ if( aKey == XK_Escape )
+ {
+ // abort drag
+ if( it != m_aDropTargets.end() )
+ {
+ DropTargetEvent dte;
+ dte.Source = static_cast< OWeakObject* >( it->second.m_pTarget );
+ aGuard.clear();
+ it->second.m_pTarget->dragExit( dte );
+ aGuard.reset();
+ }
+ else if( m_aDropProxy != None && m_nCurrentProtocolVersion >= 0 )
+ {
+ // send XdndLeave
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.message_type = m_nXdndLeave;
+ aEvent.xclient.window = m_aDropWindow;
+ aEvent.xclient.data.l[0] = m_aWindow;
+ memset( aEvent.xclient.data.l+1, 0, sizeof(long)*4);
+ m_aDropWindow = m_aDropProxy = None;
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+ }
+ // notify the listener
+ DragSourceDropEvent dsde;
+ dsde.Source = static_cast< OWeakObject* >(this);
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = DNDConstants::ACTION_NONE;
+ dsde.DropSuccess = false;
+ css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener );
+ m_xDragSourceListener.clear();
+ aGuard.clear();
+ xListener->dragDropEnd( dsde );
+ }
+ else
+ {
+ /*
+ * man page says: state is state immediate PRIOR to the
+ * event. It would seem that this is a somewhat arguable
+ * design decision.
+ */
+ int nState = rMessage.xkey.state;
+ int nNewState = 0;
+ switch( aKey )
+ {
+ case XK_Shift_R:
+ case XK_Shift_L: nNewState = ShiftMask;break;
+ case XK_Control_R:
+ case XK_Control_L: nNewState = ControlMask;break;
+ // just interested in shift and ctrl for dnd
+ }
+ if( rMessage.type == KeyPress )
+ nState |= nNewState;
+ else
+ nState &= ~nNewState;
+ aGuard.clear();
+ if( updateDragAction( nState ) )
+ sendDropPosition( true, rMessage.xkey.time );
+ }
+ }
+ else if(
+ ( rMessage.type == ButtonPress || rMessage.type == ButtonRelease ) &&
+ rMessage.xbutton.button == m_nDragButton )
+ {
+ bool bCancel = true;
+ if( m_aDropWindow != None )
+ {
+ if( it != m_aDropTargets.end() )
+ {
+ if( it->second.m_pTarget->m_bActive && m_nUserDragAction != DNDConstants::ACTION_NONE && m_bLastDropAccepted )
+ {
+ bHandled = true;
+ int x, y;
+ ::Window aChild;
+ XTranslateCoordinates( m_pDisplay, rMessage.xbutton.root, m_aDropWindow, rMessage.xbutton.x_root, rMessage.xbutton.y_root, &x, &y, &aChild );
+ DropTargetDropEvent dtde;
+ dtde.Source = static_cast< OWeakObject* >(it->second.m_pTarget );
+ dtde.Context = new DropTargetDropContext( m_aCurrentDropWindow, *this );
+ dtde.LocationX = x;
+ dtde.LocationY = y;
+ dtde.DropAction = m_nUserDragAction;
+ dtde.SourceActions = m_nSourceActions;
+ dtde.Transferable = m_xDragSourceTransferable;
+ m_bDropSent = true;
+ m_nDropTimeout = time( nullptr );
+ m_bDropWaitingForCompletion = true;
+ aGuard.clear();
+ it->second->drop( dtde );
+ bCancel = false;
+ }
+ else bCancel = true;
+ }
+ else if( m_nCurrentProtocolVersion >= 0 )
+ {
+ bHandled = true;
+
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.message_type = m_nXdndDrop;
+ aEvent.xclient.window = m_aDropWindow;
+ aEvent.xclient.data.l[0] = m_aWindow;
+ aEvent.xclient.data.l[1] = 0;
+ aEvent.xclient.data.l[2] = rMessage.xbutton.time;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+
+ m_bDropSent = true;
+ m_nDropTimeout = time( nullptr );
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+ bCancel = false;
+ }
+ else
+ {
+ // dropping on non XdndWindows: acquire ownership of
+ // PRIMARY and send a middle mouse button click down/up to
+ // target window
+ SelectionAdaptor* pAdaptor = getAdaptor( XA_PRIMARY );
+ if( pAdaptor )
+ {
+ bHandled = true;
+
+ ::Window aDummy;
+ XEvent aEvent;
+ aEvent.type = ButtonPress;
+ aEvent.xbutton.display = m_pDisplay;
+ aEvent.xbutton.window = m_aDropWindow;
+ aEvent.xbutton.root = rMessage.xbutton.root;
+ aEvent.xbutton.subwindow = m_aDropWindow;
+ aEvent.xbutton.time = rMessage.xbutton.time+1;
+ aEvent.xbutton.x_root = rMessage.xbutton.x_root;
+ aEvent.xbutton.y_root = rMessage.xbutton.y_root;
+ aEvent.xbutton.state = rMessage.xbutton.state;
+ aEvent.xbutton.button = Button2;
+ aEvent.xbutton.same_screen = True;
+ XTranslateCoordinates( m_pDisplay,
+ rMessage.xbutton.root, m_aDropWindow,
+ rMessage.xbutton.x_root, rMessage.xbutton.y_root,
+ &aEvent.xbutton.x, &aEvent.xbutton.y,
+ &aDummy );
+ XSendEvent( m_pDisplay, m_aDropWindow, False, ButtonPressMask, &aEvent );
+ aEvent.xbutton.type = ButtonRelease;
+ aEvent.xbutton.time++;
+ aEvent.xbutton.state |= Button2Mask;
+ XSendEvent( m_pDisplay, m_aDropWindow, False, ButtonReleaseMask, &aEvent );
+
+ m_bDropSent = true;
+ m_nDropTimeout = time( nullptr );
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+ m_bWaitingForPrimaryConversion = true;
+ m_bDropSent = true;
+ m_nDropTimeout = time( nullptr );
+ // HACK :-)
+ aGuard.clear();
+ static_cast< X11Clipboard* >( pAdaptor )->setContents( m_xDragSourceTransferable, css::uno::Reference< css::datatransfer::clipboard::XClipboardOwner >() );
+ aGuard.reset();
+ bCancel = false;
+ }
+ }
+ }
+ if( bCancel )
+ {
+ // cancel drag
+ DragSourceDropEvent dsde;
+ dsde.Source = static_cast< OWeakObject* >(this);
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = DNDConstants::ACTION_NONE;
+ dsde.DropSuccess = false;
+ css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener );
+ m_xDragSourceListener.clear();
+ aGuard.clear();
+ xListener->dragDropEnd( dsde );
+ bHandled = true;
+ }
+ }
+ return bHandled;
+}
+
+void SelectionManager::accept( sal_Int8 dragOperation, ::Window aDropWindow )
+{
+ if( aDropWindow != m_aCurrentDropWindow )
+ return;
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "accept: " << std::hex << dragOperation);
+#endif
+ Atom nAction = None;
+ dragOperation &= (DNDConstants::ACTION_MOVE | DNDConstants::ACTION_COPY | DNDConstants::ACTION_LINK);
+ if( dragOperation & DNDConstants::ACTION_MOVE )
+ nAction = m_nXdndActionMove;
+ else if( dragOperation & DNDConstants::ACTION_COPY )
+ nAction = m_nXdndActionCopy;
+ else if( dragOperation & DNDConstants::ACTION_LINK )
+ nAction = m_nXdndActionLink;
+ m_bLastDropAccepted = true;
+ sendDragStatus( nAction );
+}
+
+void SelectionManager::reject( ::Window aDropWindow )
+{
+ if( aDropWindow != m_aCurrentDropWindow )
+ return;
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "reject.");
+#endif
+ m_bLastDropAccepted = false;
+ sendDragStatus( None );
+ if( m_bDropSent && m_xDragSourceListener.is() )
+ {
+ DragSourceDropEvent dsde;
+ dsde.Source = static_cast< OWeakObject* >(this);
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = DNDConstants::ACTION_NONE;
+ dsde.DropSuccess = false;
+ m_xDragSourceListener->dragDropEnd( dsde );
+ m_xDragSourceListener.clear();
+ }
+}
+
+/*
+ * XDragSource
+ */
+
+sal_Bool SelectionManager::isDragImageSupported()
+{
+ return false;
+}
+
+sal_Int32 SelectionManager::getDefaultCursor( sal_Int8 dragAction )
+{
+ Cursor aCursor = m_aNoneCursor;
+ if( dragAction & DNDConstants::ACTION_MOVE )
+ aCursor = m_aMoveCursor;
+ else if( dragAction & DNDConstants::ACTION_COPY )
+ aCursor = m_aCopyCursor;
+ else if( dragAction & DNDConstants::ACTION_LINK )
+ aCursor = m_aLinkCursor;
+ return aCursor;
+}
+
+int SelectionManager::getXdndVersion( ::Window aWindow, ::Window& rProxy )
+{
+ Atom* pProperties = nullptr;
+ int nProperties = 0;
+ Atom nType;
+ int nFormat;
+ unsigned long nItems, nBytes;
+ unsigned char* pBytes = nullptr;
+
+ int nVersion = -1;
+ rProxy = None;
+
+ /*
+ * XListProperties is used here to avoid unnecessary XGetWindowProperty calls
+ * and therefore reducing latency penalty
+ */
+ pProperties = XListProperties( m_pDisplay, aWindow, &nProperties );
+ // first look for proxy
+ int i;
+ for( i = 0; i < nProperties; i++ )
+ {
+ if( pProperties[i] == m_nXdndProxy )
+ {
+ XGetWindowProperty( m_pDisplay, aWindow, m_nXdndProxy, 0, 1, False, XA_WINDOW,
+ &nType, &nFormat, &nItems, &nBytes, &pBytes );
+ if( pBytes )
+ {
+ if( nType == XA_WINDOW )
+ rProxy = *reinterpret_cast< ::Window* >(pBytes);
+ XFree( pBytes );
+ pBytes = nullptr;
+ if( rProxy != None )
+ {
+ // now check proxy whether it points to itself
+ XGetWindowProperty( m_pDisplay, rProxy, m_nXdndProxy, 0, 1, False, XA_WINDOW,
+ &nType, &nFormat, &nItems, &nBytes, &pBytes );
+ if( pBytes )
+ {
+ if( nType == XA_WINDOW && *reinterpret_cast< ::Window* >(pBytes) != rProxy )
+ rProxy = None;
+ XFree( pBytes );
+ pBytes = nullptr;
+ }
+ else
+ rProxy = None;
+ }
+ }
+ break;
+ }
+ }
+ if ( pProperties )
+ XFree (pProperties);
+
+ ::Window aAwareWindow = rProxy != None ? rProxy : aWindow;
+
+ XGetWindowProperty( m_pDisplay, aAwareWindow, m_nXdndAware, 0, 1, False, XA_ATOM,
+ &nType, &nFormat, &nItems, &nBytes, &pBytes );
+ if( pBytes )
+ {
+ if( nType == XA_ATOM )
+ nVersion = *reinterpret_cast<Atom*>(pBytes);
+ XFree( pBytes );
+ }
+
+ nVersion = std::min<int>(nVersion, nXdndProtocolRevision);
+
+ return nVersion;
+}
+
+void SelectionManager::updateDragWindow( int nX, int nY, ::Window aRoot )
+{
+ osl::ResettableMutexGuard aGuard( m_aMutex );
+
+ css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener );
+
+ m_nLastDragX = nX;
+ m_nLastDragY = nY;
+
+ ::Window aParent = aRoot;
+ ::Window aChild;
+ ::Window aNewProxy = None, aNewCurrentWindow = None;
+ int nNewProtocolVersion = -1;
+ int nWinX, nWinY;
+
+ // find the first XdndAware window or check if root window is
+ // XdndAware or has XdndProxy
+ do
+ {
+ XTranslateCoordinates( m_pDisplay, aRoot, aParent, nX, nY, &nWinX, &nWinY, &aChild );
+ if( aChild != None )
+ {
+ if( aChild == m_aCurrentDropWindow && aChild != aRoot && m_nCurrentProtocolVersion >= 0 )
+ {
+ aParent = aChild;
+ break;
+ }
+ nNewProtocolVersion = getXdndVersion( aChild, aNewProxy );
+ aParent = aChild;
+ }
+ } while( aChild != None && nNewProtocolVersion < 0 );
+
+ aNewCurrentWindow = aParent;
+ if( aNewCurrentWindow == aRoot )
+ {
+ // no children, try root drop
+ nNewProtocolVersion = getXdndVersion( aNewCurrentWindow, aNewProxy );
+ if( nNewProtocolVersion < 3 )
+ {
+ aNewCurrentWindow = aNewProxy = None;
+ nNewProtocolVersion = nXdndProtocolRevision;
+ }
+ }
+
+ DragSourceDragEvent dsde;
+ dsde.Source = static_cast< OWeakObject* >(this);
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = nNewProtocolVersion >= 0 ? m_nUserDragAction : DNDConstants::ACTION_COPY;
+ dsde.UserAction = nNewProtocolVersion >= 0 ? m_nUserDragAction : DNDConstants::ACTION_COPY;
+
+ std::unordered_map< ::Window, DropTargetEntry >::const_iterator it;
+ if( aNewCurrentWindow != m_aDropWindow )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "drag left window "
+ << std::showbase << std::hex
+ << m_aDropWindow
+ << std::dec
+ << " (rev. "
+ << m_nCurrentProtocolVersion
+ << "), entered window "
+ << std::showbase << std::hex
+ << aNewCurrentWindow
+ << " (rev "
+ << std::dec
+ << nNewProtocolVersion
+ << ").");
+#endif
+ if( m_aDropWindow != None )
+ {
+ it = m_aDropTargets.find( m_aDropWindow );
+ if( it != m_aDropTargets.end() )
+ // shortcut for own drop targets
+ {
+ DropTargetEvent dte;
+ dte.Source = static_cast< OWeakObject* >( it->second.m_pTarget );
+ aGuard.clear();
+ it->second.m_pTarget->dragExit( dte );
+ aGuard.reset();
+ }
+ else
+ {
+ // send old drop target a XdndLeave
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.message_type = m_nXdndLeave;
+ aEvent.xclient.window = m_aDropWindow;
+ aEvent.xclient.data.l[0] = m_aWindow;
+ aEvent.xclient.data.l[1] = 0;
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+ }
+ if( xListener.is() )
+ {
+ aGuard.clear();
+ xListener->dragExit( dsde );
+ aGuard.reset();
+ }
+ }
+
+ m_nCurrentProtocolVersion = nNewProtocolVersion;
+ m_aDropWindow = aNewCurrentWindow;
+ m_aDropProxy = aNewProxy != None ? aNewProxy : m_aDropWindow;
+
+ it = m_aDropTargets.find( m_aDropWindow );
+ if( it != m_aDropTargets.end() && ! it->second.m_pTarget->m_bActive )
+ m_aDropProxy = None;
+
+ if( m_aDropProxy != None && xListener.is() )
+ {
+ aGuard.clear();
+ xListener->dragEnter( dsde );
+ aGuard.reset();
+ }
+ // send XdndEnter
+ if( m_aDropProxy != None && m_nCurrentProtocolVersion >= 0 )
+ {
+ it = m_aDropTargets.find( m_aDropWindow );
+ if( it != m_aDropTargets.end() )
+ {
+ XTranslateCoordinates( m_pDisplay, aRoot, m_aDropWindow, nX, nY, &nWinX, &nWinY, &aChild );
+ DropTargetDragEnterEvent dtde;
+ dtde.Source = static_cast< OWeakObject* >( it->second.m_pTarget );
+ dtde.Context = new DropTargetDragContext( m_aCurrentDropWindow, *this );
+ dtde.LocationX = nWinX;
+ dtde.LocationY = nWinY;
+ dtde.DropAction = m_nUserDragAction;
+ dtde.SourceActions = m_nSourceActions;
+ dtde.SupportedDataFlavors = m_xDragSourceTransferable->getTransferDataFlavors();
+ aGuard.clear();
+ it->second.m_pTarget->dragEnter( dtde );
+ aGuard.reset();
+ }
+ else
+ {
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.message_type = m_nXdndEnter;
+ aEvent.xclient.window = m_aDropWindow;
+ aEvent.xclient.data.l[0] = m_aWindow;
+ aEvent.xclient.data.l[1] = m_nCurrentProtocolVersion << 24;
+ memset( aEvent.xclient.data.l + 2, 0, sizeof( long )*3 );
+ // fill in data types
+ ::std::list< Atom > aConversions;
+ getNativeTypeList( m_aDragFlavors, aConversions, m_nXdndSelection );
+ if( aConversions.size() > 3 )
+ aEvent.xclient.data.l[1] |= 1;
+ ::std::list< Atom >::const_iterator type_it = aConversions.begin();
+ for( int i = 0; type_it != aConversions.end() && i < 3; i++, ++type_it )
+ aEvent.xclient.data.l[i+2] = *type_it;
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+ }
+ }
+ m_nNoPosX = m_nNoPosY = m_nNoPosWidth = m_nNoPosHeight = 0;
+ }
+ else if( m_aDropProxy != None && xListener.is() )
+ {
+ aGuard.clear();
+ // drag over for XdndAware windows comes when receiving XdndStatus
+ xListener->dragOver( dsde );
+ }
+}
+
+void SelectionManager::startDrag(
+ const DragGestureEvent& trigger,
+ sal_Int8 sourceActions,
+ sal_Int32,
+ sal_Int32,
+ const css::uno::Reference< XTransferable >& transferable,
+ const css::uno::Reference< XDragSourceListener >& listener
+ )
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "startDrag( sourceActions = "
+ << std::hex
+ << (int)sourceActions
+ << " ).");
+#endif
+ DragSourceDropEvent aDragFailedEvent;
+ aDragFailedEvent.Source = static_cast< OWeakObject* >(this);
+ aDragFailedEvent.DragSource = static_cast< XDragSource* >(this);
+ aDragFailedEvent.DragSourceContext = new DragSourceContext( None, *this );
+ aDragFailedEvent.DropAction = DNDConstants::ACTION_NONE;
+ aDragFailedEvent.DropSuccess = false;
+
+ if( m_aDragRunning.check() )
+ {
+ if( listener.is() )
+ listener->dragDropEnd( aDragFailedEvent );
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.unx.dtrans",
+ "*** ERROR *** second drag and drop started.");
+ if( m_xDragSourceListener.is() )
+ SAL_WARN("vcl.unx.dtrans",
+ "*** ERROR *** drag source listener already set.");
+ else
+ SAL_WARN("vcl.unx.dtrans",
+ "*** ERROR *** drag thread already running.");
+#endif
+ return;
+ }
+
+ SalFrame* pCaptureFrame = nullptr;
+
+ {
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ // first get the current pointer position and the window that
+ // the pointer is located in. since said window should be one
+ // of our DropTargets at the time of executeDrag we can use
+ // them for a start
+ ::Window aRoot, aParent, aChild;
+ int root_x(0), root_y(0), win_x(0), win_y(0);
+ unsigned int mask(0);
+
+ bool bPointerFound = false;
+ for (auto const& dropTarget : m_aDropTargets)
+ {
+ if( XQueryPointer( m_pDisplay, dropTarget.second.m_aRootWindow,
+ &aRoot, &aParent,
+ &root_x, &root_y,
+ &win_x, &win_y,
+ &mask ) )
+ {
+ aParent = dropTarget.second.m_aRootWindow;
+ aRoot = aParent;
+ bPointerFound = true;
+ break;
+ }
+ }
+
+ // don't start DnD if there is none of our windows on the same screen as
+ // the pointer or if no mouse button is pressed
+ if( !bPointerFound || (mask & (Button1Mask|Button2Mask|Button3Mask)) == 0 )
+ {
+ aGuard.clear();
+ if( listener.is() )
+ listener->dragDropEnd( aDragFailedEvent );
+ return;
+ }
+
+ // try to find which of our drop targets is the drag source
+ // if that drop target is deregistered we should stop executing
+ // the drag (actually this is a poor substitute for an "endDrag"
+ // method ).
+ m_aDragSourceWindow = None;
+ do
+ {
+ XTranslateCoordinates( m_pDisplay, aRoot, aParent, root_x, root_y, &win_x, &win_y, &aChild );
+ if( aChild != None && m_aDropTargets.find( aChild ) != m_aDropTargets.end() )
+ {
+ m_aDragSourceWindow = aChild;
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "found drag source window "
+ << std::showbase << std::hex
+ << m_aDragSourceWindow);
+#endif
+ break;
+ }
+ aParent = aChild;
+ } while( aChild != None );
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "try to grab pointer ...");
+#endif
+ int nPointerGrabSuccess =
+ XGrabPointer( m_pDisplay, aRoot, True,
+ DRAG_EVENT_MASK,
+ GrabModeAsync, GrabModeAsync,
+ None,
+ None,
+ CurrentTime );
+ /* if we could not grab the pointer here, there is a chance
+ that the pointer is grabbed by the other vcl display (the main loop)
+ so let's break that grab and reset it later
+
+ remark: this whole code should really be molten into normal vcl so only
+ one display is used...
+ */
+ if( nPointerGrabSuccess != GrabSuccess )
+ {
+ comphelper::SolarMutex& rSolarMutex( Application::GetSolarMutex() );
+ if( rSolarMutex.tryToAcquire() )
+ {
+ pCaptureFrame = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetCaptureFrame();
+ if( pCaptureFrame )
+ {
+ vcl_sal::getSalDisplay(GetGenericUnixSalData())->CaptureMouse( nullptr );
+ nPointerGrabSuccess =
+ XGrabPointer( m_pDisplay, aRoot, True,
+ DRAG_EVENT_MASK,
+ GrabModeAsync, GrabModeAsync,
+ None,
+ None,
+ CurrentTime );
+ }
+ }
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "... grabbed pointer: "
+ << nPointerGrabSuccess);
+ SAL_INFO("vcl.unx.dtrans", "try to grab keyboard ...");
+#endif
+ int nKeyboardGrabSuccess =
+ XGrabKeyboard( m_pDisplay, aRoot, True,
+ GrabModeAsync, GrabModeAsync, CurrentTime );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "... grabbed keyboard: "
+ << nKeyboardGrabSuccess);
+#endif
+ if( nPointerGrabSuccess != GrabSuccess || nKeyboardGrabSuccess != GrabSuccess )
+ {
+ if( nPointerGrabSuccess == GrabSuccess )
+ XUngrabPointer( m_pDisplay, CurrentTime );
+ if( nKeyboardGrabSuccess == GrabSuccess )
+ XUngrabKeyboard( m_pDisplay, CurrentTime );
+ XFlush( m_pDisplay );
+ aGuard.clear();
+ if( listener.is() )
+ listener->dragDropEnd( aDragFailedEvent );
+ if( pCaptureFrame )
+ {
+ comphelper::SolarMutex& rSolarMutex( Application::GetSolarMutex() );
+ if( rSolarMutex.tryToAcquire() )
+ vcl_sal::getSalDisplay(GetGenericUnixSalData())->CaptureMouse( pCaptureFrame );
+#if OSL_DEBUG_LEVEL > 0
+ else
+ OSL_FAIL( "failed to acquire SolarMutex to reset capture frame" );
+#endif
+ }
+ return;
+ }
+
+ m_xDragSourceTransferable = transferable;
+ m_xDragSourceListener = listener;
+ m_aDragFlavors = transferable->getTransferDataFlavors();
+ m_aCurrentCursor = None;
+
+ requestOwnership( m_nXdndSelection );
+
+ ::std::list< Atom > aConversions;
+ getNativeTypeList( m_aDragFlavors, aConversions, m_nXdndSelection );
+
+ Atom* pTypes = static_cast<Atom*>(alloca( sizeof(Atom)*aConversions.size() ));
+ int nTypes = 0;
+ for (auto const& conversion : aConversions)
+ pTypes[nTypes++] = conversion;
+
+ XChangeProperty( m_pDisplay, m_aWindow, m_nXdndTypeList, XA_ATOM, 32, PropModeReplace, reinterpret_cast<unsigned char*>(pTypes), nTypes );
+
+ m_nSourceActions = sourceActions | DNDConstants::ACTION_DEFAULT;
+ m_nUserDragAction = DNDConstants::ACTION_MOVE & m_nSourceActions;
+ if( ! m_nUserDragAction )
+ m_nUserDragAction = DNDConstants::ACTION_COPY & m_nSourceActions;
+ if( ! m_nUserDragAction )
+ m_nUserDragAction = DNDConstants::ACTION_LINK & m_nSourceActions;
+ m_nTargetAcceptAction = DNDConstants::ACTION_DEFAULT;
+ m_bDropSent = false;
+ m_bDropSuccess = false;
+ m_bWaitingForPrimaryConversion = false;
+ m_nDragButton = Button1; // default to left button
+ css::awt::MouseEvent aEvent;
+ if( trigger.Event >>= aEvent )
+ {
+ if( aEvent.Buttons & MouseButton::LEFT )
+ m_nDragButton = Button1;
+ else if( aEvent.Buttons & MouseButton::RIGHT )
+ m_nDragButton = Button3;
+ else if( aEvent.Buttons & MouseButton::MIDDLE )
+ m_nDragButton = Button2;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "m_nUserDragAction = "
+ << std::hex
+ << (int)m_nUserDragAction);
+#endif
+ updateDragWindow( root_x, root_y, aRoot );
+ m_nUserDragAction = ~0;
+ updateDragAction( mask );
+ }
+
+ m_aDragRunning.set();
+ m_aDragExecuteThread = osl_createSuspendedThread( call_SelectionManager_runDragExecute, this );
+ if( m_aDragExecuteThread )
+ osl_resumeThread( m_aDragExecuteThread );
+ else
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "osl_createSuspendedThread failed "
+ << "for drag execute.");
+#endif
+ m_xDragSourceListener.clear();
+ m_xDragSourceTransferable.clear();
+
+ m_bDropSent = false;
+ m_bDropSuccess = false;
+ m_bWaitingForPrimaryConversion = false;
+ m_aDropWindow = None;
+ m_aDropProxy = None;
+ m_nCurrentProtocolVersion = nXdndProtocolRevision;
+ m_nNoPosX = 0;
+ m_nNoPosY = 0;
+ m_nNoPosWidth = 0;
+ m_nNoPosHeight = 0;
+ m_aCurrentCursor = None;
+
+ XUngrabPointer( m_pDisplay, CurrentTime );
+ XUngrabKeyboard( m_pDisplay, CurrentTime );
+ XFlush( m_pDisplay );
+
+ if( pCaptureFrame )
+ {
+ comphelper::SolarMutex& rSolarMutex( Application::GetSolarMutex() );
+ if( rSolarMutex.tryToAcquire() )
+ vcl_sal::getSalDisplay(GetGenericUnixSalData())->CaptureMouse( pCaptureFrame );
+#if OSL_DEBUG_LEVEL > 0
+ else
+ OSL_FAIL( "failed to acquire SolarMutex to reset capture frame" );
+#endif
+ }
+
+ m_aDragRunning.reset();
+
+ if( listener.is() )
+ listener->dragDropEnd( aDragFailedEvent );
+ }
+}
+
+void SelectionManager::runDragExecute( void* pThis )
+{
+ SelectionManager* This = static_cast<SelectionManager*>(pThis);
+ This->dragDoDispatch();
+}
+
+void SelectionManager::dragDoDispatch()
+{
+
+ // do drag
+ // m_xDragSourceListener will be cleared on finished drop
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "begin executeDrag dispatching.");
+#endif
+ oslThread aThread = m_aDragExecuteThread;
+ while( m_xDragSourceListener.is() && ( ! m_bDropSent || time(nullptr)-m_nDropTimeout < 5 ) && osl_scheduleThread( aThread ) )
+ {
+ // let the thread in the run method do the dispatching
+ // just look occasionally here whether drop timed out or is completed
+ osl::Thread::wait(std::chrono::milliseconds(200));
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "end executeDrag dispatching.");
+#endif
+ {
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener );
+ css::uno::Reference< XTransferable > xTransferable( m_xDragSourceTransferable );
+ m_xDragSourceListener.clear();
+ m_xDragSourceTransferable.clear();
+
+ DragSourceDropEvent dsde;
+ dsde.Source = static_cast< OWeakObject* >(this);
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = DNDConstants::ACTION_NONE;
+ dsde.DropSuccess = false;
+
+ // cleanup after drag
+ if( m_bWaitingForPrimaryConversion )
+ {
+ SelectionAdaptor* pAdaptor = getAdaptor( XA_PRIMARY );
+ if (pAdaptor)
+ pAdaptor->clearTransferable();
+ }
+
+ m_bDropSent = false;
+ m_bDropSuccess = false;
+ m_bWaitingForPrimaryConversion = false;
+ m_aDropWindow = None;
+ m_aDropProxy = None;
+ m_nCurrentProtocolVersion = nXdndProtocolRevision;
+ m_nNoPosX = 0;
+ m_nNoPosY = 0;
+ m_nNoPosWidth = 0;
+ m_nNoPosHeight = 0;
+ m_aCurrentCursor = None;
+
+ XUngrabPointer( m_pDisplay, CurrentTime );
+ XUngrabKeyboard( m_pDisplay, CurrentTime );
+ XFlush( m_pDisplay );
+
+ m_aDragExecuteThread = nullptr;
+ m_aDragRunning.reset();
+
+ aGuard.clear();
+ if( xListener.is() )
+ {
+ xTransferable.clear();
+ xListener->dragDropEnd( dsde );
+ }
+ }
+ osl_destroyThread( aThread );
+}
+
+/*
+ * XDragSourceContext
+ */
+
+
+void SelectionManager::setCursor( sal_Int32 cursor, ::Window aDropWindow )
+{
+ osl::MutexGuard aGuard( m_aMutex );
+ if( aDropWindow == m_aDropWindow && Cursor(cursor) != m_aCurrentCursor )
+ {
+ if( m_xDragSourceListener.is() && ! m_bDropSent )
+ {
+ m_aCurrentCursor = cursor;
+ XChangeActivePointerGrab( m_pDisplay, DRAG_EVENT_MASK, cursor, CurrentTime );
+ XFlush( m_pDisplay );
+ }
+ }
+}
+
+void SelectionManager::transferablesFlavorsChanged()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ m_aDragFlavors = m_xDragSourceTransferable->getTransferDataFlavors();
+
+ std::list< Atom > aConversions;
+
+ getNativeTypeList( m_aDragFlavors, aConversions, m_nXdndSelection );
+
+ Atom* pTypes = static_cast<Atom*>(alloca( sizeof(Atom)*aConversions.size() ));
+ int nTypes = 0;
+ for (auto const& conversion : aConversions)
+ pTypes[nTypes++] = conversion;
+ XChangeProperty( m_pDisplay, m_aWindow, m_nXdndTypeList, XA_ATOM, 32, PropModeReplace, reinterpret_cast<unsigned char*>(pTypes), nTypes );
+
+ if( m_aCurrentDropWindow == None || m_nCurrentProtocolVersion < 0 )
+ return;
+
+ // send synthetic leave and enter events
+
+ XEvent aEvent;
+
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.window = m_aDropWindow;
+ aEvent.xclient.data.l[0] = m_aWindow;
+
+ aEvent.xclient.message_type = m_nXdndLeave;
+ aEvent.xclient.data.l[1] = 0;
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+
+ aEvent.xclient.message_type = m_nXdndEnter;
+ aEvent.xclient.data.l[1] = m_nCurrentProtocolVersion << 24;
+ memset( aEvent.xclient.data.l + 2, 0, sizeof( long )*3 );
+ // fill in data types
+ if( nTypes > 3 )
+ aEvent.xclient.data.l[1] |= 1;
+ for( int j = 0; j < nTypes && j < 3; j++ )
+ aEvent.xclient.data.l[j+2] = pTypes[j];
+
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+
+}
+
+/*
+ * dispatch loop
+ */
+
+bool SelectionManager::handleXEvent( XEvent& rEvent )
+{
+ /*
+ * since we are XConnectionListener to a second X display
+ * to get client messages it is essential not to dispatch
+ * events twice that we get on both connections
+ *
+ * between dispatching ButtonPress and startDrag
+ * the user can already have released the mouse. The ButtonRelease
+ * will then be dispatched in VCLs queue and never turn up here.
+ * Which is not so good, since startDrag will XGrabPointer and
+ * XGrabKeyboard -> solid lock.
+ */
+ if( rEvent.xany.display != m_pDisplay
+ && rEvent.type != ClientMessage
+ && rEvent.type != ButtonPress
+ && rEvent.type != ButtonRelease
+ )
+ return false;
+
+ bool bHandled = false;
+ switch (rEvent.type)
+ {
+ case SelectionClear:
+ {
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "SelectionClear for selection "
+ << getString( rEvent.xselectionclear.selection ));
+#endif
+ SelectionAdaptor* pAdaptor = getAdaptor( rEvent.xselectionclear.selection );
+ std::unordered_map< Atom, Selection* >::iterator it( m_aSelections.find( rEvent.xselectionclear.selection ) );
+ if( it != m_aSelections.end() )
+ it->second->m_bOwner = false;
+ aGuard.clear();
+ if ( pAdaptor )
+ pAdaptor->clearTransferable();
+ }
+ break;
+
+ case SelectionRequest:
+ bHandled = handleSelectionRequest( rEvent.xselectionrequest );
+ break;
+ case PropertyNotify:
+ if( rEvent.xproperty.window == m_aWindow ||
+ rEvent.xproperty.window == m_aCurrentDropWindow
+ )
+ bHandled = handleReceivePropertyNotify( rEvent.xproperty );
+ else
+ bHandled = handleSendPropertyNotify( rEvent.xproperty );
+ break;
+ case SelectionNotify:
+ bHandled = handleSelectionNotify( rEvent.xselection );
+ break;
+ case ClientMessage:
+ // messages from drag target
+ if( rEvent.xclient.message_type == m_nXdndStatus ||
+ rEvent.xclient.message_type == m_nXdndFinished )
+ bHandled = handleDragEvent( rEvent );
+ // messages from drag source
+ else if(
+ rEvent.xclient.message_type == m_nXdndEnter ||
+ rEvent.xclient.message_type == m_nXdndLeave ||
+ rEvent.xclient.message_type == m_nXdndPosition ||
+ rEvent.xclient.message_type == m_nXdndDrop
+ )
+ bHandled = handleDropEvent( rEvent.xclient );
+ break;
+ case EnterNotify:
+ case LeaveNotify:
+ case MotionNotify:
+ case ButtonPress:
+ case ButtonRelease:
+ case KeyPress:
+ case KeyRelease:
+ bHandled = handleDragEvent( rEvent );
+ break;
+ default:
+ ;
+ }
+ return bHandled;
+}
+
+void SelectionManager::dispatchEvent( int millisec )
+{
+ // acquire the mutex to prevent other threads
+ // from using the same X connection
+ osl::ResettableMutexGuard aGuard(m_aMutex);
+
+ if( !XPending( m_pDisplay ))
+ {
+ int nfds = 1;
+ // wait for any events if none are already queued
+ pollfd aPollFD[2];
+ aPollFD[0].fd = XConnectionNumber( m_pDisplay );
+ aPollFD[0].events = POLLIN;
+ aPollFD[0].revents = 0;
+
+ // on infinite timeout we need endthreadpipe monitoring too
+ if (millisec < 0)
+ {
+ aPollFD[1].fd = m_EndThreadPipe[0];
+ aPollFD[1].events = POLLIN | POLLERR;
+ aPollFD[1].revents = 0;
+ nfds = 2;
+ }
+
+ // release mutex for the time of waiting for possible data
+ aGuard.clear();
+ if( poll( aPollFD, nfds, millisec ) <= 0 )
+ return;
+ aGuard.reset();
+ }
+ while( XPending( m_pDisplay ))
+ {
+ XEvent event;
+ XNextEvent( m_pDisplay, &event );
+ aGuard.clear();
+ handleXEvent( event );
+ aGuard.reset();
+ }
+}
+
+void SelectionManager::run( void* pThis )
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "SelectionManager::run.");
+#endif
+ osl::Thread::setName("SelectionManager");
+ // dispatch until the cows come home
+
+ SelectionManager* This = static_cast<SelectionManager*>(pThis);
+
+ timeval aLast;
+ gettimeofday( &aLast, nullptr );
+
+ css::uno::Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
+ This->m_xDesktop.set( Desktop::create(xContext) );
+ This->m_xDesktop->addTerminateListener(This);
+
+ // if end thread pipe properly initialized, allow infinite wait in poll
+ // otherwise, fallback on 1 sec timeout
+ const int timeout = (This->m_EndThreadPipe[0] != This->m_EndThreadPipe[1]) ? -1 : 1000;
+
+ while( osl_scheduleThread(This->m_aThread) )
+ {
+ This->dispatchEvent( timeout );
+
+ timeval aNow;
+ gettimeofday( &aNow, nullptr );
+
+ if( (aNow.tv_sec - aLast.tv_sec) > 0 )
+ {
+ osl::ClearableMutexGuard aGuard(This->m_aMutex);
+ std::vector< std::pair< SelectionAdaptor*, css::uno::Reference< XInterface > > > aChangeVector;
+
+ for (auto const& selection : This->m_aSelections)
+ {
+ if( selection.first != This->m_nXdndSelection && ! selection.second->m_bOwner )
+ {
+ ::Window aOwner = XGetSelectionOwner( This->m_pDisplay, selection.first );
+ if( aOwner != selection.second->m_aLastOwner )
+ {
+ selection.second->m_aLastOwner = aOwner;
+ std::pair< SelectionAdaptor*, css::uno::Reference< XInterface > >
+ aKeep( selection.second->m_pAdaptor, selection.second->m_pAdaptor->getReference() );
+ aChangeVector.push_back( aKeep );
+ }
+ }
+ }
+ aGuard.clear();
+ for (auto const& change : aChangeVector)
+ {
+ change.first->fireContentsChanged();
+ }
+ aLast = aNow;
+ }
+ }
+ // close write end on exit so write() fails and other thread does not block
+ // forever
+ close(This->m_EndThreadPipe[1]);
+ close(This->m_EndThreadPipe[0]);
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "SelectionManager::run end.");
+#endif
+}
+
+void SelectionManager::shutdown() noexcept
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "SelectionManager got app termination event.");
+#endif
+
+ osl::ResettableMutexGuard aGuard(m_aMutex);
+
+ if( m_bShutDown )
+ return;
+ m_bShutDown = true;
+
+ if ( m_xDesktop.is() )
+ m_xDesktop->removeTerminateListener(this);
+
+ if( m_xDisplayConnection.is() )
+ m_xDisplayConnection->removeEventHandler(Any(), this);
+
+ // stop dispatching
+ if( m_aThread )
+ {
+ osl_terminateThread( m_aThread );
+ /*
+ * Allow thread to finish before app exits to avoid pulling the carpet
+ * out from under it if pasting is occurring during shutdown
+ *
+ * a) allow it to have the Mutex and
+ * b) reschedule to allow it to complete callbacks to any
+ * Application::GetSolarMutex protected regions, etc. e.g.
+ * TransferableHelper::getTransferDataFlavors (via
+ * SelectionManager::handleSelectionRequest) which it might
+ * currently be trying to enter.
+ *
+ * Otherwise the thread may be left still waiting on a GlobalMutex
+ * when that gets destroyed, letting the thread blow up and die
+ * when enters the section in a now dead OOo instance.
+ */
+ aGuard.clear();
+ while (osl_isThreadRunning(m_aThread))
+ {
+ { // drop mutex before write - otherwise may deadlock
+ SolarMutexGuard guard2;
+ Application::Reschedule();
+ }
+ // trigger poll()'s wait end by writing a dummy value
+ char dummy=0;
+ dummy = write(m_EndThreadPipe[1], &dummy, 1);
+ }
+ osl_joinWithThread( m_aThread );
+ osl_destroyThread( m_aThread );
+ m_aThread = nullptr;
+ aGuard.reset();
+ }
+ m_xDesktop.clear();
+ m_xDisplayConnection.clear();
+ m_xDropTransferable.clear();
+}
+
+sal_Bool SelectionManager::handleEvent(const Any& event)
+{
+ Sequence< sal_Int8 > aSeq;
+ if( event >>= aSeq )
+ {
+ XEvent* pEvent = reinterpret_cast<XEvent*>(aSeq.getArray());
+ Time nTimestamp = CurrentTime;
+ if( pEvent->type == ButtonPress || pEvent->type == ButtonRelease )
+ nTimestamp = pEvent->xbutton.time;
+ else if( pEvent->type == KeyPress || pEvent->type == KeyRelease )
+ nTimestamp = pEvent->xkey.time;
+ else if( pEvent->type == MotionNotify )
+ nTimestamp = pEvent->xmotion.time;
+ else if( pEvent->type == PropertyNotify )
+ nTimestamp = pEvent->xproperty.time;
+
+ if( nTimestamp != CurrentTime )
+ {
+ osl::MutexGuard aGuard(m_aMutex);
+
+ m_nSelectionTimestamp = nTimestamp;
+ }
+
+ return handleXEvent( *pEvent );
+ }
+ else
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "SelectionManager got downing event.");
+#endif
+ shutdown();
+ }
+ return true;
+}
+
+void SAL_CALL SelectionManager::disposing( const css::lang::EventObject& rEvt )
+{
+ if (rEvt.Source == m_xDesktop || rEvt.Source == m_xDisplayConnection)
+ shutdown();
+}
+
+void SAL_CALL SelectionManager::queryTermination( const css::lang::EventObject& )
+{
+}
+
+/*
+ * To be safe, shutdown needs to be called before the ~SfxApplication is called, waiting until
+ * the downing event can be too late if paste are requested during shutdown and ~SfxApplication
+ * has been called before vcl is shutdown
+ */
+void SAL_CALL SelectionManager::notifyTermination( const css::lang::EventObject& rEvent )
+{
+ disposing(rEvent);
+}
+
+void SelectionManager::registerHandler( Atom selection, SelectionAdaptor& rAdaptor )
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ Selection* pNewSelection = new Selection();
+ pNewSelection->m_pAdaptor = &rAdaptor;
+ m_aSelections[ selection ] = pNewSelection;
+}
+
+void SelectionManager::deregisterHandler( Atom selection )
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ std::unordered_map< Atom, Selection* >::iterator it =
+ m_aSelections.find( selection );
+ if( it != m_aSelections.end() )
+ {
+ delete it->second->m_pPixmap;
+ delete it->second;
+ m_aSelections.erase( it );
+ }
+}
+
+static bool bWasError = false;
+
+extern "C"
+{
+ static int local_xerror_handler(Display* , XErrorEvent*)
+ {
+ bWasError = true;
+ return 0;
+ }
+ typedef int(*xerror_hdl_t)(Display*,XErrorEvent*);
+}
+
+void SelectionManager::registerDropTarget( ::Window aWindow, DropTarget* pTarget )
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ // sanity check
+ std::unordered_map< ::Window, DropTargetEntry >::const_iterator it =
+ m_aDropTargets.find( aWindow );
+ if( it != m_aDropTargets.end() )
+ OSL_FAIL( "attempt to register window as drop target twice" );
+ else if( aWindow && m_pDisplay )
+ {
+ DropTargetEntry aEntry( pTarget );
+ bWasError=false;
+ /* #i100000# ugly workaround: gtk sets its own XErrorHandler which is not suitable for us
+ unfortunately XErrorHandler is not per display, so this is just and ugly hack
+ Need to remove separate display and integrate clipboard/dnd into vcl's unx code ASAP
+ */
+ xerror_hdl_t pOldHandler = XSetErrorHandler( local_xerror_handler );
+ XSelectInput( m_pDisplay, aWindow, PropertyChangeMask );
+ if( ! bWasError )
+ {
+ // set XdndAware
+ XChangeProperty( m_pDisplay, aWindow, m_nXdndAware, XA_ATOM, 32, PropModeReplace, reinterpret_cast<unsigned char const *>(&nXdndProtocolRevision), 1 );
+ if( ! bWasError )
+ {
+ // get root window of window (in 99.999% of all cases this will be
+ // DefaultRootWindow( m_pDisplay )
+ int x, y;
+ unsigned int w, h, bw, d;
+ XGetGeometry( m_pDisplay, aWindow, &aEntry.m_aRootWindow,
+ &x, &y, &w, &h, &bw, &d );
+ }
+ }
+ XSetErrorHandler( pOldHandler );
+ if(bWasError)
+ return;
+ m_aDropTargets[ aWindow ] = aEntry;
+ }
+ else
+ OSL_FAIL( "attempt to register None as drop target" );
+}
+
+void SelectionManager::deregisterDropTarget( ::Window aWindow )
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ m_aDropTargets.erase( aWindow );
+ if( aWindow != m_aDragSourceWindow || !m_aDragRunning.check() )
+ return;
+
+ // abort drag
+ std::unordered_map< ::Window, DropTargetEntry >::const_iterator it =
+ m_aDropTargets.find( m_aDropWindow );
+ if( it != m_aDropTargets.end() )
+ {
+ DropTargetEvent dte;
+ dte.Source = static_cast< OWeakObject* >( it->second.m_pTarget );
+ aGuard.clear();
+ it->second.m_pTarget->dragExit( dte );
+ }
+ else if( m_aDropProxy != None && m_nCurrentProtocolVersion >= 0 )
+ {
+ // send XdndLeave
+ XEvent aEvent;
+ aEvent.type = ClientMessage;
+ aEvent.xclient.display = m_pDisplay;
+ aEvent.xclient.format = 32;
+ aEvent.xclient.message_type = m_nXdndLeave;
+ aEvent.xclient.window = m_aDropWindow;
+ aEvent.xclient.data.l[0] = m_aWindow;
+ memset( aEvent.xclient.data.l+1, 0, sizeof(long)*4);
+ m_aDropWindow = m_aDropProxy = None;
+ XSendEvent( m_pDisplay, m_aDropProxy, False, NoEventMask, &aEvent );
+ }
+ // notify the listener
+ DragSourceDropEvent dsde;
+ dsde.Source = static_cast< OWeakObject* >(this);
+ dsde.DragSourceContext = new DragSourceContext( m_aDropWindow, *this );
+ dsde.DragSource = static_cast< XDragSource* >(this);
+ dsde.DropAction = DNDConstants::ACTION_NONE;
+ dsde.DropSuccess = false;
+ css::uno::Reference< XDragSourceListener > xListener( m_xDragSourceListener );
+ m_xDragSourceListener.clear();
+ aGuard.clear();
+ xListener->dragDropEnd( dsde );
+
+}
+
+/*
+ * SelectionAdaptor
+ */
+
+css::uno::Reference< XTransferable > SelectionManager::getTransferable() noexcept
+{
+ return m_xDragSourceTransferable;
+}
+
+void SelectionManager::clearTransferable() noexcept
+{
+ m_xDragSourceTransferable.clear();
+}
+
+void SelectionManager::fireContentsChanged() noexcept
+{
+}
+
+css::uno::Reference< XInterface > SelectionManager::getReference() noexcept
+{
+ return css::uno::Reference< XInterface >( static_cast<OWeakObject*>(this) );
+}
+
+/*
+ * SelectionManagerHolder
+ */
+
+SelectionManagerHolder::SelectionManagerHolder() :
+ ::cppu::WeakComponentImplHelper<
+ XDragSource,
+ XInitialization,
+ XServiceInfo > (m_aMutex)
+{
+}
+
+SelectionManagerHolder::~SelectionManagerHolder()
+{
+}
+
+void SelectionManagerHolder::initialize( const Sequence< Any >& arguments )
+{
+ OUString aDisplayName;
+
+ if( arguments.hasElements() )
+ {
+ css::uno::Reference< XDisplayConnection > xConn;
+ arguments.getConstArray()[0] >>= xConn;
+ if( xConn.is() )
+ {
+ Any aIdentifier;
+ aIdentifier >>= aDisplayName;
+ }
+ }
+
+ SelectionManager& rManager = SelectionManager::get( aDisplayName );
+ rManager.initialize( arguments );
+ m_xRealDragSource = static_cast< XDragSource* >(&rManager);
+}
+
+/*
+ * XDragSource
+ */
+
+sal_Bool SelectionManagerHolder::isDragImageSupported()
+{
+ return m_xRealDragSource.is() && m_xRealDragSource->isDragImageSupported();
+}
+
+sal_Int32 SelectionManagerHolder::getDefaultCursor( sal_Int8 dragAction )
+{
+ return m_xRealDragSource.is() ? m_xRealDragSource->getDefaultCursor( dragAction ) : 0;
+}
+
+void SelectionManagerHolder::startDrag(
+ const css::datatransfer::dnd::DragGestureEvent& trigger,
+ sal_Int8 sourceActions, sal_Int32 cursor, sal_Int32 image,
+ const css::uno::Reference< css::datatransfer::XTransferable >& transferable,
+ const css::uno::Reference< css::datatransfer::dnd::XDragSourceListener >& listener
+ )
+{
+ if( m_xRealDragSource.is() )
+ m_xRealDragSource->startDrag( trigger, sourceActions, cursor, image, transferable, listener );
+}
+
+/*
+ * XServiceInfo
+ */
+
+OUString SelectionManagerHolder::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.XdndSupport";
+}
+
+sal_Bool SelectionManagerHolder::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SelectionManagerHolder::getSupportedServiceNames()
+{
+ return Xdnd_getSupportedServiceNames();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_selection.hxx b/vcl/unx/generic/dtrans/X11_selection.hxx
new file mode 100644
index 000000000..c0ae171c6
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_selection.hxx
@@ -0,0 +1,496 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragSource.hpp>
+#include <com/sun/star/awt/XDisplayConnection.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/frame/XDesktop2.hpp>
+#include <osl/thread.h>
+#include <osl/conditn.hxx>
+#include <rtl/ref.hxx>
+
+#include <list>
+#include <unordered_map>
+#include <vector>
+
+#include <X11/Xlib.h>
+
+
+namespace x11 {
+
+ class PixmapHolder; // in bmp.hxx
+ class SelectionManager;
+
+ rtl_TextEncoding getTextPlainEncoding( const OUString& rMimeType );
+
+ class SelectionAdaptor
+ {
+ public:
+ virtual css::uno::Reference< css::datatransfer::XTransferable > getTransferable() = 0;
+ virtual void clearTransferable() = 0;
+ virtual void fireContentsChanged() = 0;
+ virtual css::uno::Reference< css::uno::XInterface > getReference() = 0;
+ // returns a reference that will keep the SelectionAdaptor alive until the
+ // reference is released
+
+ protected:
+ ~SelectionAdaptor() {}
+ };
+
+ class DropTarget :
+ public ::cppu::WeakComponentImplHelper<
+ css::datatransfer::dnd::XDropTarget,
+ css::lang::XInitialization,
+ css::lang::XServiceInfo
+ >
+ {
+ public:
+ ::osl::Mutex m_aMutex;
+ bool m_bActive;
+ sal_Int8 m_nDefaultActions;
+ ::Window m_aTargetWindow;
+ rtl::Reference<SelectionManager>
+ m_xSelectionManager;
+ ::std::vector< css::uno::Reference< css::datatransfer::dnd::XDropTargetListener > >
+ m_aListeners;
+
+ DropTarget();
+ virtual ~DropTarget() override;
+
+ // convenience functions that loop over listeners
+ void dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& dtde ) noexcept;
+ void dragExit( const css::datatransfer::dnd::DropTargetEvent& dte ) noexcept;
+ void dragOver( const css::datatransfer::dnd::DropTargetDragEvent& dtde ) noexcept;
+ void drop( const css::datatransfer::dnd::DropTargetDropEvent& dtde ) noexcept;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& args ) override;
+
+ // XDropTarget
+ virtual void SAL_CALL addDropTargetListener( const css::uno::Reference< css::datatransfer::dnd::XDropTargetListener >& ) override;
+ virtual void SAL_CALL removeDropTargetListener( const css::uno::Reference< css::datatransfer::dnd::XDropTargetListener >& ) override;
+ virtual sal_Bool SAL_CALL isActive() override;
+ virtual void SAL_CALL setActive( sal_Bool active ) override;
+ virtual sal_Int8 SAL_CALL getDefaultActions() override;
+ virtual void SAL_CALL setDefaultActions( sal_Int8 actions ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString >
+ SAL_CALL getSupportedServiceNames() override;
+ };
+
+ class SelectionManagerHolder :
+ public ::cppu::WeakComponentImplHelper<
+ css::datatransfer::dnd::XDragSource,
+ css::lang::XInitialization,
+ css::lang::XServiceInfo
+ >
+ {
+ ::osl::Mutex m_aMutex;
+ css::uno::Reference< css::datatransfer::dnd::XDragSource >
+ m_xRealDragSource;
+ public:
+ SelectionManagerHolder();
+ virtual ~SelectionManagerHolder() override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString >
+ SAL_CALL getSupportedServiceNames() override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& arguments ) override;
+
+ // XDragSource
+ virtual sal_Bool SAL_CALL isDragImageSupported() override;
+ virtual sal_Int32 SAL_CALL getDefaultCursor( sal_Int8 dragAction ) override;
+ virtual void SAL_CALL startDrag(
+ const css::datatransfer::dnd::DragGestureEvent& trigger,
+ sal_Int8 sourceActions, sal_Int32 cursor, sal_Int32 image,
+ const css::uno::Reference< css::datatransfer::XTransferable >& transferable,
+ const css::uno::Reference< css::datatransfer::dnd::XDragSourceListener >& listener
+ ) override;
+
+ };
+
+ class SelectionManager :
+ public ::cppu::WeakImplHelper<
+ css::datatransfer::dnd::XDragSource,
+ css::lang::XInitialization,
+ css::awt::XEventHandler,
+ css::frame::XTerminateListener
+ >,
+ public SelectionAdaptor
+ {
+ static std::unordered_map< OUString, SelectionManager* >& getInstances();
+
+ // for INCR type selection transfer
+ // INCR protocol is used if the data cannot
+ // be transported at once but in parts
+ // IncrementalTransfer holds the bytes to be transmitted
+ // as well as the current position
+ // INCR triggers the delivery of the next part by deleting the
+ // property used to transfer the data
+ struct IncrementalTransfer
+ {
+ css::uno::Sequence< sal_Int8 > m_aData;
+ int m_nBufferPos;
+ ::Window m_aRequestor;
+ Atom m_aProperty;
+ Atom m_aTarget;
+ int m_nFormat;
+ int m_nTransferStartTime;
+ };
+ int m_nIncrementalThreshold;
+
+ // a struct to hold the data associated with a selection
+ struct Selection
+ {
+ enum State
+ {
+ Inactive, WaitingForResponse, WaitingForData, IncrementalTransfer
+ };
+
+ State m_eState;
+ SelectionAdaptor* m_pAdaptor;
+ ::osl::Condition m_aDataArrived;
+ css::uno::Sequence< sal_Int8 > m_aData;
+ css::uno::Sequence< css::datatransfer::DataFlavor >
+ m_aTypes;
+ std::vector< Atom > m_aNativeTypes;
+ // this is used for caching
+ // m_aTypes is invalid after 2 seconds
+ // m_aNativeTypes contains the corresponding original atom
+ Atom m_aRequestedType;
+ // m_aRequestedType is only valid while WaitingForResponse and WaitingFotData
+ int m_nLastTimestamp;
+ bool m_bHaveUTF16;
+ Atom m_aUTF8Type;
+ bool m_bHaveCompound;
+ bool m_bOwner;
+ ::Window m_aLastOwner;
+ PixmapHolder* m_pPixmap;
+ // m_nOrigTimestamp contains the Timestamp at which the selection
+ // was acquired; needed for TimeSTAMP target
+ Time m_nOrigTimestamp;
+
+ Selection() : m_eState( Inactive ),
+ m_pAdaptor( nullptr ),
+ m_aRequestedType( None ),
+ m_nLastTimestamp( 0 ),
+ m_bHaveUTF16( false ),
+ m_aUTF8Type( None ),
+ m_bHaveCompound( false ),
+ m_bOwner( false ),
+ m_aLastOwner( None ),
+ m_pPixmap( nullptr ),
+ m_nOrigTimestamp( CurrentTime )
+ {}
+ };
+
+ // a struct to hold data associated with a XDropTarget
+ struct DropTargetEntry
+ {
+ DropTarget* m_pTarget;
+ ::Window m_aRootWindow;
+
+ DropTargetEntry() : m_pTarget( nullptr ), m_aRootWindow( None ) {}
+ explicit DropTargetEntry( DropTarget* pTarget ) :
+ m_pTarget( pTarget ),
+ m_aRootWindow( None )
+ {}
+ DropTargetEntry( const DropTargetEntry& rEntry ) :
+ m_pTarget( rEntry.m_pTarget ),
+ m_aRootWindow( rEntry.m_aRootWindow )
+ {}
+
+ DropTarget* operator->() const { return m_pTarget; }
+ DropTargetEntry& operator=(const DropTargetEntry& rEntry)
+ { m_pTarget = rEntry.m_pTarget; m_aRootWindow = rEntry.m_aRootWindow; return *this; }
+ };
+
+ // internal data
+ Display* m_pDisplay;
+ oslThread m_aThread;
+ int m_EndThreadPipe[2];
+ oslThread m_aDragExecuteThread;
+ ::osl::Condition m_aDragRunning;
+ ::Window m_aWindow;
+ css::uno::Reference< css::frame::XDesktop2 > m_xDesktop;
+ css::uno::Reference< css::awt::XDisplayConnection >
+ m_xDisplayConnection;
+ sal_Int32 m_nSelectionTimeout;
+ Time m_nSelectionTimestamp;
+
+ // members used for Xdnd
+
+ // drop only
+
+ // contains the XdndEnterEvent of a drop action running
+ // with one of our targets. The data.l[0] member
+ // (containing the drag source ::Window) is set
+ // to None while that is not the case
+ XClientMessageEvent m_aDropEnterEvent;
+ // set to false on XdndEnter
+ // set to true on first XdndPosition or XdndLeave
+ bool m_bDropEnterSent;
+ ::Window m_aCurrentDropWindow;
+ // Time code of XdndDrop
+ Time m_nDropTime;
+ sal_Int8 m_nLastDropAction;
+ // XTransferable for Xdnd with foreign drag source
+ css::uno::Reference< css::datatransfer::XTransferable >
+ m_xDropTransferable;
+ int m_nLastX, m_nLastY;
+ // set to true when calling drop()
+ // if another XdndEnter is received this shows that
+ // someone forgot to call dropComplete - we should reset
+ // and react to the new drop
+ bool m_bDropWaitingForCompletion;
+
+ // drag only
+
+ // None if no Dnd action is running with us as source
+ ::Window m_aDropWindow;
+ // either m_aDropWindow or its XdndProxy
+ ::Window m_aDropProxy;
+ ::Window m_aDragSourceWindow;
+ // XTransferable for Xdnd when we are drag source
+ css::uno::Reference< css::datatransfer::XTransferable >
+ m_xDragSourceTransferable;
+ css::uno::Reference< css::datatransfer::dnd::XDragSourceListener >
+ m_xDragSourceListener;
+ // root coordinates
+ int m_nLastDragX, m_nLastDragY;
+ css::uno::Sequence< css::datatransfer::DataFlavor >
+ m_aDragFlavors;
+ // the rectangle the pointer must leave until a new XdndPosition should
+ // be sent. empty unless the drop target told to fill
+ int m_nNoPosX, m_nNoPosY, m_nNoPosWidth, m_nNoPosHeight;
+ unsigned int m_nDragButton;
+ sal_Int8 m_nUserDragAction;
+ sal_Int8 m_nTargetAcceptAction;
+ sal_Int8 m_nSourceActions;
+ bool m_bLastDropAccepted;
+ bool m_bDropSuccess;
+ bool m_bDropSent;
+ time_t m_nDropTimeout;
+ bool m_bWaitingForPrimaryConversion;
+
+ // drag cursors
+ Cursor m_aMoveCursor;
+ Cursor m_aCopyCursor;
+ Cursor m_aLinkCursor;
+ Cursor m_aNoneCursor;
+ Cursor m_aCurrentCursor;
+
+ // drag and drop
+
+ int m_nCurrentProtocolVersion;
+ std::unordered_map< ::Window, DropTargetEntry >
+ m_aDropTargets;
+
+ // some special atoms that are needed often
+ Atom m_nTARGETSAtom;
+ Atom m_nTIMESTAMPAtom;
+ Atom m_nTEXTAtom;
+ Atom m_nINCRAtom;
+ Atom m_nCOMPOUNDAtom;
+ Atom m_nMULTIPLEAtom;
+ Atom m_nImageBmpAtom;
+ Atom m_nXdndAware;
+ Atom m_nXdndEnter;
+ Atom m_nXdndLeave;
+ Atom m_nXdndPosition;
+ Atom m_nXdndStatus;
+ Atom m_nXdndDrop;
+ Atom m_nXdndFinished;
+ Atom m_nXdndSelection;
+ Atom m_nXdndTypeList;
+ Atom m_nXdndProxy;
+ Atom m_nXdndActionCopy;
+ Atom m_nXdndActionMove;
+ Atom m_nXdndActionLink;
+ Atom m_nXdndActionAsk;
+
+ // caching for atoms
+ std::unordered_map< Atom, OUString >
+ m_aAtomToString;
+ std::unordered_map< OUString, Atom >
+ m_aStringToAtom;
+
+ // the registered selections
+ std::unordered_map< Atom, Selection* >
+ m_aSelections;
+ // IncrementalTransfers in progress
+ std::unordered_map< ::Window, std::unordered_map< Atom, IncrementalTransfer > >
+ m_aIncrementals;
+
+ // do not use X11 multithreading capabilities
+ // since this leads to deadlocks in different Xlib implementations
+ // (XFree as well as Xsun) use an own mutex instead
+ ::osl::Mutex m_aMutex;
+ bool m_bShutDown;
+
+ SelectionManager();
+ virtual ~SelectionManager() override;
+
+ SelectionAdaptor* getAdaptor( Atom selection );
+ PixmapHolder* getPixmapHolder( Atom selection );
+
+ // handle various events
+ bool handleSelectionRequest( XSelectionRequestEvent& rRequest );
+ bool handleSendPropertyNotify( XPropertyEvent const & rNotify );
+ bool handleReceivePropertyNotify( XPropertyEvent const & rNotify );
+ bool handleSelectionNotify( XSelectionEvent const & rNotify );
+ bool handleDragEvent( XEvent const & rMessage );
+ bool handleDropEvent( XClientMessageEvent const & rMessage );
+
+ // dnd helpers
+ void sendDragStatus( Atom nDropAction );
+ void sendDropPosition( bool bForce, Time eventTime );
+ bool updateDragAction( int modifierState );
+ int getXdndVersion( ::Window aXLIB_Window, ::Window& rProxy );
+ Cursor createCursor( const unsigned char* pPointerData, const unsigned char* pMaskData, int width, int height, int hotX, int hotY );
+ // coordinates on root ::Window
+ void updateDragWindow( int nX, int nY, ::Window aRoot );
+
+ bool getPasteData( Atom selection, Atom type, css::uno::Sequence< sal_Int8 >& rData );
+ // returns true if conversion was successful
+ bool convertData( const css::uno::Reference< css::datatransfer::XTransferable >& xTransferable,
+ Atom nType,
+ Atom nSelection,
+ int & rFormat,
+ css::uno::Sequence< sal_Int8 >& rData );
+ bool sendData( SelectionAdaptor* pAdaptor, ::Window requestor, Atom target, Atom property, Atom selection );
+
+ // thread dispatch loop
+ public:
+ // public for extern "C" stub
+ static void run( void* );
+ private:
+ void dispatchEvent( int millisec );
+ // drag thread dispatch
+ public:
+ // public for extern "C" stub
+ static void runDragExecute( void* );
+ private:
+ void dragDoDispatch();
+ bool handleXEvent( XEvent& rEvent );
+
+ // compound text conversion
+ OString convertToCompound( const OUString& rText );
+ OUString convertFromCompound( const char* pText, int nLen );
+
+ sal_Int8 getUserDragAction() const;
+ sal_Int32 getSelectionTimeout();
+ public:
+ static SelectionManager& get( const OUString& rDisplayName = OUString() );
+
+ Display * getDisplay() { return m_pDisplay; };
+
+ void registerHandler( Atom selection, SelectionAdaptor& rAdaptor );
+ void deregisterHandler( Atom selection );
+ bool requestOwnership( Atom selection );
+
+ // allow for synchronization over one mutex for XClipboard
+ osl::Mutex& getMutex() { return m_aMutex; }
+
+ Atom getAtom( const OUString& rString );
+ OUString getString( Atom nAtom );
+
+ // type conversion
+ // note: convertTypeToNative does NOT clear the list, so you can append
+ // multiple types to the same list
+ void convertTypeToNative( const OUString& rType, Atom selection, int& rFormat, ::std::list< Atom >& rConversions, bool bPushFront = false );
+ OUString convertTypeFromNative( Atom nType, Atom selection, int& rFormat );
+ void getNativeTypeList( const css::uno::Sequence< css::datatransfer::DataFlavor >& rTypes, std::list< Atom >& rOutTypeList, Atom targetselection );
+
+ // methods for transferable
+ bool getPasteDataTypes( Atom selection, css::uno::Sequence< css::datatransfer::DataFlavor >& rTypes );
+ bool getPasteData( Atom selection, const OUString& rType, css::uno::Sequence< sal_Int8 >& rData );
+
+ // for XDropTarget to register/deregister itself
+ void registerDropTarget( ::Window aXLIB_Window, DropTarget* pTarget );
+ void deregisterDropTarget( ::Window aXLIB_Window );
+
+ // for XDropTarget{Drag|Drop}Context
+ void accept( sal_Int8 dragOperation, ::Window aDropXLIB_Window );
+ void reject( ::Window aDropXLIB_Window );
+ void dropComplete( bool success, ::Window aDropXLIB_Window );
+
+ // for XDragSourceContext
+ sal_Int32 getCurrentCursor() const { return m_aCurrentCursor;}
+ void setCursor( sal_Int32 cursor, ::Window aDropXLIB_Window );
+ void transferablesFlavorsChanged();
+
+ void shutdown() noexcept;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& arguments ) override;
+
+ // XEventHandler
+ virtual sal_Bool SAL_CALL handleEvent(const css::uno::Any& event) override;
+
+ // XDragSource
+ virtual sal_Bool SAL_CALL isDragImageSupported() override;
+ virtual sal_Int32 SAL_CALL getDefaultCursor( sal_Int8 dragAction ) override;
+ virtual void SAL_CALL startDrag(
+ const css::datatransfer::dnd::DragGestureEvent& trigger,
+ sal_Int8 sourceActions, sal_Int32 cursor, sal_Int32 image,
+ const css::uno::Reference< css::datatransfer::XTransferable >& transferable,
+ const css::uno::Reference< css::datatransfer::dnd::XDragSourceListener >& listener
+ ) override;
+
+ // SelectionAdaptor for XdndSelection Drag (we are drag source)
+ virtual css::uno::Reference< css::datatransfer::XTransferable > getTransferable() noexcept override;
+ virtual void clearTransferable() noexcept override;
+ virtual void fireContentsChanged() noexcept override;
+ virtual css::uno::Reference< css::uno::XInterface > getReference() noexcept override;
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+
+ // XTerminateListener
+ virtual void SAL_CALL queryTermination( const css::lang::EventObject& aEvent ) override;
+ virtual void SAL_CALL notifyTermination( const css::lang::EventObject& aEvent ) override;
+ };
+
+ css::uno::Sequence< OUString > Xdnd_getSupportedServiceNames();
+ css::uno::Reference< css::uno::XInterface > SAL_CALL Xdnd_createInstance(
+ const css::uno::Reference< css::lang::XMultiServiceFactory > & xMultiServiceFactory);
+
+ css::uno::Sequence< OUString > Xdnd_dropTarget_getSupportedServiceNames();
+ css::uno::Reference< css::uno::XInterface > SAL_CALL Xdnd_dropTarget_createInstance(
+ const css::uno::Reference< css::lang::XMultiServiceFactory > & xMultiServiceFactory);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_service.cxx b/vcl/unx/generic/dtrans/X11_service.cxx
new file mode 100644
index 000000000..e633020b6
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_service.cxx
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unx/salinst.h>
+#include <dndhelper.hxx>
+#include <vcl/sysdata.hxx>
+
+#include "X11_clipboard.hxx"
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+
+using namespace cppu;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::datatransfer::clipboard;
+using namespace com::sun::star::awt;
+using namespace x11;
+
+Sequence< OUString > x11::X11Clipboard_getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.clipboard.SystemClipboard" };
+}
+
+Sequence< OUString > x11::Xdnd_getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.dnd.X11DragSource" };
+}
+
+Sequence< OUString > x11::Xdnd_dropTarget_getSupportedServiceNames()
+{
+ return { "com.sun.star.datatransfer.dnd.X11DropTarget" };
+}
+
+css::uno::Reference< XInterface > X11SalInstance::CreateClipboard( const Sequence< Any >& arguments )
+{
+ if ( IsRunningUnitTest() )
+ return SalInstance::CreateClipboard( arguments );
+
+ SelectionManager& rManager = SelectionManager::get();
+ css::uno::Sequence<css::uno::Any> mgrArgs{ css::uno::Any(Application::GetDisplayConnection()) };
+ rManager.initialize(mgrArgs);
+
+ OUString sel;
+ if (!arguments.hasElements()) {
+ sel = "CLIPBOARD";
+ } else if (arguments.getLength() != 1 || !(arguments[0] >>= sel)) {
+ throw css::lang::IllegalArgumentException(
+ "bad X11SalInstance::CreateClipboard arguments",
+ css::uno::Reference<css::uno::XInterface>(), -1);
+ }
+ Atom nSelection = rManager.getAtom(sel);
+
+ std::unordered_map< Atom, css::uno::Reference< XClipboard > >::iterator it = m_aInstances.find( nSelection );
+ if( it != m_aInstances.end() )
+ return it->second;
+
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> pClipboard = X11Clipboard::create( rManager, nSelection );
+ m_aInstances[ nSelection ] = pClipboard;
+
+ return pClipboard;
+}
+
+css::uno::Reference<XInterface> X11SalInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv)
+{
+ return vcl::X11DnDHelper(new SelectionManagerHolder(), pSysEnv->aShellWindow);
+}
+
+css::uno::Reference<XInterface> X11SalInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv)
+{
+ return vcl::X11DnDHelper(new DropTarget(), pSysEnv->aShellWindow);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_transferable.cxx b/vcl/unx/generic/dtrans/X11_transferable.cxx
new file mode 100644
index 000000000..a6ad1b4a1
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_transferable.cxx
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "X11_transferable.hxx"
+#include <X11/Xatom.h>
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <sal/log.hxx>
+
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::io;
+using namespace com::sun::star::uno;
+using namespace cppu;
+using namespace osl;
+
+using namespace x11;
+
+X11Transferable::X11Transferable(
+ SelectionManager& rManager,
+ Atom selection
+ ) :
+ m_rManager( rManager ),
+ m_aSelection( selection )
+{
+}
+
+X11Transferable::~X11Transferable()
+{
+}
+
+Any SAL_CALL X11Transferable::getTransferData( const DataFlavor& rFlavor )
+{
+ Any aRet;
+ Sequence< sal_Int8 > aData;
+ bool bSuccess = m_rManager.getPasteData( m_aSelection ? m_aSelection : XA_PRIMARY, rFlavor.MimeType, aData );
+ if( ! bSuccess && m_aSelection == 0 )
+ bSuccess = m_rManager.getPasteData( m_rManager.getAtom( "CLIPBOARD" ), rFlavor.MimeType, aData );
+
+ if( ! bSuccess )
+ {
+ throw UnsupportedFlavorException( rFlavor.MimeType, static_cast < XTransferable * > ( this ) );
+ }
+ if( rFlavor.MimeType.equalsIgnoreAsciiCase( "text/plain;charset=utf-16" ) )
+ {
+ int nLen = aData.getLength()/2;
+ if( reinterpret_cast<sal_Unicode const *>(aData.getConstArray())[nLen-1] == 0 )
+ nLen--;
+ OUString aString( reinterpret_cast<sal_Unicode const *>(aData.getConstArray()), nLen );
+ SAL_INFO( "vcl.unx.dtrans", "X11Transferable::getTransferData( \"" << rFlavor.MimeType << "\" )\n -> \"" << aString << "\"");
+ aRet <<= aString.replaceAll("\r\n", "\n");
+ }
+ else
+ aRet <<= aData;
+ return aRet;
+}
+
+Sequence< DataFlavor > SAL_CALL X11Transferable::getTransferDataFlavors()
+{
+ Sequence< DataFlavor > aFlavorList;
+ bool bSuccess = m_rManager.getPasteDataTypes( m_aSelection ? m_aSelection : XA_PRIMARY, aFlavorList );
+ if( ! bSuccess && m_aSelection == 0 )
+ m_rManager.getPasteDataTypes( m_rManager.getAtom( "CLIPBOARD" ), aFlavorList );
+
+ return aFlavorList;
+}
+
+sal_Bool SAL_CALL X11Transferable::isDataFlavorSupported( const DataFlavor& aFlavor )
+{
+ if( aFlavor.DataType != cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ {
+ if( ! aFlavor.MimeType.equalsIgnoreAsciiCase( "text/plain;charset=utf-16" ) &&
+ aFlavor.DataType == cppu::UnoType<OUString>::get() )
+ return false;
+ }
+
+ const Sequence< DataFlavor > aFlavors( getTransferDataFlavors() );
+ return std::any_of(aFlavors.begin(), aFlavors.end(),
+ [&aFlavor](const DataFlavor& rFlavor) {
+ return aFlavor.MimeType.equalsIgnoreAsciiCase( rFlavor.MimeType )
+ && aFlavor.DataType == rFlavor.DataType;
+ });
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/X11_transferable.hxx b/vcl/unx/generic/dtrans/X11_transferable.hxx
new file mode 100644
index 000000000..23cb9dd37
--- /dev/null
+++ b/vcl/unx/generic/dtrans/X11_transferable.hxx
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "X11_selection.hxx"
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+
+#include <cppuhelper/implbase.hxx>
+
+namespace x11 {
+
+ class X11Transferable : public ::cppu::WeakImplHelper< css::datatransfer::XTransferable >
+ {
+ SelectionManager& m_rManager;
+ Atom m_aSelection;
+ public:
+ X11Transferable( SelectionManager& rManager, Atom selection );
+ virtual ~X11Transferable() override;
+
+ /*
+ * XTransferable
+ */
+
+ virtual css::uno::Any SAL_CALL getTransferData( const css::datatransfer::DataFlavor& aFlavor ) override;
+
+ virtual css::uno::Sequence< css::datatransfer::DataFlavor > SAL_CALL getTransferDataFlavors( ) override;
+
+ virtual sal_Bool SAL_CALL isDataFlavorSupported( const css::datatransfer::DataFlavor& aFlavor ) override;
+ };
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/bmp.cxx b/vcl/unx/generic/dtrans/bmp.cxx
new file mode 100644
index 000000000..ac8e50cc2
--- /dev/null
+++ b/vcl/unx/generic/dtrans/bmp.cxx
@@ -0,0 +1,786 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/stream.hxx>
+
+#include <vcl/dibtools.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapSimpleColorQuantizationFilter.hxx>
+
+#include <sal/log.hxx>
+#include <unx/x11/xlimits.hxx>
+
+#include "bmp.hxx"
+
+using namespace x11;
+
+/*
+ * helper functions
+ */
+
+static void writeLE( sal_uInt16 nNumber, sal_uInt8* pBuffer )
+{
+ pBuffer[ 0 ] = (nNumber & 0xff);
+ pBuffer[ 1 ] = ((nNumber>>8)&0xff);
+}
+
+static void writeLE( sal_uInt32 nNumber, sal_uInt8* pBuffer )
+{
+ pBuffer[ 0 ] = (nNumber & 0xff);
+ pBuffer[ 1 ] = ((nNumber>>8)&0xff);
+ pBuffer[ 2 ] = ((nNumber>>16)&0xff);
+ pBuffer[ 3 ] = ((nNumber>>24)&0xff);
+}
+
+static sal_uInt16 readLE16( const sal_uInt8* pBuffer )
+{
+ //This is untainted data which comes from a controlled source
+ //so, using a byte-swapping pattern which coverity doesn't
+ //detect as such
+ //http://security.coverity.com/blog/2014/Apr/on-detecting-heartbleed-with-static-analysis.html
+ sal_uInt16 v = pBuffer[1]; v <<= 8;
+ v |= pBuffer[0];
+ return v;
+}
+
+static sal_uInt32 readLE32( const sal_uInt8* pBuffer )
+{
+ //This is untainted data which comes from a controlled source
+ //so, using a byte-swapping pattern which coverity doesn't
+ //detect as such
+ //http://security.coverity.com/blog/2014/Apr/on-detecting-heartbleed-with-static-analysis.html
+ sal_uInt32 v = pBuffer[3]; v <<= 8;
+ v |= pBuffer[2]; v <<= 8;
+ v |= pBuffer[1]; v <<= 8;
+ v |= pBuffer[0];
+ return v;
+}
+
+/*
+ * scanline helpers
+ */
+
+static void X11_writeScanlinePixel( unsigned long nColor, sal_uInt8* pScanline, int depth, int x )
+{
+ switch( depth )
+ {
+ case 1:
+ pScanline[ x/8 ] &= ~(1 << (x&7));
+ pScanline[ x/8 ] |= ((nColor & 1) << (x&7));
+ break;
+ case 4:
+ pScanline[ x/2 ] &= ((x&1) ? 0x0f : 0xf0);
+ pScanline[ x/2 ] |= ((x&1) ? (nColor & 0x0f) : ((nColor & 0x0f) << 4));
+ break;
+ default:
+ case 8:
+ pScanline[ x ] = (nColor & 0xff);
+ break;
+ }
+}
+
+static sal_uInt8* X11_getPaletteBmpFromImage(
+ Display* pDisplay,
+ XImage* pImage,
+ Colormap aColormap,
+ sal_Int32& rOutSize
+ )
+{
+ sal_uInt32 nColors = 0;
+
+ rOutSize = 0;
+
+ sal_uInt8* pBuffer = nullptr;
+ sal_uInt32 nHeaderSize, nScanlineSize;
+ sal_uInt16 nBitCount;
+ // determine header and scanline size
+ switch( pImage->depth )
+ {
+ case 1:
+ nHeaderSize = 64;
+ nScanlineSize = (pImage->width+31)/32;
+ nBitCount = 1;
+ break;
+ case 4:
+ nHeaderSize = 72;
+ nScanlineSize = (pImage->width+1)/2;
+ nBitCount = 4;
+ break;
+ default:
+ case 8:
+ nHeaderSize = 1084;
+ nScanlineSize = pImage->width;
+ nBitCount = 8;
+ break;
+ }
+ // adjust scan lines to begin on %4 boundaries
+ if( nScanlineSize & 3 )
+ {
+ nScanlineSize &= 0xfffffffc;
+ nScanlineSize += 4;
+ }
+
+ // allocate buffer to hold header and scanlines, initialize to zero
+ rOutSize = nHeaderSize + nScanlineSize*pImage->height;
+ pBuffer = static_cast<sal_uInt8*>(rtl_allocateZeroMemory( rOutSize ));
+ for( int y = 0; y < pImage->height; y++ )
+ {
+ sal_uInt8* pScanline = pBuffer + nHeaderSize + (pImage->height-1-y)*nScanlineSize;
+ for( int x = 0; x < pImage->width; x++ )
+ {
+ unsigned long nPixel = XGetPixel( pImage, x, y );
+ if( nPixel >= nColors )
+ nColors = nPixel+1;
+ X11_writeScanlinePixel( nPixel, pScanline, pImage->depth, x );
+ }
+ }
+
+ // fill in header fields
+ pBuffer[ 0 ] = 'B';
+ pBuffer[ 1 ] = 'M';
+
+ writeLE( nHeaderSize, pBuffer+10 );
+ writeLE( sal_uInt32(40), pBuffer+14 );
+ writeLE( static_cast<sal_uInt32>(pImage->width), pBuffer+18 );
+ writeLE( static_cast<sal_uInt32>(pImage->height), pBuffer+22 );
+ writeLE( sal_uInt16(1), pBuffer+26 );
+ writeLE( nBitCount, pBuffer+28 );
+ writeLE( static_cast<sal_uInt32>(DisplayWidth(pDisplay,DefaultScreen(pDisplay))*1000/DisplayWidthMM(pDisplay,DefaultScreen(pDisplay))), pBuffer+38);
+ writeLE( static_cast<sal_uInt32>(DisplayHeight(pDisplay,DefaultScreen(pDisplay))*1000/DisplayHeightMM(pDisplay,DefaultScreen(pDisplay))), pBuffer+42);
+ writeLE( nColors, pBuffer+46 );
+ writeLE( nColors, pBuffer+50 );
+
+ XColor aColors[256];
+ if( nColors > (1U << nBitCount) ) // paranoia
+ nColors = (1U << nBitCount);
+ for( unsigned long nPixel = 0; nPixel < nColors; nPixel++ )
+ {
+ aColors[nPixel].flags = DoRed | DoGreen | DoBlue;
+ aColors[nPixel].pixel = nPixel;
+ }
+ XQueryColors( pDisplay, aColormap, aColors, nColors );
+ for( sal_uInt32 i = 0; i < nColors; i++ )
+ {
+ pBuffer[ 54 + i*4 ] = static_cast<sal_uInt8>(aColors[i].blue >> 8);
+ pBuffer[ 55 + i*4 ] = static_cast<sal_uInt8>(aColors[i].green >> 8);
+ pBuffer[ 56 + i*4 ] = static_cast<sal_uInt8>(aColors[i].red >> 8);
+ }
+
+ // done
+
+ return pBuffer;
+}
+
+static unsigned long doRightShift( unsigned long nValue, int nShift )
+{
+ return (nShift > 0) ? (nValue >> nShift) : (nValue << (-nShift));
+}
+
+static unsigned long doLeftShift( unsigned long nValue, int nShift )
+{
+ return (nShift > 0) ? (nValue << nShift) : (nValue >> (-nShift));
+}
+
+static void getShift( unsigned long nMask, int& rShift, int& rSigBits, int& rShift2 )
+{
+ unsigned long nUseMask = nMask;
+ rShift = 0;
+ while( nMask & 0xffffff00 )
+ {
+ rShift++;
+ nMask >>= 1;
+ }
+ if( rShift == 0 )
+ while( ! (nMask & 0x00000080) )
+ {
+ rShift--;
+ nMask <<= 1;
+ }
+
+ int nRotate = sizeof(unsigned long)*8 - rShift;
+ rSigBits = 0;
+ nMask = doRightShift( nUseMask, rShift) ;
+ while( nRotate-- )
+ {
+ if( nMask & 1 )
+ rSigBits++;
+ nMask >>= 1;
+ }
+
+ rShift2 = 0;
+ if( rSigBits < 8 )
+ rShift2 = 8-rSigBits;
+}
+
+static sal_uInt8* X11_getTCBmpFromImage(
+ Display* pDisplay,
+ XImage* pImage,
+ sal_Int32& rOutSize,
+ int nScreenNo
+ )
+{
+ // get masks from visual info (guesswork)
+ XVisualInfo aVInfo;
+ if( ! XMatchVisualInfo( pDisplay, nScreenNo, pImage->depth, TrueColor, &aVInfo ) )
+ return nullptr;
+
+ rOutSize = 0;
+
+ sal_uInt8* pBuffer = nullptr;
+ sal_uInt32 nHeaderSize = 60;
+ sal_uInt32 nScanlineSize = pImage->width*3;
+
+ // adjust scan lines to begin on %4 boundaries
+ if( nScanlineSize & 3 )
+ {
+ nScanlineSize &= 0xfffffffc;
+ nScanlineSize += 4;
+ }
+ int nRedShift, nRedSig, nRedShift2 = 0;
+ getShift( aVInfo.red_mask, nRedShift, nRedSig, nRedShift2 );
+ int nGreenShift, nGreenSig, nGreenShift2 = 0;
+ getShift( aVInfo.green_mask, nGreenShift, nGreenSig, nGreenShift2 );
+ int nBlueShift, nBlueSig, nBlueShift2 = 0;
+ getShift( aVInfo.blue_mask, nBlueShift, nBlueSig, nBlueShift2 );
+
+ // allocate buffer to hold header and scanlines, initialize to zero
+ rOutSize = nHeaderSize + nScanlineSize*pImage->height;
+ pBuffer = static_cast<sal_uInt8*>(rtl_allocateZeroMemory( rOutSize ));
+ for( int y = 0; y < pImage->height; y++ )
+ {
+ sal_uInt8* pScanline = pBuffer + nHeaderSize + (pImage->height-1-y)*nScanlineSize;
+ for( int x = 0; x < pImage->width; x++ )
+ {
+ unsigned long nPixel = XGetPixel( pImage, x, y );
+
+ sal_uInt8 nValue = static_cast<sal_uInt8>(doRightShift( nPixel&aVInfo.blue_mask, nBlueShift));
+ if( nBlueShift2 )
+ nValue |= (nValue >> nBlueShift2 );
+ *pScanline++ = nValue;
+
+ nValue = static_cast<sal_uInt8>(doRightShift( nPixel&aVInfo.green_mask, nGreenShift));
+ if( nGreenShift2 )
+ nValue |= (nValue >> nGreenShift2 );
+ *pScanline++ = nValue;
+
+ nValue = static_cast<sal_uInt8>(doRightShift( nPixel&aVInfo.red_mask, nRedShift));
+ if( nRedShift2 )
+ nValue |= (nValue >> nRedShift2 );
+ *pScanline++ = nValue;
+ }
+ }
+
+ // fill in header fields
+ pBuffer[ 0 ] = 'B';
+ pBuffer[ 1 ] = 'M';
+
+ writeLE( nHeaderSize, pBuffer+10 );
+ writeLE( sal_uInt32(40), pBuffer+14 );
+ writeLE( static_cast<sal_uInt32>(pImage->width), pBuffer+18 );
+ writeLE( static_cast<sal_uInt32>(pImage->height), pBuffer+22 );
+ writeLE( sal_uInt16(1), pBuffer+26 );
+ writeLE( sal_uInt16(24), pBuffer+28 );
+ writeLE( static_cast<sal_uInt32>(DisplayWidth(pDisplay,DefaultScreen(pDisplay))*1000/DisplayWidthMM(pDisplay,DefaultScreen(pDisplay))), pBuffer+38);
+ writeLE( static_cast<sal_uInt32>(DisplayHeight(pDisplay,DefaultScreen(pDisplay))*1000/DisplayHeightMM(pDisplay,DefaultScreen(pDisplay))), pBuffer+42);
+
+ // done
+
+ return pBuffer;
+}
+
+sal_uInt8* x11::X11_getBmpFromPixmap(
+ Display* pDisplay,
+ Drawable aDrawable,
+ Colormap aColormap,
+ sal_Int32& rOutSize
+ )
+{
+ // get geometry of drawable
+ ::Window aRoot;
+ int x,y;
+ unsigned int w, h, bw, d;
+ XGetGeometry( pDisplay, aDrawable, &aRoot, &x, &y, &w, &h, &bw, &d );
+
+ // find which screen we are on
+ int nScreenNo = ScreenCount( pDisplay );
+ while( nScreenNo-- )
+ {
+ if( RootWindow( pDisplay, nScreenNo ) == aRoot )
+ break;
+ }
+ if( nScreenNo < 0 )
+ return nullptr;
+
+ if( aColormap == None )
+ aColormap = DefaultColormap( pDisplay, nScreenNo );
+
+ // get the image
+ XImage* pImage = XGetImage( pDisplay, aDrawable, 0, 0, w, h, AllPlanes, ZPixmap );
+ if( ! pImage )
+ return nullptr;
+
+ sal_uInt8* pBmp = d <= 8 ?
+ X11_getPaletteBmpFromImage( pDisplay, pImage, aColormap, rOutSize ) :
+ X11_getTCBmpFromImage( pDisplay, pImage, rOutSize, nScreenNo );
+ XDestroyImage( pImage );
+
+ return pBmp;
+}
+
+/*
+ * PixmapHolder
+ */
+
+PixmapHolder::PixmapHolder( Display* pDisplay )
+ : m_pDisplay(pDisplay)
+ , m_aColormap(None)
+ , m_aPixmap(None)
+ , m_aBitmap(None)
+ , m_nRedShift(0)
+ , m_nGreenShift(0)
+ , m_nBlueShift(0)
+ , m_nBlueShift2Mask(0)
+ , m_nRedShift2Mask(0)
+ , m_nGreenShift2Mask(0)
+{
+ /* try to get a 24 bit true color visual, if that fails,
+ * revert to default visual
+ */
+ if( ! XMatchVisualInfo( m_pDisplay, DefaultScreen( m_pDisplay ), 24, TrueColor, &m_aInfo ) )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "PixmapHolder reverting to default visual.");
+#endif
+ Visual* pVisual = DefaultVisual( m_pDisplay, DefaultScreen( m_pDisplay ) );
+ m_aInfo.screen = DefaultScreen( m_pDisplay );
+ m_aInfo.visual = pVisual;
+ m_aInfo.visualid = pVisual->visualid;
+ m_aInfo.c_class = pVisual->c_class;
+ m_aInfo.red_mask = pVisual->red_mask;
+ m_aInfo.green_mask = pVisual->green_mask;
+ m_aInfo.blue_mask = pVisual->blue_mask;
+ m_aInfo.depth = DefaultDepth( m_pDisplay, m_aInfo.screen );
+ }
+ m_aColormap = DefaultColormap( m_pDisplay, m_aInfo.screen );
+#if OSL_DEBUG_LEVEL > 1
+ static const char* pClasses[] =
+ { "StaticGray", "GrayScale", "StaticColor", "PseudoColor", "TrueColor", "DirectColor" };
+ SAL_INFO("vcl.unx.dtrans", "PixmapHolder visual: id = "
+ << std::showbase << std::hex
+ << m_aInfo.visualid
+ << ", class = "
+ << ((m_aInfo.c_class >= 0 &&
+ unsigned(m_aInfo.c_class) <
+ SAL_N_ELEMENTS(pClasses)) ?
+ pClasses[m_aInfo.c_class] :
+ "<unknown>")
+ << " ("
+ << std::dec
+ << m_aInfo.c_class
+ << "), depth="
+ << m_aInfo.depth
+ << "; color map = "
+ << std::showbase << std::hex
+ << m_aColormap);
+#endif
+ if( m_aInfo.c_class != TrueColor )
+ return;
+
+ int nRedShift2(0);
+ int nGreenShift2(0);
+ int nBlueShift2(0);
+ int nRedSig, nGreenSig, nBlueSig;
+ getShift( m_aInfo.red_mask, m_nRedShift, nRedSig, nRedShift2 );
+ getShift( m_aInfo.green_mask, m_nGreenShift, nGreenSig, nGreenShift2 );
+ getShift( m_aInfo.blue_mask, m_nBlueShift, nBlueSig, nBlueShift2 );
+
+ m_nBlueShift2Mask = nBlueShift2 ? ~static_cast<unsigned long>((1<<nBlueShift2)-1) : ~0L;
+ m_nGreenShift2Mask = nGreenShift2 ? ~static_cast<unsigned long>((1<<nGreenShift2)-1) : ~0L;
+ m_nRedShift2Mask = nRedShift2 ? ~static_cast<unsigned long>((1<<nRedShift2)-1) : ~0L;
+}
+
+PixmapHolder::~PixmapHolder()
+{
+ if( m_aPixmap != None )
+ XFreePixmap( m_pDisplay, m_aPixmap );
+ if( m_aBitmap != None )
+ XFreePixmap( m_pDisplay, m_aBitmap );
+}
+
+unsigned long PixmapHolder::getTCPixel( sal_uInt8 r, sal_uInt8 g, sal_uInt8 b ) const
+{
+ unsigned long nPixel = 0;
+ unsigned long nValue = static_cast<unsigned long>(b);
+ nValue &= m_nBlueShift2Mask;
+ nPixel |= doLeftShift( nValue, m_nBlueShift );
+
+ nValue = static_cast<unsigned long>(g);
+ nValue &= m_nGreenShift2Mask;
+ nPixel |= doLeftShift( nValue, m_nGreenShift );
+
+ nValue = static_cast<unsigned long>(r);
+ nValue &= m_nRedShift2Mask;
+ nPixel |= doLeftShift( nValue, m_nRedShift );
+
+ return nPixel;
+}
+
+void PixmapHolder::setBitmapDataPalette( const sal_uInt8* pData, XImage* pImage )
+{
+ // setup palette
+ XColor aPalette[256];
+
+ sal_uInt32 nColors = readLE32( pData+32 );
+ sal_uInt32 nWidth = readLE32( pData+4 );
+ sal_uInt32 nHeight = readLE32( pData+8 );
+ sal_uInt16 nDepth = readLE16( pData+14 );
+
+ for( sal_uInt32 i = 0 ; i < nColors; i++ )
+ {
+ if( m_aInfo.c_class != TrueColor )
+ {
+ //This is untainted data which comes from a controlled source
+ //so, using a byte-swapping pattern which coverity doesn't
+ //detect as such
+ //http://security.coverity.com/blog/2014/Apr/on-detecting-heartbleed-with-static-analysis.html
+ aPalette[i].red = static_cast<unsigned short>(pData[42 + i*4]);
+ aPalette[i].red <<= 8;
+ aPalette[i].red |= static_cast<unsigned short>(pData[42 + i*4]);
+
+ aPalette[i].green = static_cast<unsigned short>(pData[41 + i*4]);
+ aPalette[i].green <<= 8;
+ aPalette[i].green |= static_cast<unsigned short>(pData[41 + i*4]);
+
+ aPalette[i].blue = static_cast<unsigned short>(pData[40 + i*4]);
+ aPalette[i].blue <<= 8;
+ aPalette[i].blue |= static_cast<unsigned short>(pData[40 + i*4]);
+ XAllocColor( m_pDisplay, m_aColormap, aPalette+i );
+ }
+ else
+ aPalette[i].pixel = getTCPixel( pData[42+i*4], pData[41+i*4], pData[40+i*4] );
+ }
+ const sal_uInt8* pBMData = pData + readLE32( pData ) + 4*nColors;
+
+ sal_uInt32 nScanlineSize = 0;
+ switch( nDepth )
+ {
+ case 1:
+ nScanlineSize = (nWidth+31)/32;
+ break;
+ case 4:
+ nScanlineSize = (nWidth+1)/2;
+ break;
+ case 8:
+ nScanlineSize = nWidth;
+ break;
+ }
+ // adjust scan lines to begin on %4 boundaries
+ if( nScanlineSize & 3 )
+ {
+ nScanlineSize &= 0xfffffffc;
+ nScanlineSize += 4;
+ }
+
+ // allocate buffer to hold header and scanlines, initialize to zero
+ for( unsigned int y = 0; y < nHeight; y++ )
+ {
+ const sal_uInt8* pScanline = pBMData + (nHeight-1-y)*nScanlineSize;
+ for( unsigned int x = 0; x < nWidth; x++ )
+ {
+ int nCol = 0;
+ switch( nDepth )
+ {
+ case 1: nCol = (pScanline[ x/8 ] & (0x80 >> (x&7))) != 0 ? 0 : 1; break;
+ case 4:
+ if( x & 1 )
+ nCol = static_cast<int>(pScanline[ x/2 ] >> 4);
+ else
+ nCol = static_cast<int>(pScanline[ x/2 ] & 0x0f);
+ break;
+ case 8: nCol = static_cast<int>(pScanline[x]);
+ }
+ XPutPixel( pImage, x, y, aPalette[nCol].pixel );
+ }
+ }
+}
+
+void PixmapHolder::setBitmapDataTCDither( const sal_uInt8* pData, XImage* pImage )
+{
+ XColor aPalette[216];
+
+ int nNonAllocs = 0;
+
+ for( int r = 0; r < 6; r++ )
+ {
+ for( int g = 0; g < 6; g++ )
+ {
+ for( int b = 0; b < 6; b++ )
+ {
+ int i = r*36+g*6+b;
+ aPalette[i].red = r == 5 ? 0xffff : r*10922;
+ aPalette[i].green = g == 5 ? 0xffff : g*10922;
+ aPalette[i].blue = b == 5 ? 0xffff : b*10922;
+ aPalette[i].pixel = 0;
+ if( ! XAllocColor( m_pDisplay, m_aColormap, aPalette+i ) )
+ nNonAllocs++;
+ }
+ }
+ }
+
+ if( nNonAllocs )
+ {
+ XColor aRealPalette[256];
+ int nColors = 1 << m_aInfo.depth;
+ int i;
+ for( i = 0; i < nColors; i++ )
+ aRealPalette[i].pixel = static_cast<unsigned long>(i);
+ XQueryColors( m_pDisplay, m_aColormap, aRealPalette, nColors );
+ for( i = 0; i < nColors; i++ )
+ {
+ sal_uInt8 nIndex =
+ 36*static_cast<sal_uInt8>(aRealPalette[i].red/10923) +
+ 6*static_cast<sal_uInt8>(aRealPalette[i].green/10923) +
+ static_cast<sal_uInt8>(aRealPalette[i].blue/10923);
+ if( aPalette[nIndex].pixel == 0 )
+ aPalette[nIndex] = aRealPalette[i];
+ }
+ }
+
+ sal_uInt32 nWidth = readLE32( pData+4 );
+ sal_uInt32 nHeight = readLE32( pData+8 );
+
+ const sal_uInt8* pBMData = pData + readLE32( pData );
+ sal_uInt32 nScanlineSize = nWidth*3;
+ // adjust scan lines to begin on %4 boundaries
+ if( nScanlineSize & 3 )
+ {
+ nScanlineSize &= 0xfffffffc;
+ nScanlineSize += 4;
+ }
+
+ for( int y = 0; y < static_cast<int>(nHeight); y++ )
+ {
+ const sal_uInt8* pScanline = pBMData + (nHeight-1-static_cast<sal_uInt32>(y))*nScanlineSize;
+ for( int x = 0; x < static_cast<int>(nWidth); x++ )
+ {
+ sal_uInt8 b = pScanline[3*x];
+ sal_uInt8 g = pScanline[3*x+1];
+ sal_uInt8 r = pScanline[3*x+2];
+ sal_uInt8 i = 36*(r/43) + 6*(g/43) + (b/43);
+
+ XPutPixel( pImage, x, y, aPalette[ i ].pixel );
+ }
+ }
+}
+
+void PixmapHolder::setBitmapDataTC( const sal_uInt8* pData, XImage* pImage )
+{
+ sal_uInt32 nWidth = readLE32( pData+4 );
+ sal_uInt32 nHeight = readLE32( pData+8 );
+
+ if (!nWidth || !nHeight)
+ return;
+
+ const sal_uInt8* pBMData = pData + readLE32( pData );
+ sal_uInt32 nScanlineSize = nWidth*3;
+ // adjust scan lines to begin on %4 boundaries
+ if( nScanlineSize & 3 )
+ {
+ nScanlineSize &= 0xfffffffc;
+ nScanlineSize += 4;
+ }
+
+ for( int y = 0; y < static_cast<int>(nHeight); y++ )
+ {
+ const sal_uInt8* pScanline = pBMData + (nHeight-1-static_cast<sal_uInt32>(y))*nScanlineSize;
+ for( int x = 0; x < static_cast<int>(nWidth); x++ )
+ {
+ unsigned long nPixel = getTCPixel( pScanline[3*x+2], pScanline[3*x+1], pScanline[3*x] );
+ XPutPixel( pImage, x, y, nPixel );
+ }
+ }
+}
+
+bool PixmapHolder::needsConversion( const sal_uInt8* pData ) const
+{
+ if( pData[0] != 'B' || pData[1] != 'M' )
+ return true;
+
+ pData = pData+14;
+ sal_uInt32 nDepth = readLE32( pData+14 );
+ if( nDepth == 24 )
+ {
+ if( m_aInfo.c_class != TrueColor )
+ return true;
+ }
+ else if( nDepth != static_cast<sal_uInt32>(m_aInfo.depth) )
+ {
+ if( m_aInfo.c_class != TrueColor )
+ return true;
+ }
+
+ return false;
+}
+
+Pixmap PixmapHolder::setBitmapData( const sal_uInt8* pData )
+{
+ if( pData[0] != 'B' || pData[1] != 'M' )
+ return None;
+
+ pData = pData+14;
+
+ // reject compressed data
+ if( readLE32( pData + 16 ) != 0 )
+ return None;
+
+ sal_uInt32 nWidth = readLE32( pData+4 );
+ sal_uInt32 nHeight = readLE32( pData+8 );
+
+ if( m_aPixmap != None )
+ {
+ XFreePixmap( m_pDisplay, m_aPixmap );
+ m_aPixmap = None;
+ }
+ if( m_aBitmap != None )
+ {
+ XFreePixmap( m_pDisplay, m_aBitmap );
+ m_aBitmap = None;
+ }
+
+ m_aPixmap = limitXCreatePixmap( m_pDisplay,
+ RootWindow( m_pDisplay, m_aInfo.screen ),
+ nWidth, nHeight, m_aInfo.depth );
+
+ if( m_aPixmap != None )
+ {
+ XImage aImage;
+ aImage.width = static_cast<int>(nWidth);
+ aImage.height = static_cast<int>(nHeight);
+ aImage.xoffset = 0;
+ aImage.format = ZPixmap;
+ aImage.data = nullptr;
+ aImage.byte_order = ImageByteOrder( m_pDisplay );
+ aImage.bitmap_unit = BitmapUnit( m_pDisplay );
+ aImage.bitmap_bit_order = BitmapBitOrder( m_pDisplay );
+ aImage.bitmap_pad = BitmapPad( m_pDisplay );
+ aImage.depth = m_aInfo.depth;
+ aImage.red_mask = m_aInfo.red_mask;
+ aImage.green_mask = m_aInfo.green_mask;
+ aImage.blue_mask = m_aInfo.blue_mask;
+ aImage.bytes_per_line = 0; // filled in by XInitImage
+ if( m_aInfo.depth <= 8 )
+ aImage.bits_per_pixel = m_aInfo.depth;
+ else
+ aImage.bits_per_pixel = 8*((m_aInfo.depth+7)/8);
+ aImage.obdata = nullptr;
+
+ XInitImage( &aImage );
+ aImage.data = static_cast<char*>(std::malloc( nHeight*aImage.bytes_per_line ));
+
+ if( readLE32( pData+14 ) == 24 )
+ {
+ if( m_aInfo.c_class == TrueColor )
+ setBitmapDataTC( pData, &aImage );
+ else
+ setBitmapDataTCDither( pData, &aImage );
+ }
+ else
+ setBitmapDataPalette( pData, &aImage );
+
+ // put the image
+ XPutImage( m_pDisplay,
+ m_aPixmap,
+ DefaultGC( m_pDisplay, m_aInfo.screen ),
+ &aImage,
+ 0, 0,
+ 0, 0,
+ nWidth, nHeight );
+
+ // clean up
+ std::free( aImage.data );
+
+ // prepare bitmap (mask)
+ m_aBitmap = limitXCreatePixmap( m_pDisplay,
+ RootWindow( m_pDisplay, m_aInfo.screen ),
+ nWidth, nHeight, 1 );
+ XGCValues aVal;
+ aVal.function = GXcopy;
+ aVal.foreground = 0xffffffff;
+ GC aGC = XCreateGC( m_pDisplay, m_aBitmap, GCFunction | GCForeground, &aVal );
+ XFillRectangle( m_pDisplay, m_aBitmap, aGC, 0, 0, nWidth, nHeight );
+ XFreeGC( m_pDisplay, aGC );
+ }
+
+ return m_aPixmap;
+}
+
+css::uno::Sequence<sal_Int8> x11::convertBitmapDepth(
+ css::uno::Sequence<sal_Int8> const & data, int depth)
+{
+ if (depth < 4) {
+ depth = 1;
+ } else if (depth < 8) {
+ depth = 4;
+ } else if (depth > 8 && depth < 24) {
+ depth = 24;
+ }
+ SolarMutexGuard g;
+ SvMemoryStream in(
+ const_cast<sal_Int8 *>(data.getConstArray()), data.getLength(),
+ StreamMode::READ);
+ Bitmap bm;
+ ReadDIB(bm, in, true);
+ if (bm.getPixelFormat() == vcl::PixelFormat::N24_BPP && depth <= 8) {
+ bm.Dither();
+ }
+ if (vcl::pixelFormatBitCount(bm.getPixelFormat()) != depth) {
+ switch (depth) {
+ case 1:
+ bm.Convert(BmpConversion::N1BitThreshold);
+ break;
+ case 4:
+ {
+ BitmapEx aBmpEx(bm);
+ BitmapFilter::Filter(aBmpEx, BitmapSimpleColorQuantizationFilter(1<<4));
+ bm = aBmpEx.GetBitmap();
+ }
+ break;
+
+ case 8:
+ {
+ BitmapEx aBmpEx(bm);
+ BitmapFilter::Filter(aBmpEx, BitmapSimpleColorQuantizationFilter(1<<8));
+ bm = aBmpEx.GetBitmap();
+ }
+ break;
+
+ case 24:
+ bm.Convert(BmpConversion::N24Bit);
+ break;
+ }
+ }
+ SvMemoryStream out;
+ WriteDIB(bm, out, false, true);
+ return css::uno::Sequence<sal_Int8>(
+ static_cast<sal_Int8 const *>(out.GetData()), out.GetEndOfData());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/bmp.hxx b/vcl/unx/generic/dtrans/bmp.hxx
new file mode 100644
index 000000000..3a37158db
--- /dev/null
+++ b/vcl/unx/generic/dtrans/bmp.hxx
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <com/sun/star/uno/Sequence.hxx>
+#include <sal/types.h>
+
+namespace x11 {
+
+// helper methods
+sal_uInt8* X11_getBmpFromPixmap( Display* pDisplay,
+ Drawable aDrawable,
+ Colormap aColormap,
+ sal_Int32& rOutSize );
+
+class PixmapHolder
+{
+ Display* m_pDisplay;
+ Colormap m_aColormap;
+ Pixmap m_aPixmap;
+ Pixmap m_aBitmap;
+ XVisualInfo m_aInfo;
+
+ int m_nRedShift;
+ int m_nGreenShift;
+ int m_nBlueShift;
+ tools::ULong m_nBlueShift2Mask, m_nRedShift2Mask, m_nGreenShift2Mask;
+
+ // these expect data pointers to bitmapinfo header
+ void setBitmapDataTC( const sal_uInt8* pData, XImage* pImage );
+ void setBitmapDataTCDither( const sal_uInt8* pData, XImage* pImage );
+ void setBitmapDataPalette( const sal_uInt8* pData, XImage* pImage );
+
+ tools::ULong getTCPixel( sal_uInt8 r, sal_uInt8 g, sal_uInt8 b ) const;
+public:
+ PixmapHolder( Display* pDisplay );
+ ~PixmapHolder();
+
+ // accepts bitmap file (including bitmap file header)
+ Pixmap setBitmapData( const sal_uInt8* pData );
+ bool needsConversion( const sal_uInt8* pData ) const;
+
+ Colormap getColormap() const { return m_aColormap; }
+ Pixmap getPixmap() const { return m_aPixmap; }
+ Pixmap getBitmap() const { return m_aBitmap; }
+ VisualID getVisualID() const { return m_aInfo.visualid; }
+ int getDepth() const { return m_aInfo.depth; }
+};
+
+css::uno::Sequence<sal_Int8> convertBitmapDepth(
+ css::uno::Sequence<sal_Int8> const & data, int depth);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/config.cxx b/vcl/unx/generic/dtrans/config.cxx
new file mode 100644
index 000000000..1d851a28d
--- /dev/null
+++ b/vcl/unx/generic/dtrans/config.cxx
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <o3tl/any.hxx>
+#include <sal/log.hxx>
+#include <unotools/configitem.hxx>
+
+#include "X11_selection.hxx"
+
+constexpr OUStringLiteral SETTINGS_CONFIGNODE = u"VCL/Settings/Transfer";
+constexpr OUStringLiteral SELECTION_PROPERTY = u"SelectionTimeout";
+
+namespace x11
+{
+
+namespace {
+
+class DtransX11ConfigItem : public ::utl::ConfigItem
+{
+ sal_Int32 m_nSelectionTimeout;
+
+ virtual void Notify( const css::uno::Sequence< OUString >& rPropertyNames ) override;
+ virtual void ImplCommit() override;
+
+public:
+ DtransX11ConfigItem();
+
+ sal_Int32 getSelectionTimeout() const { return m_nSelectionTimeout; }
+};
+
+}
+
+}
+
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace x11;
+
+sal_Int32 SelectionManager::getSelectionTimeout()
+{
+ if( m_nSelectionTimeout < 1 )
+ {
+ DtransX11ConfigItem aCfg;
+ m_nSelectionTimeout = aCfg.getSelectionTimeout();
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "initialized selection timeout to "
+ << m_nSelectionTimeout
+ << " seconds.");
+#endif
+ }
+ return m_nSelectionTimeout;
+}
+
+/*
+ * DtransX11ConfigItem constructor
+ */
+
+DtransX11ConfigItem::DtransX11ConfigItem() :
+ ConfigItem( SETTINGS_CONFIGNODE,
+ ConfigItemMode::NONE ),
+ m_nSelectionTimeout( 3 )
+{
+ Sequence<OUString> aKeys { SELECTION_PROPERTY };
+ const Sequence< Any > aValues = GetProperties( aKeys );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "found "
+ << aValues.getLength()
+ << " properties for "
+ << SELECTION_PROPERTY);
+#endif
+ for( Any const & value : aValues )
+ {
+ if( auto pLine = o3tl::tryAccess<OUString>(value) )
+ {
+ if( !pLine->isEmpty() )
+ {
+ m_nSelectionTimeout = pLine->toInt32();
+ if( m_nSelectionTimeout < 1 )
+ m_nSelectionTimeout = 1;
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.dtrans", "found SelectionTimeout \"" << *pLine << "\".");
+#endif
+ }
+#if OSL_DEBUG_LEVEL > 1
+ else
+ SAL_INFO("vcl.unx.dtrans", "found SelectionTimeout of type \""
+ << value.getValueType().getTypeName() << "\".");
+#endif
+ }
+}
+
+void DtransX11ConfigItem::ImplCommit()
+{
+ // for the clipboard service this is readonly, so
+ // there is nothing to commit
+}
+
+/*
+ * DtransX11ConfigItem::Notify
+ */
+
+void DtransX11ConfigItem::Notify( const Sequence< OUString >& /*rPropertyNames*/ )
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/copydata_curs.h b/vcl/unx/generic/dtrans/copydata_curs.h
new file mode 100644
index 000000000..4cc36ebde
--- /dev/null
+++ b/vcl/unx/generic/dtrans/copydata_curs.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define copydata_curs_width 32
+#define copydata_curs_height 32
+#define copydata_curs_x_hot 1
+#define copydata_curs_y_hot 1
+static unsigned char copydata_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
+ 0x7e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00,
+ 0xfe, 0x03, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00,
+ 0x66, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
+ 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x10, 0x53, 0x00, 0x00,
+ 0x28, 0xa3, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x10, 0xf0, 0x1f, 0x00, 0x08, 0xf0, 0x1f, 0x00, 0x10, 0xf0, 0x1e, 0x00,
+ 0xa8, 0xf2, 0x1e, 0x00, 0x50, 0x35, 0x18, 0x00, 0x00, 0xf0, 0x1e, 0x00,
+ 0x00, 0xf0, 0x1e, 0x00, 0x00, 0xf0, 0x1f, 0x00, 0x00, 0xf0, 0x1f, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/copydata_mask.h b/vcl/unx/generic/dtrans/copydata_mask.h
new file mode 100644
index 000000000..a3538c952
--- /dev/null
+++ b/vcl/unx/generic/dtrans/copydata_mask.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define copydata_mask_width 32
+#define copydata_mask_height 32
+#define copydata_mask_x_hot 1
+#define copydata_mask_y_hot 1
+static unsigned char copydata_mask_bits[] = {
+ 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+ 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00,
+ 0xe0, 0x03, 0x00, 0x00, 0xf8, 0xff, 0x00, 0x00, 0xfc, 0xff, 0x01, 0x00,
+ 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0x3c, 0xf8, 0x3f, 0x00,
+ 0x3c, 0xf8, 0x3f, 0x00, 0x3c, 0xf8, 0x3f, 0x00, 0xfc, 0xff, 0x3f, 0x00,
+ 0xfc, 0xff, 0x3f, 0x00, 0xfc, 0xff, 0x3f, 0x00, 0xf8, 0xff, 0x3f, 0x00,
+ 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00,
+ 0x00, 0xf8, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/linkdata_curs.h b/vcl/unx/generic/dtrans/linkdata_curs.h
new file mode 100644
index 000000000..8a4e6db38
--- /dev/null
+++ b/vcl/unx/generic/dtrans/linkdata_curs.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define linkdata_curs_width 32
+#define linkdata_curs_height 32
+#define linkdata_curs_x_hot 1
+#define linkdata_curs_y_hot 1
+static unsigned char linkdata_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
+ 0x7e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00,
+ 0xfe, 0x03, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00,
+ 0x66, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
+ 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x10, 0x53, 0x00, 0x00,
+ 0x28, 0xa3, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x10, 0xf0, 0x1f, 0x00, 0x08, 0x70, 0x18, 0x00, 0x10, 0xf0, 0x18, 0x00,
+ 0xa8, 0x72, 0x18, 0x00, 0x50, 0x35, 0x1a, 0x00, 0x00, 0x30, 0x1f, 0x00,
+ 0x00, 0xb0, 0x1f, 0x00, 0x00, 0x70, 0x1f, 0x00, 0x00, 0xf0, 0x1f, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/linkdata_mask.h b/vcl/unx/generic/dtrans/linkdata_mask.h
new file mode 100644
index 000000000..a1875a8e0
--- /dev/null
+++ b/vcl/unx/generic/dtrans/linkdata_mask.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define linkdata_mask_width 32
+#define linkdata_mask_height 32
+#define linkdata_mask_x_hot 1
+#define linkdata_mask_y_hot 1
+static unsigned char linkdata_mask_bits[] = {
+ 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+ 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00,
+ 0xe0, 0x03, 0x00, 0x00, 0xf8, 0xff, 0x00, 0x00, 0xfc, 0xff, 0x01, 0x00,
+ 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0x3c, 0xf8, 0x3f, 0x00,
+ 0x3c, 0xf8, 0x3f, 0x00, 0x3c, 0xf8, 0x3f, 0x00, 0xfc, 0xff, 0x3f, 0x00,
+ 0xfc, 0xff, 0x3f, 0x00, 0xfc, 0xff, 0x3f, 0x00, 0xf8, 0xff, 0x3f, 0x00,
+ 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00,
+ 0x00, 0xf8, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/movedata_curs.h b/vcl/unx/generic/dtrans/movedata_curs.h
new file mode 100644
index 000000000..b253ce70c
--- /dev/null
+++ b/vcl/unx/generic/dtrans/movedata_curs.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define movedata_curs_width 32
+#define movedata_curs_height 32
+#define movedata_curs_x_hot 1
+#define movedata_curs_y_hot 1
+static unsigned char movedata_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
+ 0x7e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00,
+ 0xfe, 0x03, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00,
+ 0x66, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
+ 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x10, 0x53, 0x00, 0x00,
+ 0x28, 0xa3, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00,
+ 0x10, 0x40, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00,
+ 0xa8, 0xaa, 0x00, 0x00, 0x50, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/movedata_mask.h b/vcl/unx/generic/dtrans/movedata_mask.h
new file mode 100644
index 000000000..d317b1556
--- /dev/null
+++ b/vcl/unx/generic/dtrans/movedata_mask.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define movedata_mask_width 32
+#define movedata_mask_height 32
+#define movedata_mask_x_hot 1
+#define movedata_mask_y_hot 1
+static unsigned char movedata_mask_bits[] = {
+ 0x07, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
+ 0x3f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00,
+ 0xe0, 0x03, 0x00, 0x00, 0xf8, 0xff, 0x00, 0x00, 0xfc, 0xff, 0x01, 0x00,
+ 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0x3c, 0xe0, 0x01, 0x00,
+ 0x3c, 0xe0, 0x01, 0x00, 0x3c, 0xe0, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00,
+ 0xfc, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x01, 0x00, 0xf8, 0xff, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/nodrop_curs.h b/vcl/unx/generic/dtrans/nodrop_curs.h
new file mode 100644
index 000000000..958257518
--- /dev/null
+++ b/vcl/unx/generic/dtrans/nodrop_curs.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define nodrop_curs_width 32
+#define nodrop_curs_height 32
+#define nodrop_curs_x_hot 9
+#define nodrop_curs_y_hot 9
+static unsigned char nodrop_curs_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00,
+ 0xf8, 0x7f, 0x00, 0x00, 0x7c, 0xf8, 0x00, 0x00, 0x1c, 0xfc, 0x00, 0x00,
+ 0x1e, 0xfe, 0x01, 0x00, 0x0e, 0xdf, 0x01, 0x00, 0x8e, 0xcf, 0x01, 0x00,
+ 0xce, 0xc7, 0x01, 0x00, 0xee, 0xc3, 0x01, 0x00, 0xfe, 0xe1, 0x01, 0x00,
+ 0xfc, 0xe0, 0x00, 0x00, 0x7c, 0xf8, 0x00, 0x00, 0xf8, 0x7f, 0x00, 0x00,
+ 0xf0, 0x3f, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/dtrans/nodrop_mask.h b/vcl/unx/generic/dtrans/nodrop_mask.h
new file mode 100644
index 000000000..662a30064
--- /dev/null
+++ b/vcl/unx/generic/dtrans/nodrop_mask.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#define nodrop_mask_width 32
+#define nodrop_mask_height 32
+#define nodrop_mask_x_hot 9
+#define nodrop_mask_y_hot 9
+static unsigned char nodrop_mask_bits[] = {
+ 0xc0, 0x0f, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, 0xf8, 0x7f, 0x00, 0x00,
+ 0xfc, 0xff, 0x00, 0x00, 0xfe, 0xff, 0x01, 0x00, 0x7e, 0xfe, 0x01, 0x00,
+ 0x3f, 0xff, 0x03, 0x00, 0x9f, 0xff, 0x03, 0x00, 0xdf, 0xff, 0x03, 0x00,
+ 0xff, 0xef, 0x03, 0x00, 0xff, 0xe7, 0x03, 0x00, 0xff, 0xf3, 0x03, 0x00,
+ 0xfe, 0xf9, 0x01, 0x00, 0xfe, 0xff, 0x01, 0x00, 0xfc, 0xff, 0x00, 0x00,
+ 0xf8, 0x7f, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, 0xc0, 0x0f, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/fontmanager/fontconfig.cxx b/vcl/unx/generic/fontmanager/fontconfig.cxx
new file mode 100644
index 000000000..c44cdd2ee
--- /dev/null
+++ b/vcl/unx/generic/fontmanager/fontconfig.cxx
@@ -0,0 +1,1316 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#if defined __GNUC__ && !defined __clang_ && __GNUC__ == 10
+// gcc 10.2.0 gets unhappy about one of the OString inside PrintFont at line 656 (while at least a
+// recent GCC 12 trunk is happy);
+// I have to turn it off here because the warning actually occurs inside rtl::OString
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
+
+#include <sal/config.h>
+
+#include <memory>
+#include <string_view>
+
+#include <o3tl/lru_map.hxx>
+#include <unx/fontmanager.hxx>
+#include <unx/helper.hxx>
+#include <comphelper/sequence.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/vclenum.hxx>
+#include <font/FontSelectPattern.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nutil/unicode.hxx>
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/diagnose_ex.h>
+#include <unicode/uchar.h>
+#include <unicode/uscript.h>
+#include <officecfg/Office/Common.hxx>
+#include <org/freedesktop/PackageKit/SyncDbusSessionHelper.hpp>
+#include <config_fonts.h>
+
+using namespace psp;
+
+#include <fontconfig/fontconfig.h>
+
+#include <cstdio>
+
+#include <unotools/configmgr.hxx>
+#include <unotools/syslocaleoptions.hxx>
+
+#include <osl/process.h>
+
+#include <o3tl/hash_combine.hxx>
+#include <utility>
+#include <algorithm>
+
+using namespace osl;
+
+namespace
+{
+
+struct FontOptionsKey
+{
+ OUString m_sFamilyName;
+ int m_nFontSize;
+ FontItalic m_eItalic;
+ FontWeight m_eWeight;
+ FontWidth m_eWidth;
+ FontPitch m_ePitch;
+
+ bool operator==(const FontOptionsKey& rOther) const
+ {
+ return m_sFamilyName == rOther.m_sFamilyName &&
+ m_nFontSize == rOther.m_nFontSize &&
+ m_eItalic == rOther.m_eItalic &&
+ m_eWeight == rOther.m_eWeight &&
+ m_eWidth == rOther.m_eWidth &&
+ m_ePitch == rOther.m_ePitch;
+ }
+};
+
+}
+
+namespace std
+{
+
+template <> struct hash<FontOptionsKey>
+{
+ std::size_t operator()(const FontOptionsKey& k) const noexcept
+ {
+ std::size_t seed = k.m_sFamilyName.hashCode();
+ o3tl::hash_combine(seed, k.m_nFontSize);
+ o3tl::hash_combine(seed, k.m_eItalic);
+ o3tl::hash_combine(seed, k.m_eWeight);
+ o3tl::hash_combine(seed, k.m_eWidth);
+ o3tl::hash_combine(seed, k.m_ePitch);
+ return seed;
+ }
+};
+
+} // end std namespace
+
+namespace
+{
+
+struct FcPatternDeleter
+{
+ void operator()(FcPattern* pPattern) const
+ {
+ FcPatternDestroy(pPattern);
+ }
+};
+
+typedef std::unique_ptr<FcPattern, FcPatternDeleter> FcPatternUniquePtr;
+
+class CachedFontConfigFontOptions
+{
+private:
+ o3tl::lru_map<FontOptionsKey, FcPatternUniquePtr> lru_options_cache;
+
+public:
+ CachedFontConfigFontOptions()
+ : lru_options_cache(10) // arbitrary cache size of 10
+ {
+ }
+
+ std::unique_ptr<FontConfigFontOptions> lookup(const FontOptionsKey& rKey)
+ {
+ auto it = lru_options_cache.find(rKey);
+ if (it != lru_options_cache.end())
+ return std::make_unique<FontConfigFontOptions>(FcPatternDuplicate(it->second.get()));
+ return nullptr;
+ }
+
+ void cache(const FontOptionsKey& rKey, const FcPattern* pPattern)
+ {
+ lru_options_cache.insert(std::make_pair(rKey, FcPatternUniquePtr(FcPatternDuplicate(pPattern))));
+ }
+
+};
+
+typedef std::pair<FcChar8*, FcChar8*> lang_and_element;
+
+class FontCfgWrapper
+{
+ FcFontSet* m_pFontSet;
+
+ void addFontSet( FcSetName );
+
+ FontCfgWrapper();
+ ~FontCfgWrapper();
+
+public:
+ static FontCfgWrapper& get();
+ static void release();
+
+ FcFontSet* getFontSet();
+
+ void clear();
+
+public:
+ FcResult LocalizedElementFromPattern(FcPattern const * pPattern, FcChar8 **family,
+ const char *elementtype, const char *elementlangtype);
+//to-do, make private and add some cleaner accessor methods
+ std::unordered_map< OString, OString > m_aFontNameToLocalized;
+ std::unordered_map< OString, OString > m_aLocalizedToCanonical;
+ CachedFontConfigFontOptions m_aCachedFontOptions;
+private:
+ void cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname, const std::vector< lang_and_element > &lang_and_elements);
+
+ std::unique_ptr<LanguageTag> m_pLanguageTag;
+};
+
+}
+
+FontCfgWrapper::FontCfgWrapper()
+ : m_pFontSet( nullptr )
+{
+ FcInit();
+}
+
+void FontCfgWrapper::addFontSet( FcSetName eSetName )
+{
+ // Add only acceptable fonts to our config, for future fontconfig use.
+ FcFontSet* pOrig = FcConfigGetFonts( FcConfigGetCurrent(), eSetName );
+ if( !pOrig )
+ return;
+
+ // filter the font sets to remove obsolete faces
+ for( int i = 0; i < pOrig->nfont; ++i )
+ {
+ FcPattern* pPattern = pOrig->fonts[i];
+ // #i115131# ignore non-scalable fonts
+ // Scalable fonts are usually outline fonts, but some bitmaps fonts
+ // (like Noto Color Emoji) are also scalable.
+ FcBool bScalable = FcFalse;
+ FcResult eScalableRes = FcPatternGetBool(pPattern, FC_SCALABLE, 0, &bScalable);
+ if ((eScalableRes != FcResultMatch) || (bScalable == FcFalse))
+ continue;
+
+ // Ignore Type 1 fonts, too.
+ FcChar8* pFormat = nullptr;
+ FcResult eFormatRes = FcPatternGetString(pPattern, FC_FONTFORMAT, 0, &pFormat);
+ if ((eFormatRes == FcResultMatch) && (strcmp(reinterpret_cast<char*>(pFormat), "Type 1") == 0))
+ continue;
+
+ FcPatternReference( pPattern );
+ FcFontSetAdd( m_pFontSet, pPattern );
+ }
+
+ // TODO?: FcFontSetDestroy( pOrig );
+}
+
+namespace
+{
+ int compareFontNames(const FcPattern *a, const FcPattern *b)
+ {
+ FcChar8 *pNameA=nullptr, *pNameB=nullptr;
+
+ bool bHaveA = FcPatternGetString(a, FC_FAMILY, 0, &pNameA) == FcResultMatch;
+ bool bHaveB = FcPatternGetString(b, FC_FAMILY, 0, &pNameB) == FcResultMatch;
+
+ if (bHaveA && bHaveB)
+ return strcmp(reinterpret_cast<const char*>(pNameA), reinterpret_cast<const char*>(pNameB));
+
+ return int(bHaveA) - int(bHaveB);
+ }
+
+ //Sort fonts so that fonts with the same family name are side-by-side, with
+ //those with higher version numbers first
+ class SortFont
+ {
+ public:
+ bool operator()(const FcPattern *a, const FcPattern *b)
+ {
+ int comp = compareFontNames(a, b);
+ if (comp != 0)
+ return comp < 0;
+
+ int nVersionA=0, nVersionB=0;
+
+ bool bHaveA = FcPatternGetInteger(a, FC_FONTVERSION, 0, &nVersionA) == FcResultMatch;
+ bool bHaveB = FcPatternGetInteger(b, FC_FONTVERSION, 0, &nVersionB) == FcResultMatch;
+
+ if (bHaveA && bHaveB)
+ return nVersionA > nVersionB;
+
+ return bHaveA > bHaveB;
+ }
+ };
+
+ //See fdo#30729 for where an old opensymbol installed system-wide can
+ //clobber the new opensymbol installed locally
+
+ //See if this font is a duplicate with equal attributes which has already been
+ //inserted, or if it an older version of an inserted fonts. Depends on FcFontSet
+ //on being sorted with SortFont
+ bool isPreviouslyDuplicateOrObsoleted(FcFontSet const *pFSet, int i)
+ {
+ const FcPattern *a = pFSet->fonts[i];
+
+ FcPattern* pTestPatternA = FcPatternDuplicate(a);
+ FcPatternDel(pTestPatternA, FC_FILE);
+ FcPatternDel(pTestPatternA, FC_CHARSET);
+ FcPatternDel(pTestPatternA, FC_CAPABILITY);
+ FcPatternDel(pTestPatternA, FC_FONTVERSION);
+ FcPatternDel(pTestPatternA, FC_LANG);
+
+ bool bIsDup(false);
+
+ // fdo#66715: loop for case of several font files for same font
+ for (int j = i - 1; 0 <= j && !bIsDup; --j)
+ {
+ const FcPattern *b = pFSet->fonts[j];
+
+ if (compareFontNames(a, b) != 0)
+ break;
+
+ FcPattern* pTestPatternB = FcPatternDuplicate(b);
+ FcPatternDel(pTestPatternB, FC_FILE);
+ FcPatternDel(pTestPatternB, FC_CHARSET);
+ FcPatternDel(pTestPatternB, FC_CAPABILITY);
+ FcPatternDel(pTestPatternB, FC_FONTVERSION);
+ FcPatternDel(pTestPatternB, FC_LANG);
+
+ bIsDup = FcPatternEqual(pTestPatternA, pTestPatternB);
+
+ FcPatternDestroy(pTestPatternB);
+ }
+
+ FcPatternDestroy(pTestPatternA);
+
+ return bIsDup;
+ }
+}
+
+FcFontSet* FontCfgWrapper::getFontSet()
+{
+ if( !m_pFontSet )
+ {
+ m_pFontSet = FcFontSetCreate();
+ bool bRestrictFontSetToApplicationFonts = false;
+#if HAVE_MORE_FONTS
+ bRestrictFontSetToApplicationFonts = getenv("SAL_ABORT_ON_NON_APPLICATION_FONT_USE") != nullptr;
+#endif
+ if (!bRestrictFontSetToApplicationFonts)
+ addFontSet( FcSetSystem );
+ addFontSet( FcSetApplication );
+
+ std::stable_sort(m_pFontSet->fonts,m_pFontSet->fonts+m_pFontSet->nfont,SortFont());
+ }
+
+ return m_pFontSet;
+}
+
+FontCfgWrapper::~FontCfgWrapper()
+{
+ clear();
+ //To-Do: get gtk vclplug smoketest to pass
+ //FcFini();
+}
+
+static FontCfgWrapper* pOneInstance = nullptr;
+
+FontCfgWrapper& FontCfgWrapper::get()
+{
+ if( ! pOneInstance )
+ pOneInstance = new FontCfgWrapper();
+ return *pOneInstance;
+}
+
+void FontCfgWrapper::release()
+{
+ if( pOneInstance )
+ {
+ delete pOneInstance;
+ pOneInstance = nullptr;
+ }
+}
+
+namespace
+{
+ FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag);
+
+ FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag)
+ {
+ FcChar8* candidate = elements.begin()->second;
+ /* FIXME-BCP47: once fontconfig supports language tags this
+ * language-territory stuff needs to be changed! */
+ SAL_INFO_IF( !rLangTag.isIsoLocale(), "vcl.fonts", "localizedsorter::bestname - not an ISO locale");
+ OString sLangMatch(OUStringToOString(rLangTag.getLanguage().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8));
+ OString sFullMatch = sLangMatch +
+ "-" +
+ OUStringToOString(rLangTag.getCountry().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8);
+
+ bool alreadyclosematch = false;
+ bool found_fallback_englishname = false;
+ for (auto const& element : elements)
+ {
+ const char *pLang = reinterpret_cast<const char*>(element.first);
+ if( sFullMatch == pLang)
+ {
+ // both language and country match
+ candidate = element.second;
+ break;
+ }
+ else if( alreadyclosematch )
+ {
+ // current candidate matches lang of lang-TERRITORY
+ // override candidate only if there is a full match
+ continue;
+ }
+ else if( sLangMatch == pLang)
+ {
+ // just the language matches
+ candidate = element.second;
+ alreadyclosematch = true;
+ }
+ else if( found_fallback_englishname )
+ {
+ // already found an english fallback, don't override candidate
+ // unless there is a better language match
+ continue;
+ }
+ else if( rtl_str_compare( pLang, "en") == 0)
+ {
+ // select a fallback candidate of the first english element
+ // name
+ candidate = element.second;
+ found_fallback_englishname = true;
+ }
+ }
+ return candidate;
+ }
+}
+
+//Set up maps to quickly map between a fonts best UI name and all the rest of its names, and vice versa
+void FontCfgWrapper::cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname,
+ const std::vector< lang_and_element > &lang_and_elements)
+{
+ for (auto const& element : lang_and_elements)
+ {
+ const char *candidate = reinterpret_cast<const char*>(element.second);
+ if (rtl_str_compare(candidate, reinterpret_cast<const char*>(bestfontname)) != 0)
+ m_aFontNameToLocalized[OString(candidate)] = OString(reinterpret_cast<const char*>(bestfontname));
+ }
+ if (rtl_str_compare(reinterpret_cast<const char*>(origfontname), reinterpret_cast<const char*>(bestfontname)) != 0)
+ m_aLocalizedToCanonical[OString(reinterpret_cast<const char*>(bestfontname))] = OString(reinterpret_cast<const char*>(origfontname));
+}
+
+FcResult FontCfgWrapper::LocalizedElementFromPattern(FcPattern const * pPattern, FcChar8 **element,
+ const char *elementtype, const char *elementlangtype)
+{ /* e. g.: ^ FC_FAMILY ^ FC_FAMILYLANG */
+ FcChar8 *origelement;
+ FcResult eElementRes = FcPatternGetString( pPattern, elementtype, 0, &origelement );
+ *element = origelement;
+
+ if( eElementRes == FcResultMatch)
+ {
+ FcChar8* elementlang = nullptr;
+ if (FcPatternGetString( pPattern, elementlangtype, 0, &elementlang ) == FcResultMatch)
+ {
+ std::vector< lang_and_element > lang_and_elements;
+ lang_and_elements.emplace_back(elementlang, *element);
+ int k = 1;
+ while (true)
+ {
+ if (FcPatternGetString( pPattern, elementlangtype, k, &elementlang ) != FcResultMatch)
+ break;
+ if (FcPatternGetString( pPattern, elementtype, k, element ) != FcResultMatch)
+ break;
+ lang_and_elements.emplace_back(elementlang, *element);
+ ++k;
+ }
+
+ if (!m_pLanguageTag)
+ m_pLanguageTag.reset(new LanguageTag(SvtSysLocaleOptions().GetRealUILanguageTag()));
+
+ *element = bestname(lang_and_elements, *m_pLanguageTag);
+
+ //if this element is a fontname, map the other names to this best-name
+ if (rtl_str_compare(elementtype, FC_FAMILY) == 0)
+ cacheLocalizedFontNames(origelement, *element, lang_and_elements);
+ }
+ }
+
+ return eElementRes;
+}
+
+void FontCfgWrapper::clear()
+{
+ m_aFontNameToLocalized.clear();
+ m_aLocalizedToCanonical.clear();
+ if( m_pFontSet )
+ {
+ FcFontSetDestroy( m_pFontSet );
+ m_pFontSet = nullptr;
+ }
+ m_pLanguageTag.reset();
+}
+
+/*
+ * PrintFontManager::initFontconfig
+ */
+void PrintFontManager::initFontconfig()
+{
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+ rWrapper.clear();
+}
+
+namespace
+{
+ FontWeight convertWeight(int weight)
+ {
+ // set weight
+ if( weight <= FC_WEIGHT_THIN )
+ return WEIGHT_THIN;
+ else if( weight <= FC_WEIGHT_ULTRALIGHT )
+ return WEIGHT_ULTRALIGHT;
+ else if( weight <= FC_WEIGHT_LIGHT )
+ return WEIGHT_LIGHT;
+ else if( weight <= FC_WEIGHT_BOOK )
+ return WEIGHT_SEMILIGHT;
+ else if( weight <= FC_WEIGHT_NORMAL )
+ return WEIGHT_NORMAL;
+ else if( weight <= FC_WEIGHT_MEDIUM )
+ return WEIGHT_MEDIUM;
+ else if( weight <= FC_WEIGHT_SEMIBOLD )
+ return WEIGHT_SEMIBOLD;
+ else if( weight <= FC_WEIGHT_BOLD )
+ return WEIGHT_BOLD;
+ else if( weight <= FC_WEIGHT_ULTRABOLD )
+ return WEIGHT_ULTRABOLD;
+ return WEIGHT_BLACK;
+ }
+
+ FontItalic convertSlant(int slant)
+ {
+ // set italic
+ if( slant == FC_SLANT_ITALIC )
+ return ITALIC_NORMAL;
+ else if( slant == FC_SLANT_OBLIQUE )
+ return ITALIC_OBLIQUE;
+ return ITALIC_NONE;
+ }
+
+ FontPitch convertSpacing(int spacing)
+ {
+ // set pitch
+ if( spacing == FC_MONO || spacing == FC_CHARCELL )
+ return PITCH_FIXED;
+ return PITCH_VARIABLE;
+ }
+
+ // translation: fontconfig enum -> vcl enum
+ FontWidth convertWidth(int width)
+ {
+ if (width == FC_WIDTH_ULTRACONDENSED)
+ return WIDTH_ULTRA_CONDENSED;
+ else if (width == FC_WIDTH_EXTRACONDENSED)
+ return WIDTH_EXTRA_CONDENSED;
+ else if (width == FC_WIDTH_CONDENSED)
+ return WIDTH_CONDENSED;
+ else if (width == FC_WIDTH_SEMICONDENSED)
+ return WIDTH_SEMI_CONDENSED;
+ else if (width == FC_WIDTH_SEMIEXPANDED)
+ return WIDTH_SEMI_EXPANDED;
+ else if (width == FC_WIDTH_EXPANDED)
+ return WIDTH_EXPANDED;
+ else if (width == FC_WIDTH_EXTRAEXPANDED)
+ return WIDTH_EXTRA_EXPANDED;
+ else if (width == FC_WIDTH_ULTRAEXPANDED)
+ return WIDTH_ULTRA_EXPANDED;
+ return WIDTH_NORMAL;
+ }
+}
+
+//FontConfig doesn't come with a way to remove an element from a FontSet as far
+//as I can see
+static void lcl_FcFontSetRemove(FcFontSet* pFSet, int i)
+{
+ FcPatternDestroy(pFSet->fonts[i]);
+
+ int nTail = pFSet->nfont - (i + 1);
+ --pFSet->nfont;
+ if (!nTail)
+ return;
+ memmove(pFSet->fonts + i, pFSet->fonts + i + 1, nTail*sizeof(FcPattern*));
+}
+
+namespace
+{
+ // for variable fonts, FC_INDEX has been changed such that the lower half is now the
+ // index of the font within the collection, and the upper half has been repurposed
+ // as the index within the variations
+ unsigned int GetCollectionIndex(unsigned int nEntryId)
+ {
+ return nEntryId & 0xFFFF;
+ }
+
+ unsigned int GetVariationIndex(unsigned int nEntryId)
+ {
+ return nEntryId >> 16;
+ }
+}
+
+void PrintFontManager::countFontconfigFonts()
+{
+ int nFonts = 0;
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+
+ FcFontSet* pFSet = rWrapper.getFontSet();
+ const bool bMinimalFontset = utl::ConfigManager::IsFuzzing();
+ if( pFSet )
+ {
+ SAL_INFO("vcl.fonts", "found " << pFSet->nfont << " entries in fontconfig fontset");
+ for( int i = 0; i < pFSet->nfont; i++ )
+ {
+ FcChar8* file = nullptr;
+ FcChar8* family = nullptr;
+ FcChar8* style = nullptr;
+ FcChar8* format = nullptr;
+ int slant = 0;
+ int weight = 0;
+ int width = 0;
+ int spacing = 0;
+ int nEntryId = -1;
+ FcBool scalable = false;
+
+ FcResult eFileRes = FcPatternGetString(pFSet->fonts[i], FC_FILE, 0, &file);
+ FcResult eFamilyRes = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &family, FC_FAMILY, FC_FAMILYLANG );
+ if (bMinimalFontset && strncmp(reinterpret_cast<char*>(family), "Liberation", strlen("Liberation")))
+ continue;
+ FcResult eStyleRes = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &style, FC_STYLE, FC_STYLELANG );
+ FcResult eSlantRes = FcPatternGetInteger(pFSet->fonts[i], FC_SLANT, 0, &slant);
+ FcResult eWeightRes = FcPatternGetInteger(pFSet->fonts[i], FC_WEIGHT, 0, &weight);
+ FcResult eWidthRes = FcPatternGetInteger(pFSet->fonts[i], FC_WIDTH, 0, &width);
+ FcResult eSpacRes = FcPatternGetInteger(pFSet->fonts[i], FC_SPACING, 0, &spacing);
+ FcResult eScalableRes = FcPatternGetBool(pFSet->fonts[i], FC_SCALABLE, 0, &scalable);
+ FcResult eIndexRes = FcPatternGetInteger(pFSet->fonts[i], FC_INDEX, 0, &nEntryId);
+ FcResult eFormatRes = FcPatternGetString(pFSet->fonts[i], FC_FONTFORMAT, 0, &format);
+
+ if( eFileRes != FcResultMatch || eFamilyRes != FcResultMatch || eScalableRes != FcResultMatch )
+ continue;
+
+ SAL_INFO(
+ "vcl.fonts.detail",
+ "found font \"" << family << "\" in file " << file << ", weight = "
+ << (eWeightRes == FcResultMatch ? weight : -1) << ", slant = "
+ << (eSpacRes == FcResultMatch ? slant : -1) << ", style = \""
+ << (eStyleRes == FcResultMatch ? reinterpret_cast<const char*>(style) : "<nil>")
+ << "\", width = " << (eWeightRes == FcResultMatch ? width : -1) << ", spacing = "
+ << (eSpacRes == FcResultMatch ? spacing : -1) << ", scalable = "
+ << (eScalableRes == FcResultMatch ? scalable : -1) << ", format "
+ << (eFormatRes == FcResultMatch
+ ? reinterpret_cast<const char*>(format) : "<unknown>"));
+
+// OSL_ASSERT(eScalableRes != FcResultMatch || scalable);
+
+ // only scalable fonts are usable to psprint anyway
+ if( eScalableRes == FcResultMatch && ! scalable )
+ continue;
+
+ if (isPreviouslyDuplicateOrObsoleted(pFSet, i))
+ {
+ SAL_INFO("vcl.fonts.detail", "Ditching " << file << " as duplicate/obsolete");
+ continue;
+ }
+
+ // see if this font is already cached
+ // update attributes
+ OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
+ splitPath( aOrgPath, aDir, aBase );
+
+ int nDirID = getDirectoryAtom( aDir );
+ SAL_INFO("vcl.fonts.detail", "file " << aBase << " not cached");
+ // not known, analyze font file to get attributes
+ // not described by fontconfig (e.g. alias names, PSName)
+ if (eFormatRes != FcResultMatch)
+ format = nullptr;
+ std::vector<PrintFont> aFonts = analyzeFontFile( nDirID, aBase, reinterpret_cast<char*>(format) );
+ if(aFonts.empty())
+ {
+ SAL_INFO(
+ "vcl.fonts", "Warning: file \"" << aOrgPath << "\" is unusable to psprint");
+ //remove font, reuse index
+ //we want to remove unusable fonts here, in case there is a usable font
+ //which duplicates the properties of the unusable one
+
+ //not removing the unusable font will risk the usable font being rejected
+ //as a duplicate by isPreviouslyDuplicateOrObsoleted
+ lcl_FcFontSetRemove(pFSet, i--);
+ continue;
+ }
+
+ std::optional<PrintFont> xUpdate;
+
+ if (aFonts.size() == 1) // one font
+ xUpdate = aFonts.front();
+ else // more than one font
+ {
+ // a collection entry, get the correct index
+ if( eIndexRes == FcResultMatch && nEntryId != -1 )
+ {
+ int nCollectionEntry = GetCollectionIndex(nEntryId);
+ for (const auto & font : aFonts)
+ {
+ if( font.m_nCollectionEntry == nCollectionEntry )
+ {
+ xUpdate = font;
+ break;
+ }
+ }
+ }
+
+ if (xUpdate)
+ {
+ // update collection entry
+ // additional entries will be created in the cache
+ // if this is a new index (that is if the loop above
+ // ran to the end of the list)
+ xUpdate->m_nCollectionEntry = GetCollectionIndex(nEntryId);
+ }
+ else
+ {
+ SAL_INFO(
+ "vcl.fonts",
+ "multiple fonts for file, but no index in fontconfig pattern ! (index res ="
+ << eIndexRes << " collection entry = " << nEntryId
+ << "; file will not be used");
+ // we have found more than one font in this file
+ // but fontconfig will not tell us which index is meant
+ // -> something is in disorder, do not use this font
+ }
+ }
+
+ if (xUpdate)
+ {
+ // set family name
+ if( eWeightRes == FcResultMatch )
+ xUpdate->m_eWeight = convertWeight(weight);
+ if( eWidthRes == FcResultMatch )
+ xUpdate->m_eWidth = convertWidth(width);
+ if( eSpacRes == FcResultMatch )
+ xUpdate->m_ePitch = convertSpacing(spacing);
+ if( eSlantRes == FcResultMatch )
+ xUpdate->m_eItalic = convertSlant(slant);
+ if( eStyleRes == FcResultMatch )
+ xUpdate->m_aStyleName = OStringToOUString( std::string_view( reinterpret_cast<char*>(style) ), RTL_TEXTENCODING_UTF8 );
+ if( eIndexRes == FcResultMatch )
+ xUpdate->m_nVariationEntry = GetVariationIndex(nEntryId);
+
+ // sort into known fonts
+ fontID aFont = m_nNextFontID++;
+ m_aFonts.emplace( aFont, *xUpdate );
+ m_aFontFileToFontID[ aBase ].insert( aFont );
+ nFonts++;
+ SAL_INFO("vcl.fonts.detail", "inserted font " << family << " as fontID " << aFont);
+ }
+ }
+ }
+
+ // how does one get rid of the config ?
+ SAL_INFO("vcl.fonts", "inserted " << nFonts << " fonts from fontconfig");
+}
+
+void PrintFontManager::deinitFontconfig()
+{
+ FontCfgWrapper::release();
+}
+
+void PrintFontManager::addFontconfigDir( const OString& rDirName )
+{
+ const char* pDirName = rDirName.getStr();
+ bool bDirOk = (FcConfigAppFontAddDir(FcConfigGetCurrent(), reinterpret_cast<FcChar8 const *>(pDirName) ) == FcTrue);
+
+ SAL_INFO("vcl.fonts", "FcConfigAppFontAddDir( \"" << pDirName << "\") => " << bDirOk);
+
+ if( !bDirOk )
+ return;
+
+ // load dir-specific fc-config file too if available
+ const OString aConfFileName = rDirName + "/fc_local.conf";
+ FILE* pCfgFile = fopen( aConfFileName.getStr(), "rb" );
+ if( pCfgFile )
+ {
+ fclose( pCfgFile);
+ bool bCfgOk = FcConfigParseAndLoad(FcConfigGetCurrent(),
+ reinterpret_cast<FcChar8 const *>(aConfFileName.getStr()), FcTrue);
+
+ SAL_INFO_IF(!bCfgOk,
+ "vcl.fonts", "FcConfigParseAndLoad( \""
+ << aConfFileName << "\") => " << bCfgOk);
+ } else {
+ SAL_INFO("vcl.fonts", "cannot open " << aConfFileName);
+ }
+}
+
+static void addtopattern(FcPattern *pPattern,
+ FontItalic eItalic, FontWeight eWeight, FontWidth eWidth, FontPitch ePitch)
+{
+ if( eItalic != ITALIC_DONTKNOW )
+ {
+ int nSlant = FC_SLANT_ROMAN;
+ switch( eItalic )
+ {
+ case ITALIC_NORMAL:
+ nSlant = FC_SLANT_ITALIC;
+ break;
+ case ITALIC_OBLIQUE:
+ nSlant = FC_SLANT_OBLIQUE;
+ break;
+ default:
+ break;
+ }
+ FcPatternAddInteger(pPattern, FC_SLANT, nSlant);
+ }
+ if( eWeight != WEIGHT_DONTKNOW )
+ {
+ int nWeight = FC_WEIGHT_NORMAL;
+ switch( eWeight )
+ {
+ case WEIGHT_THIN: nWeight = FC_WEIGHT_THIN;break;
+ case WEIGHT_ULTRALIGHT: nWeight = FC_WEIGHT_ULTRALIGHT;break;
+ case WEIGHT_LIGHT: nWeight = FC_WEIGHT_LIGHT;break;
+ case WEIGHT_SEMILIGHT: nWeight = FC_WEIGHT_BOOK;break;
+ case WEIGHT_NORMAL: nWeight = FC_WEIGHT_NORMAL;break;
+ case WEIGHT_MEDIUM: nWeight = FC_WEIGHT_MEDIUM;break;
+ case WEIGHT_SEMIBOLD: nWeight = FC_WEIGHT_SEMIBOLD;break;
+ case WEIGHT_BOLD: nWeight = FC_WEIGHT_BOLD;break;
+ case WEIGHT_ULTRABOLD: nWeight = FC_WEIGHT_ULTRABOLD;break;
+ case WEIGHT_BLACK: nWeight = FC_WEIGHT_BLACK;break;
+ default:
+ break;
+ }
+ FcPatternAddInteger(pPattern, FC_WEIGHT, nWeight);
+ }
+ if( eWidth != WIDTH_DONTKNOW )
+ {
+ int nWidth = FC_WIDTH_NORMAL;
+ switch( eWidth )
+ {
+ case WIDTH_ULTRA_CONDENSED: nWidth = FC_WIDTH_ULTRACONDENSED;break;
+ case WIDTH_EXTRA_CONDENSED: nWidth = FC_WIDTH_EXTRACONDENSED;break;
+ case WIDTH_CONDENSED: nWidth = FC_WIDTH_CONDENSED;break;
+ case WIDTH_SEMI_CONDENSED: nWidth = FC_WIDTH_SEMICONDENSED;break;
+ case WIDTH_NORMAL: nWidth = FC_WIDTH_NORMAL;break;
+ case WIDTH_SEMI_EXPANDED: nWidth = FC_WIDTH_SEMIEXPANDED;break;
+ case WIDTH_EXPANDED: nWidth = FC_WIDTH_EXPANDED;break;
+ case WIDTH_EXTRA_EXPANDED: nWidth = FC_WIDTH_EXTRAEXPANDED;break;
+ case WIDTH_ULTRA_EXPANDED: nWidth = FC_WIDTH_ULTRAEXPANDED;break;
+ default:
+ break;
+ }
+ FcPatternAddInteger(pPattern, FC_WIDTH, nWidth);
+ }
+ if( ePitch == PITCH_DONTKNOW )
+ return;
+
+ int nSpacing = FC_PROPORTIONAL;
+ switch( ePitch )
+ {
+ case PITCH_FIXED: nSpacing = FC_MONO;break;
+ case PITCH_VARIABLE: nSpacing = FC_PROPORTIONAL;break;
+ default:
+ break;
+ }
+ FcPatternAddInteger(pPattern, FC_SPACING, nSpacing);
+ if (nSpacing == FC_MONO)
+ FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>("monospace"));
+}
+
+namespace
+{
+ //Someday fontconfig will hopefully use bcp47, see fdo#19869
+ //In the meantime try something that will fit to workaround fdo#35118
+ OString mapToFontConfigLangTag(const LanguageTag &rLangTag)
+ {
+#if defined(FC_VERSION) && (FC_VERSION >= 20492)
+ std::shared_ptr<FcStrSet> xLangSet(FcGetLangs(), FcStrSetDestroy);
+ OString sLangAttrib;
+
+ sLangAttrib = OUStringToOString(rLangTag.getBcp47(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
+ if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
+ {
+ return sLangAttrib;
+ }
+
+ sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
+ if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
+ {
+ return sLangAttrib;
+ }
+
+ OString sLang = OUStringToOString(rLangTag.getLanguage(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
+ OString sRegion = OUStringToOString(rLangTag.getCountry(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
+
+ if (!sRegion.isEmpty())
+ {
+ sLangAttrib = sLang + "-" + sRegion;
+ if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
+ {
+ return sLangAttrib;
+ }
+ }
+
+ if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLang.getStr())))
+ {
+ return sLang;
+ }
+
+ return OString();
+#else
+ OString sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
+ if (sLangAttrib.equalsIgnoreAsciiCase("pa-in"))
+ sLangAttrib = "pa";
+ return sLangAttrib;
+#endif
+ }
+
+ bool isEmoji(sal_uInt32 nCurrentChar)
+ {
+#if U_ICU_VERSION_MAJOR_NUM >= 57
+ return u_hasBinaryProperty(nCurrentChar, UCHAR_EMOJI);
+#else
+ return false;
+#endif
+ }
+
+ //returns true if the given code-point couldn't possibly be in rLangTag.
+ bool isImpossibleCodePointForLang(const LanguageTag &rLangTag, sal_uInt32 currentChar)
+ {
+ //a non-default script is set, lets believe it
+ if (rLangTag.hasScript())
+ return false;
+
+ int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT);
+ UScriptCode eScript = static_cast<UScriptCode>(script);
+ bool bIsImpossible = false;
+ OUString sLang = rLangTag.getLanguage();
+ switch (eScript)
+ {
+ //http://en.wiktionary.org/wiki/Category:Oriya_script_languages
+ case USCRIPT_ORIYA:
+ bIsImpossible =
+ sLang != "or" &&
+ sLang != "kxv";
+ break;
+ //http://en.wiktionary.org/wiki/Category:Telugu_script_languages
+ case USCRIPT_TELUGU:
+ bIsImpossible =
+ sLang != "te" &&
+ sLang != "gon" &&
+ sLang != "kfc";
+ break;
+ //http://en.wiktionary.org/wiki/Category:Bengali_script_languages
+ case USCRIPT_BENGALI:
+ bIsImpossible =
+ sLang != "bn" &&
+ sLang != "as" &&
+ sLang != "bpy" &&
+ sLang != "ctg" &&
+ sLang != "sa";
+ break;
+ default:
+ break;
+ }
+ SAL_WARN_IF(bIsImpossible, "vcl.fonts", "In glyph fallback throwing away the language property of "
+ << sLang << " because the detected script for '0x"
+ << OUString::number(currentChar, 16)
+ << "' is " << uscript_getName(eScript)
+ << " and that language doesn't make sense. Autodetecting instead.");
+ return bIsImpossible;
+ }
+
+ OUString getExemplarLangTagForCodePoint(sal_uInt32 currentChar)
+ {
+ if (isEmoji(currentChar))
+ return "und-zsye";
+ int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT);
+ UScriptCode eScript = static_cast<UScriptCode>(script);
+ OStringBuffer aBuf(unicode::getExemplarLanguageForUScriptCode(eScript));
+ if (const char* pScriptCode = uscript_getShortName(eScript))
+ aBuf.append('-').append(pScriptCode);
+ return OStringToOUString(aBuf, RTL_TEXTENCODING_UTF8);
+ }
+}
+
+IMPL_LINK_NOARG(PrintFontManager, autoInstallFontLangSupport, Timer *, void)
+{
+ try
+ {
+ using namespace org::freedesktop::PackageKit;
+ css::uno::Reference<XSyncDbusSessionHelper> xSyncDbusSessionHelper(SyncDbusSessionHelper::create(comphelper::getProcessComponentContext()));
+ xSyncDbusSessionHelper->InstallFontconfigResources(comphelper::containerToSequence(m_aCurrentRequests), "hide-finished");
+ }
+ catch (const css::uno::Exception&)
+ {
+ TOOLS_INFO_EXCEPTION("vcl.fonts", "InstallFontconfigResources problem");
+ // Disable this method from now on. It's simply not available on some systems
+ // and leads to an error dialog being shown each time this is called tdf#104883
+ std::shared_ptr<comphelper::ConfigurationChanges> batch( comphelper::ConfigurationChanges::create() );
+ officecfg::Office::Common::PackageKit::EnableFontInstallation::set(false, batch);
+ batch->commit();
+ }
+
+ m_aCurrentRequests.clear();
+}
+
+void PrintFontManager::Substitute(vcl::font::FontSelectPattern &rPattern, OUString& rMissingCodes)
+{
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+
+ // build pattern argument for fontconfig query
+ FcPattern* pPattern = FcPatternCreate();
+
+ // Prefer scalable fonts
+ FcPatternAddBool(pPattern, FC_SCALABLE, FcTrue);
+
+ const OString aTargetName = OUStringToOString( rPattern.maTargetName, RTL_TEXTENCODING_UTF8 );
+ const FcChar8* pTargetNameUtf8 = reinterpret_cast<FcChar8 const *>(aTargetName.getStr());
+ FcPatternAddString(pPattern, FC_FAMILY, pTargetNameUtf8);
+
+ LanguageTag aLangTag(rPattern.meLanguage);
+ OString aLangAttrib = mapToFontConfigLangTag(aLangTag);
+
+ bool bMissingJustBullet = false;
+
+ // Add required Unicode characters, if any
+ if ( !rMissingCodes.isEmpty() )
+ {
+ FcCharSet *codePoints = FcCharSetCreate();
+ bMissingJustBullet = rMissingCodes.getLength() == 1 && rMissingCodes[0] == 0xb7;
+ for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); )
+ {
+ // also handle unicode surrogates
+ const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex );
+ FcCharSetAddChar( codePoints, nCode );
+ //if the codepoint is impossible for this lang tag, then clear it
+ //and autodetect something useful
+ if (!aLangAttrib.isEmpty() && (isImpossibleCodePointForLang(aLangTag, nCode) || isEmoji(nCode)))
+ aLangAttrib.clear();
+ //#i105784#/rhbz#527719 improve selection of fallback font
+ if (aLangAttrib.isEmpty())
+ {
+ aLangTag.reset(getExemplarLangTagForCodePoint(nCode));
+ aLangAttrib = mapToFontConfigLangTag(aLangTag);
+ }
+ }
+ FcPatternAddCharSet(pPattern, FC_CHARSET, codePoints);
+ FcCharSetDestroy(codePoints);
+ }
+
+ if (!aLangAttrib.isEmpty())
+ FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr()));
+
+ addtopattern(pPattern, rPattern.GetItalic(), rPattern.GetWeight(),
+ rPattern.GetWidthType(), rPattern.GetPitch());
+
+ // query fontconfig for a substitute
+ FcConfigSubstitute(FcConfigGetCurrent(), pPattern, FcMatchPattern);
+ FcDefaultSubstitute(pPattern);
+
+ // process the result of the fontconfig query
+ FcResult eResult = FcResultNoMatch;
+ FcFontSet* pFontSet = rWrapper.getFontSet();
+ FcPattern* pResult = FcFontSetMatch(FcConfigGetCurrent(), &pFontSet, 1, pPattern, &eResult);
+ FcPatternDestroy( pPattern );
+
+ FcFontSet* pSet = nullptr;
+ if( pResult )
+ {
+ pSet = FcFontSetCreate();
+ // info: destroying the pSet destroys pResult implicitly
+ // since pResult was "added" to pSet
+ FcFontSetAdd( pSet, pResult );
+ }
+
+ if( pSet )
+ {
+ if( pSet->nfont > 0 )
+ {
+ bool bRet = false;
+
+ //extract the closest match
+ FcChar8* file = nullptr;
+ FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file);
+ int nEntryId = 0;
+ FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nEntryId);
+ if (eIndexRes != FcResultMatch)
+ nEntryId = 0;
+ if( eFileRes == FcResultMatch )
+ {
+ OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
+ splitPath( aOrgPath, aDir, aBase );
+ int nDirID = getDirectoryAtom( aDir );
+ fontID aFont = findFontFileID(nDirID, aBase, GetCollectionIndex(nEntryId), GetVariationIndex(nEntryId));
+ if( aFont > 0 )
+ {
+ FastPrintFontInfo aInfo;
+ bRet = getFontFastInfo( aFont, aInfo );
+ rPattern.maSearchName = aInfo.m_aFamilyName;
+ }
+ }
+
+ SAL_WARN_IF(!bRet, "vcl.fonts", "no FC_FILE found, falling back to name search");
+
+ if (!bRet)
+ {
+ FcChar8* family = nullptr;
+ FcResult eFamilyRes = FcPatternGetString( pSet->fonts[0], FC_FAMILY, 0, &family );
+
+ // get the family name
+ if( eFamilyRes == FcResultMatch )
+ {
+ OString sFamily(reinterpret_cast<char*>(family));
+ std::unordered_map< OString, OString >::const_iterator aI =
+ rWrapper.m_aFontNameToLocalized.find(sFamily);
+ if (aI != rWrapper.m_aFontNameToLocalized.end())
+ sFamily = aI->second;
+ rPattern.maSearchName = OStringToOUString( sFamily, RTL_TEXTENCODING_UTF8 );
+ bRet = true;
+ }
+ }
+
+ if (bRet)
+ {
+ int val = 0;
+ if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WEIGHT, 0, &val))
+ rPattern.SetWeight( convertWeight(val) );
+ if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SLANT, 0, &val))
+ rPattern.SetItalic( convertSlant(val) );
+ if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SPACING, 0, &val))
+ rPattern.SetPitch ( convertSpacing(val) );
+ if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WIDTH, 0, &val))
+ rPattern.SetWidthType ( convertWidth(val) );
+ FcBool bEmbolden;
+ if (FcResultMatch == FcPatternGetBool(pSet->fonts[0], FC_EMBOLDEN, 0, &bEmbolden))
+ rPattern.mbEmbolden = bEmbolden;
+ FcMatrix *pMatrix = nullptr;
+ if (FcResultMatch == FcPatternGetMatrix(pSet->fonts[0], FC_MATRIX, 0, &pMatrix))
+ {
+ rPattern.maItalicMatrix.xx = pMatrix->xx;
+ rPattern.maItalicMatrix.xy = pMatrix->xy;
+ rPattern.maItalicMatrix.yx = pMatrix->yx;
+ rPattern.maItalicMatrix.yy = pMatrix->yy;
+ }
+ }
+
+ // update rMissingCodes by removing resolved code points
+ if( !rMissingCodes.isEmpty() )
+ {
+ std::unique_ptr<sal_uInt32[]> const pRemainingCodes(new sal_uInt32[rMissingCodes.getLength()]);
+ int nRemainingLen = 0;
+ FcCharSet* codePoints;
+ if (!FcPatternGetCharSet(pSet->fonts[0], FC_CHARSET, 0, &codePoints))
+ {
+ for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); )
+ {
+ // also handle surrogates
+ const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex );
+ if (FcCharSetHasChar(codePoints, nCode) != FcTrue)
+ pRemainingCodes[ nRemainingLen++ ] = nCode;
+ }
+ }
+ OUString sStillMissing(pRemainingCodes.get(), nRemainingLen);
+ if (!Application::IsHeadlessModeEnabled() && officecfg::Office::Common::PackageKit::EnableFontInstallation::get())
+ {
+ if (sStillMissing == rMissingCodes) //replaced nothing
+ {
+ //It'd be better if we could ask packagekit using the
+ //missing codepoints or some such rather than using
+ //"language" as a proxy to how fontconfig considers
+ //scripts to default to a given language.
+ for (sal_Int32 i = 0; i < nRemainingLen; ++i)
+ {
+ LanguageTag aOurTag(getExemplarLangTagForCodePoint(pRemainingCodes[i]));
+ OString sTag = OUStringToOString(aOurTag.getBcp47(), RTL_TEXTENCODING_UTF8);
+ if (!m_aPreviousLangSupportRequests.insert(sTag).second)
+ continue;
+ sTag = mapToFontConfigLangTag(aOurTag);
+ if (!sTag.isEmpty() && m_aPreviousLangSupportRequests.find(sTag) == m_aPreviousLangSupportRequests.end())
+ {
+ OString sReq = OString::Concat(":lang=") + sTag;
+ m_aCurrentRequests.push_back(OUString::fromUtf8(sReq));
+ m_aPreviousLangSupportRequests.insert(sTag);
+ }
+ }
+ }
+ if (!m_aCurrentRequests.empty())
+ m_aFontInstallerTimer.Start();
+ }
+ rMissingCodes = sStillMissing;
+ }
+ }
+
+ FcFontSetDestroy( pSet );
+ }
+
+ SAL_INFO("vcl.fonts", "PrintFontManager::Substitute: replacing missing font: '"
+ << rPattern.maTargetName << "' with '" << rPattern.maSearchName
+ << "'");
+
+ static bool bAbortOnFontSubstitute = getenv("SAL_ABORT_ON_NON_APPLICATION_FONT_USE") != nullptr;
+ if (bAbortOnFontSubstitute && rPattern.maTargetName != rPattern.maSearchName)
+ {
+ if (bMissingJustBullet)
+ {
+ assert(rPattern.maTargetName == "Amiri Quran" || rPattern.maTargetName == "David CLM" ||
+ rPattern.maTargetName == "EmojiOne Color" || rPattern.maTargetName == "Frank Ruehl CLM" ||
+ rPattern.maTargetName == "KacstBook" || rPattern.maTargetName == "KacstOffice");
+ // These fonts exist in "more_fonts", but have no U+00B7 MIDDLE DOT
+ // so will always glyph fallback on measuring mnBulletOffset in
+ // ImplFontMetricData::ImplInitTextLineSize
+ return;
+ }
+ if (rPattern.maTargetName == "Linux Libertine G" && rPattern.maSearchName == "Linux Libertine O")
+ return;
+ SAL_WARN("vcl.fonts", "PrintFontManager::Substitute: missing font: '" << rPattern.maTargetName <<
+ "' try: " << rPattern.maSearchName << " instead");
+ std::abort();
+ }
+}
+
+FontConfigFontOptions::~FontConfigFontOptions()
+{
+ FcPatternDestroy(mpPattern);
+}
+
+FcPattern *FontConfigFontOptions::GetPattern() const
+{
+ return mpPattern;
+}
+
+void FontConfigFontOptions::SyncPattern(const OString& rFileName, sal_uInt32 nIndex, sal_uInt32 nVariation, bool bEmbolden)
+{
+ FcPatternDel(mpPattern, FC_FILE);
+ FcPatternAddString(mpPattern, FC_FILE, reinterpret_cast<FcChar8 const *>(rFileName.getStr()));
+ FcPatternDel(mpPattern, FC_INDEX);
+ sal_uInt32 nFcIndex = (nVariation << 16) | nIndex;
+ FcPatternAddInteger(mpPattern, FC_INDEX, nFcIndex);
+ FcPatternDel(mpPattern, FC_EMBOLDEN);
+ FcPatternAddBool(mpPattern, FC_EMBOLDEN, bEmbolden ? FcTrue : FcFalse);
+}
+
+std::unique_ptr<FontConfigFontOptions> PrintFontManager::getFontOptions(const FontAttributes& rInfo, int nSize)
+{
+ FontOptionsKey aKey{ rInfo.GetFamilyName(), nSize, rInfo.GetItalic(),
+ rInfo.GetWeight(), rInfo.GetWidthType(), rInfo.GetPitch() };
+
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+
+ std::unique_ptr<FontConfigFontOptions> pOptions = rWrapper.m_aCachedFontOptions.lookup(aKey);
+ if (pOptions)
+ return pOptions;
+
+ FcConfig* pConfig = FcConfigGetCurrent();
+ FcPattern* pPattern = FcPatternCreate();
+
+ OString sFamily = OUStringToOString(aKey.m_sFamilyName, RTL_TEXTENCODING_UTF8);
+
+ std::unordered_map< OString, OString >::const_iterator aI = rWrapper.m_aLocalizedToCanonical.find(sFamily);
+ if (aI != rWrapper.m_aLocalizedToCanonical.end())
+ sFamily = aI->second;
+ if( !sFamily.isEmpty() )
+ FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(sFamily.getStr()));
+
+ addtopattern(pPattern, aKey.m_eItalic, aKey.m_eWeight, aKey.m_eWidth, aKey.m_ePitch);
+ FcPatternAddDouble(pPattern, FC_PIXEL_SIZE, nSize);
+
+ FcConfigSubstitute(pConfig, pPattern, FcMatchPattern);
+ FontConfigFontOptions::cairo_font_options_substitute(pPattern);
+ FcDefaultSubstitute(pPattern);
+
+ FcResult eResult = FcResultNoMatch;
+ FcFontSet* pFontSet = rWrapper.getFontSet();
+ if (FcPattern* pResult = FcFontSetMatch(pConfig, &pFontSet, 1, pPattern, &eResult))
+ {
+ rWrapper.m_aCachedFontOptions.cache(aKey, pResult);
+ pOptions.reset(new FontConfigFontOptions(pResult));
+ }
+
+ // cleanup
+ FcPatternDestroy( pPattern );
+
+ return pOptions;
+}
+
+
+void PrintFontManager::matchFont( FastPrintFontInfo& rInfo, const css::lang::Locale& rLocale )
+{
+ FontCfgWrapper& rWrapper = FontCfgWrapper::get();
+
+ FcConfig* pConfig = FcConfigGetCurrent();
+ FcPattern* pPattern = FcPatternCreate();
+
+ // populate pattern with font characteristics
+ const LanguageTag aLangTag(rLocale);
+ const OString aLangAttrib = mapToFontConfigLangTag(aLangTag);
+ if (!aLangAttrib.isEmpty())
+ FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr()));
+
+ OString aFamily = OUStringToOString( rInfo.m_aFamilyName, RTL_TEXTENCODING_UTF8 );
+ if( !aFamily.isEmpty() )
+ FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(aFamily.getStr()));
+
+ addtopattern(pPattern, rInfo.m_eItalic, rInfo.m_eWeight, rInfo.m_eWidth, rInfo.m_ePitch);
+
+ FcConfigSubstitute(pConfig, pPattern, FcMatchPattern);
+ FcDefaultSubstitute(pPattern);
+ FcResult eResult = FcResultNoMatch;
+ FcFontSet *pFontSet = rWrapper.getFontSet();
+ FcPattern* pResult = FcFontSetMatch(pConfig, &pFontSet, 1, pPattern, &eResult);
+ if( pResult )
+ {
+ FcFontSet* pSet = FcFontSetCreate();
+ FcFontSetAdd( pSet, pResult );
+ if( pSet->nfont > 0 )
+ {
+ //extract the closest match
+ FcChar8* file = nullptr;
+ FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file);
+ int nEntryId = 0;
+ FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nEntryId);
+ if (eIndexRes != FcResultMatch)
+ nEntryId = 0;
+ if( eFileRes == FcResultMatch )
+ {
+ OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
+ splitPath( aOrgPath, aDir, aBase );
+ int nDirID = getDirectoryAtom( aDir );
+ fontID aFont = findFontFileID(nDirID, aBase,
+ GetCollectionIndex(nEntryId),
+ GetVariationIndex(nEntryId));
+ if( aFont > 0 )
+ getFontFastInfo( aFont, rInfo );
+ }
+ }
+ // info: destroying the pSet destroys pResult implicitly
+ // since pResult was "added" to pSet
+ FcFontSetDestroy( pSet );
+ }
+
+ // cleanup
+ FcPatternDestroy( pPattern );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/fontmanager/fontmanager.cxx b/vcl/unx/generic/fontmanager/fontmanager.cxx
new file mode 100644
index 000000000..46e8ae162
--- /dev/null
+++ b/vcl/unx/generic/fontmanager/fontmanager.cxx
@@ -0,0 +1,1147 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <unistd.h>
+#include <osl/thread.h>
+
+#include <unx/fontmanager.hxx>
+#include <fontsubset.hxx>
+#include <impfontcharmap.hxx>
+#include <unx/gendata.hxx>
+#include <unx/helper.hxx>
+#include <vcl/fontcharmap.hxx>
+
+#include <tools/urlobj.hxx>
+
+#include <osl/file.hxx>
+
+#include <rtl/ustrbuf.hxx>
+#include <rtl/strbuf.hxx>
+
+#include <sal/macros.h>
+#include <sal/log.hxx>
+
+#include <i18nlangtag/applelangid.hxx>
+
+#include <sft.hxx>
+
+#if OSL_DEBUG_LEVEL > 1
+#include <sys/times.h>
+#include <stdio.h>
+#endif
+
+#include <algorithm>
+#include <set>
+
+#ifdef CALLGRIND_COMPILE
+#include <valgrind/callgrind.h>
+#endif
+
+#include <com/sun/star/beans/XMaterialHolder.hpp>
+
+using namespace vcl;
+using namespace utl;
+using namespace psp;
+using namespace osl;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::lang;
+
+/*
+ * static helpers
+ */
+
+static sal_uInt16 getUInt16BE( const sal_uInt8*& pBuffer )
+{
+ sal_uInt16 nRet = static_cast<sal_uInt16>(pBuffer[1]) |
+ (static_cast<sal_uInt16>(pBuffer[0]) << 8);
+ pBuffer+=2;
+ return nRet;
+}
+
+/*
+ * PrintFont implementations
+ */
+PrintFontManager::PrintFont::PrintFont()
+: m_eFamilyStyle(FAMILY_DONTKNOW)
+, m_eItalic(ITALIC_DONTKNOW)
+, m_eWidth(WIDTH_DONTKNOW)
+, m_eWeight(WEIGHT_DONTKNOW)
+, m_ePitch(PITCH_DONTKNOW)
+, m_aEncoding(RTL_TEXTENCODING_DONTKNOW)
+, m_nAscend(0)
+, m_nDescend(0)
+, m_nLeading(0)
+, m_nXMin(0)
+, m_nYMin(0)
+, m_nXMax(0)
+, m_nYMax(0)
+, m_nDirectory(0)
+, m_nCollectionEntry(0)
+, m_nVariationEntry(0)
+{
+}
+
+/*
+ * one instance only
+ */
+PrintFontManager& PrintFontManager::get()
+{
+ GenericUnixSalData* const pSalData(GetGenericUnixSalData());
+ assert(pSalData);
+ return *pSalData->GetPrintFontManager();
+}
+
+/*
+ * the PrintFontManager
+ */
+
+PrintFontManager::PrintFontManager()
+ : m_nNextFontID( 1 )
+ , m_nNextDirAtom( 1 )
+ , m_aFontInstallerTimer("PrintFontManager m_aFontInstallerTimer")
+{
+ m_aFontInstallerTimer.SetInvokeHandler(LINK(this, PrintFontManager, autoInstallFontLangSupport));
+ m_aFontInstallerTimer.SetTimeout(5000);
+}
+
+PrintFontManager::~PrintFontManager()
+{
+ m_aFontInstallerTimer.Stop();
+ deinitFontconfig();
+}
+
+OString PrintFontManager::getDirectory( int nAtom ) const
+{
+ std::unordered_map< int, OString >::const_iterator it( m_aAtomToDir.find( nAtom ) );
+ return it != m_aAtomToDir.end() ? it->second : OString();
+}
+
+int PrintFontManager::getDirectoryAtom( const OString& rDirectory )
+{
+ int nAtom = 0;
+ std::unordered_map< OString, int >::const_iterator it
+ ( m_aDirToAtom.find( rDirectory ) );
+ if( it != m_aDirToAtom.end() )
+ nAtom = it->second;
+ else
+ {
+ nAtom = m_nNextDirAtom++;
+ m_aDirToAtom[ rDirectory ] = nAtom;
+ m_aAtomToDir[ nAtom ] = rDirectory;
+ }
+ return nAtom;
+}
+
+std::vector<fontID> PrintFontManager::addFontFile( std::u16string_view rFileUrl )
+{
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ INetURLObject aPath( rFileUrl );
+ OString aName(OUStringToOString(aPath.GetLastName(INetURLObject::DecodeMechanism::WithCharset, aEncoding), aEncoding));
+ OString aDir( OUStringToOString(
+ INetURLObject::decode( aPath.GetPath(), INetURLObject::DecodeMechanism::WithCharset, aEncoding ), aEncoding ) );
+
+ int nDirID = getDirectoryAtom( aDir );
+ std::vector<fontID> aFontIds = findFontFileIDs( nDirID, aName );
+ if( aFontIds.empty() )
+ {
+ std::vector<PrintFont> aNewFonts = analyzeFontFile(nDirID, aName);
+ for (auto & font : aNewFonts)
+ {
+ fontID nFontId = m_nNextFontID++;
+ m_aFonts[nFontId] = std::move(font);
+ m_aFontFileToFontID[ aName ].insert( nFontId );
+ aFontIds.push_back(nFontId);
+ }
+ }
+ return aFontIds;
+}
+
+std::vector<PrintFontManager::PrintFont> PrintFontManager::analyzeFontFile( int nDirID, const OString& rFontFile, const char *pFormat ) const
+{
+ std::vector<PrintFontManager::PrintFont> aNewFonts;
+
+ OString aDir( getDirectory( nDirID ) );
+
+ OString aFullPath = aDir + "/" + rFontFile;
+
+ // #i1872# reject unreadable files
+ if( access( aFullPath.getStr(), R_OK ) )
+ return aNewFonts;
+
+ bool bSupported = false;
+ if (pFormat)
+ {
+ if (!strcmp(pFormat, "TrueType") ||
+ !strcmp(pFormat, "CFF"))
+ bSupported = true;
+ }
+ if (!bSupported)
+ {
+ OString aExt( rFontFile.copy( rFontFile.lastIndexOf( '.' )+1 ) );
+ if( aExt.equalsIgnoreAsciiCase("ttf")
+ || aExt.equalsIgnoreAsciiCase("ttc")
+ || aExt.equalsIgnoreAsciiCase("tte") // #i33947# for Gaiji support
+ || aExt.equalsIgnoreAsciiCase("otf") ) // check for TTF- and PS-OpenType too
+ bSupported = true;
+ }
+
+ if (bSupported)
+ {
+ // get number of ttc entries
+ int nLength = CountTTCFonts( aFullPath.getStr() );
+ if (nLength > 0)
+ {
+ SAL_INFO("vcl.fonts", "ttc: " << aFullPath << " contains " << nLength << " fonts");
+
+ sal_uInt64 fileSize = 0;
+
+ OUString aURL;
+ if (osl::File::getFileURLFromSystemPath(OStringToOUString(aFullPath, osl_getThreadTextEncoding()),
+ aURL) == osl::File::E_None)
+ {
+ osl::File aFile(aURL);
+ if (aFile.open(osl_File_OpenFlag_Read | osl_File_OpenFlag_NoLock) == osl::File::E_None)
+ {
+ osl::DirectoryItem aItem;
+ if (osl::DirectoryItem::get(aURL, aItem) == osl::File::E_None)
+ {
+ osl::FileStatus aFileStatus( osl_FileStatus_Mask_FileSize );
+ if (aItem.getFileStatus(aFileStatus) == osl::File::E_None)
+ fileSize = aFileStatus.getFileSize();
+ }
+ }
+ }
+
+ //Feel free to calc the exact max possible number of fonts a file
+ //could contain given its physical size. But this will clamp it to
+ //a sane starting point
+ //http://processingjs.nihongoresources.com/the_smallest_font/
+ //https://github.com/grzegorzrolek/null-ttf
+ const int nMaxFontsPossible = fileSize / 528;
+ if (nLength > nMaxFontsPossible)
+ nLength = nMaxFontsPossible;
+
+ for( int i = 0; i < nLength; i++ )
+ {
+ PrintFont aFont;
+ aFont.m_nDirectory = nDirID;
+ aFont.m_aFontFile = rFontFile;
+ aFont.m_nCollectionEntry = i;
+ if (analyzeSfntFile(aFont))
+ aNewFonts.push_back(aFont);
+ }
+ }
+ else
+ {
+ PrintFont aFont;
+ aFont.m_nDirectory = nDirID;
+ aFont.m_aFontFile = rFontFile;
+ aFont.m_nCollectionEntry = 0;
+
+ // need to read the font anyway to get aliases inside the font file
+ if (analyzeSfntFile(aFont))
+ aNewFonts.push_back(aFont);
+ }
+ }
+ return aNewFonts;
+}
+
+fontID PrintFontManager::findFontFileID(int nDirID, const OString& rFontFile, int nFaceIndex, int nVariationIndex) const
+{
+ fontID nID = 0;
+
+ std::unordered_map< OString, ::std::set< fontID > >::const_iterator set_it = m_aFontFileToFontID.find( rFontFile );
+ if( set_it == m_aFontFileToFontID.end() )
+ return nID;
+
+ for (auto const& elem : set_it->second)
+ {
+ auto it = m_aFonts.find(elem);
+ if( it == m_aFonts.end() )
+ continue;
+ const PrintFont& rFont = (*it).second;
+ if (rFont.m_nDirectory == nDirID &&
+ rFont.m_aFontFile == rFontFile &&
+ rFont.m_nCollectionEntry == nFaceIndex &&
+ rFont.m_nVariationEntry == nVariationIndex)
+ {
+ nID = it->first;
+ if (nID)
+ break;
+ }
+ }
+
+ return nID;
+}
+
+std::vector<fontID> PrintFontManager::findFontFileIDs( int nDirID, const OString& rFontFile ) const
+{
+ std::vector<fontID> aIds;
+
+ std::unordered_map< OString, ::std::set< fontID > >::const_iterator set_it = m_aFontFileToFontID.find( rFontFile );
+ if( set_it == m_aFontFileToFontID.end() )
+ return aIds;
+
+ for (auto const& elem : set_it->second)
+ {
+ auto it = m_aFonts.find(elem);
+ if( it == m_aFonts.end() )
+ continue;
+ const PrintFont& rFont = (*it).second;
+ if (rFont.m_nDirectory == nDirID &&
+ rFont.m_aFontFile == rFontFile)
+ aIds.push_back(it->first);
+ }
+
+ return aIds;
+}
+
+OUString PrintFontManager::convertSfntName( void* pRecord )
+{
+ NameRecord* pNameRecord = static_cast<NameRecord*>(pRecord);
+ OUString aValue;
+ if(
+ ( pNameRecord->platformID == 3 && ( pNameRecord->encodingID == 0 || pNameRecord->encodingID == 1 ) ) // MS, Unicode
+ ||
+ ( pNameRecord->platformID == 0 ) // Apple, Unicode
+ )
+ {
+ OUStringBuffer aName( pNameRecord->slen/2 );
+ const sal_uInt8* pNameBuffer = pNameRecord->sptr;
+ for(int n = 0; n < pNameRecord->slen/2; n++ )
+ aName.append( static_cast<sal_Unicode>(getUInt16BE( pNameBuffer )) );
+ aValue = aName.makeStringAndClear();
+ }
+ else if( pNameRecord->platformID == 3 )
+ {
+ if( pNameRecord->encodingID >= 2 && pNameRecord->encodingID <= 6 )
+ {
+ /*
+ * and now for a special kind of madness:
+ * some fonts encode their byte value string as BE uint16
+ * (leading to stray zero bytes in the string)
+ * while others code two bytes as a uint16 and swap to BE
+ */
+ OStringBuffer aName;
+ const sal_uInt8* pNameBuffer = pNameRecord->sptr;
+ for(int n = 0; n < pNameRecord->slen/2; n++ )
+ {
+ sal_Unicode aCode = static_cast<sal_Unicode>(getUInt16BE( pNameBuffer ));
+ char aChar = aCode >> 8;
+ if( aChar )
+ aName.append( aChar );
+ aChar = aCode & 0x00ff;
+ if( aChar )
+ aName.append( aChar );
+ }
+ switch( pNameRecord->encodingID )
+ {
+ case 2:
+ aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_932 );
+ break;
+ case 3:
+ aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_936 );
+ break;
+ case 4:
+ aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_950 );
+ break;
+ case 5:
+ aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_949 );
+ break;
+ case 6:
+ aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_1361 );
+ break;
+ }
+ }
+ }
+ else if( pNameRecord->platformID == 1 )
+ {
+ OString aName(reinterpret_cast<char*>(pNameRecord->sptr), pNameRecord->slen);
+ rtl_TextEncoding eEncoding = RTL_TEXTENCODING_DONTKNOW;
+ switch (pNameRecord->encodingID)
+ {
+ case 0:
+ eEncoding = RTL_TEXTENCODING_APPLE_ROMAN;
+ break;
+ case 1:
+ eEncoding = RTL_TEXTENCODING_APPLE_JAPANESE;
+ break;
+ case 2:
+ eEncoding = RTL_TEXTENCODING_APPLE_CHINTRAD;
+ break;
+ case 3:
+ eEncoding = RTL_TEXTENCODING_APPLE_KOREAN;
+ break;
+ case 4:
+ eEncoding = RTL_TEXTENCODING_APPLE_ARABIC;
+ break;
+ case 5:
+ eEncoding = RTL_TEXTENCODING_APPLE_HEBREW;
+ break;
+ case 6:
+ eEncoding = RTL_TEXTENCODING_APPLE_GREEK;
+ break;
+ case 7:
+ eEncoding = RTL_TEXTENCODING_APPLE_CYRILLIC;
+ break;
+ case 9:
+ eEncoding = RTL_TEXTENCODING_APPLE_DEVANAGARI;
+ break;
+ case 10:
+ eEncoding = RTL_TEXTENCODING_APPLE_GURMUKHI;
+ break;
+ case 11:
+ eEncoding = RTL_TEXTENCODING_APPLE_GUJARATI;
+ break;
+ case 21:
+ eEncoding = RTL_TEXTENCODING_APPLE_THAI;
+ break;
+ case 25:
+ eEncoding = RTL_TEXTENCODING_APPLE_CHINSIMP;
+ break;
+ case 29:
+ eEncoding = RTL_TEXTENCODING_APPLE_CENTEURO;
+ break;
+ case 32: //Uninterpreted
+ eEncoding = RTL_TEXTENCODING_UTF8;
+ break;
+ default:
+ if (aName.startsWith("Khmer OS"))
+ eEncoding = RTL_TEXTENCODING_UTF8;
+ SAL_WARN_IF(eEncoding == RTL_TEXTENCODING_DONTKNOW, "vcl.fonts", "Unimplemented mac encoding " << pNameRecord->encodingID << " to unicode conversion for fontname " << aName);
+ break;
+ }
+ if (eEncoding != RTL_TEXTENCODING_DONTKNOW)
+ aValue = OStringToOUString(aName, eEncoding);
+ }
+
+ return aValue;
+}
+
+//fdo#33349.There exists an archaic Berling Antiqua font which has a "Times New
+//Roman" name field in it. We don't want the "Times New Roman" name to take
+//precedence in this case. We take Berling Antiqua as a higher priority name,
+//and erase the "Times New Roman" name
+namespace
+{
+ bool isBadTNR(std::u16string_view rName, ::std::set< OUString >& rSet)
+ {
+ bool bRet = false;
+ if ( rName == u"Berling Antiqua" )
+ {
+ ::std::set< OUString >::iterator aEnd = rSet.end();
+ ::std::set< OUString >::iterator aI = rSet.find("Times New Roman");
+ if (aI != aEnd)
+ {
+ bRet = true;
+ rSet.erase(aI);
+ }
+ }
+ return bRet;
+ }
+}
+
+void PrintFontManager::analyzeSfntFamilyName( void const * pTTFont, ::std::vector< OUString >& rNames )
+{
+ OUString aFamily;
+
+ rNames.clear();
+ ::std::set< OUString > aSet;
+
+ NameRecord* pNameRecords = nullptr;
+ int nNameRecords = GetTTNameRecords( static_cast<TrueTypeFont const *>(pTTFont), &pNameRecords );
+ if( nNameRecords && pNameRecords )
+ {
+ LanguageTag aSystem("");
+ LanguageType eLang = aSystem.getLanguageType();
+ int nLastMatch = -1;
+ for( int i = 0; i < nNameRecords; i++ )
+ {
+ if( pNameRecords[i].nameID != 1 || pNameRecords[i].sptr == nullptr )
+ continue;
+ int nMatch = -1;
+ if( pNameRecords[i].platformID == 0 ) // Unicode
+ nMatch = 4000;
+ else if( pNameRecords[i].platformID == 3 )
+ {
+ // this bases on the LanguageType actually being a Win LCID
+ if (pNameRecords[i].languageID == eLang)
+ nMatch = 8000;
+ else if( pNameRecords[i].languageID == LANGUAGE_ENGLISH_US )
+ nMatch = 2000;
+ else if( pNameRecords[i].languageID == LANGUAGE_ENGLISH ||
+ pNameRecords[i].languageID == LANGUAGE_ENGLISH_UK )
+ nMatch = 1500;
+ else
+ nMatch = 1000;
+ }
+ else if (pNameRecords[i].platformID == 1)
+ {
+ AppleLanguageId aAppleId = static_cast<AppleLanguageId>(static_cast<sal_uInt16>(pNameRecords[i].languageID));
+ LanguageTag aApple(makeLanguageTagFromAppleLanguageId(aAppleId));
+ if (aApple == aSystem)
+ nMatch = 8000;
+ else if (aAppleId == AppleLanguageId::ENGLISH)
+ nMatch = 2000;
+ else
+ nMatch = 1000;
+ }
+ OUString aName = convertSfntName( pNameRecords + i );
+ aSet.insert( aName );
+ if (aName.isEmpty())
+ continue;
+ if( nMatch > nLastMatch || isBadTNR(aName, aSet) )
+ {
+ nLastMatch = nMatch;
+ aFamily = aName;
+ }
+ }
+ }
+ DisposeNameRecords( pNameRecords, nNameRecords );
+ if( !aFamily.isEmpty() )
+ {
+ rNames.push_back( aFamily );
+ for (auto const& elem : aSet)
+ if( elem != aFamily )
+ rNames.push_back(elem);
+ }
+}
+
+bool PrintFontManager::analyzeSfntFile( PrintFont& rFont ) const
+{
+ bool bSuccess = false;
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OString aFile = getFontFile( rFont );
+ TrueTypeFont* pTTFont = nullptr;
+
+ auto const e = OpenTTFontFile( aFile.getStr(), rFont.m_nCollectionEntry, &pTTFont );
+ if( e == SFErrCodes::Ok )
+ {
+ TTGlobalFontInfo aInfo;
+ GetTTGlobalFontInfo( pTTFont, & aInfo );
+
+ ::std::vector< OUString > aNames;
+ analyzeSfntFamilyName( pTTFont, aNames );
+
+ // set family name from XLFD if possible
+ if (rFont.m_aFamilyName.isEmpty())
+ {
+ if( !aNames.empty() )
+ {
+ rFont.m_aFamilyName = aNames.front();
+ aNames.erase(aNames.begin());
+ }
+ else
+ {
+ sal_Int32 dotIndex;
+
+ // poor font does not have a family name
+ // name it to file name minus the extension
+ dotIndex = rFont.m_aFontFile.lastIndexOf( '.' );
+ if ( dotIndex == -1 )
+ dotIndex = rFont.m_aFontFile.getLength();
+
+ rFont.m_aFamilyName = OStringToOUString(rFont.m_aFontFile.subView(0, dotIndex), aEncoding);
+ }
+ }
+ for (auto const& aAlias : aNames)
+ {
+ if (!aAlias.isEmpty())
+ {
+ if (rFont.m_aFamilyName != aAlias)
+ {
+ auto al_it = std::find(rFont.m_aAliases.begin(), rFont.m_aAliases.end(), aAlias);
+ if( al_it == rFont.m_aAliases.end() )
+ rFont.m_aAliases.push_back(aAlias);
+ }
+ }
+ }
+
+ if( aInfo.usubfamily )
+ rFont.m_aStyleName = OUString( aInfo.usubfamily );
+
+ SAL_WARN_IF( !aInfo.psname, "vcl.fonts", "No PostScript name in font:" << aFile );
+
+ rFont.m_aPSName = aInfo.psname ?
+ OUString(aInfo.psname, rtl_str_getLength(aInfo.psname), aEncoding) :
+ rFont.m_aFamilyName; // poor font does not have a postscript name
+
+ rFont.m_eFamilyStyle = matchFamilyName(rFont.m_aFamilyName);
+
+ switch( aInfo.weight )
+ {
+ case FW_THIN: rFont.m_eWeight = WEIGHT_THIN; break;
+ case FW_EXTRALIGHT: rFont.m_eWeight = WEIGHT_ULTRALIGHT; break;
+ case FW_LIGHT: rFont.m_eWeight = WEIGHT_LIGHT; break;
+ case FW_MEDIUM: rFont.m_eWeight = WEIGHT_MEDIUM; break;
+ case FW_SEMIBOLD: rFont.m_eWeight = WEIGHT_SEMIBOLD; break;
+ case FW_BOLD: rFont.m_eWeight = WEIGHT_BOLD; break;
+ case FW_EXTRABOLD: rFont.m_eWeight = WEIGHT_ULTRABOLD; break;
+ case FW_BLACK: rFont.m_eWeight = WEIGHT_BLACK; break;
+
+ case FW_NORMAL:
+ default: rFont.m_eWeight = WEIGHT_NORMAL; break;
+ }
+
+ switch( aInfo.width )
+ {
+ case FWIDTH_ULTRA_CONDENSED: rFont.m_eWidth = WIDTH_ULTRA_CONDENSED; break;
+ case FWIDTH_EXTRA_CONDENSED: rFont.m_eWidth = WIDTH_EXTRA_CONDENSED; break;
+ case FWIDTH_CONDENSED: rFont.m_eWidth = WIDTH_CONDENSED; break;
+ case FWIDTH_SEMI_CONDENSED: rFont.m_eWidth = WIDTH_SEMI_CONDENSED; break;
+ case FWIDTH_SEMI_EXPANDED: rFont.m_eWidth = WIDTH_SEMI_EXPANDED; break;
+ case FWIDTH_EXPANDED: rFont.m_eWidth = WIDTH_EXPANDED; break;
+ case FWIDTH_EXTRA_EXPANDED: rFont.m_eWidth = WIDTH_EXTRA_EXPANDED; break;
+ case FWIDTH_ULTRA_EXPANDED: rFont.m_eWidth = WIDTH_ULTRA_EXPANDED; break;
+
+ case FWIDTH_NORMAL:
+ default: rFont.m_eWidth = WIDTH_NORMAL; break;
+ }
+
+ rFont.m_ePitch = aInfo.pitch ? PITCH_FIXED : PITCH_VARIABLE;
+ rFont.m_eItalic = aInfo.italicAngle == 0 ? ITALIC_NONE : ( aInfo.italicAngle < 0 ? ITALIC_NORMAL : ITALIC_OBLIQUE );
+ // #104264# there are fonts that set italic angle 0 although they are
+ // italic; use macstyle bit here
+ if( aInfo.italicAngle == 0 && (aInfo.macStyle & 2) )
+ rFont.m_eItalic = ITALIC_NORMAL;
+
+ rFont.m_aEncoding = aInfo.symbolEncoded ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UCS2;
+
+ if( aInfo.ascender && aInfo.descender )
+ {
+ rFont.m_nLeading = aInfo.linegap;
+ rFont.m_nAscend = aInfo.ascender;
+ rFont.m_nDescend = -aInfo.descender;
+ }
+ else if( aInfo.typoAscender && aInfo.typoDescender )
+ {
+ rFont.m_nLeading = aInfo.typoLineGap;
+ rFont.m_nAscend = aInfo.typoAscender;
+ rFont.m_nDescend = -aInfo.typoDescender;
+ }
+ else if( aInfo.winAscent && aInfo.winDescent )
+ {
+ rFont.m_nAscend = aInfo.winAscent;
+ rFont.m_nDescend = aInfo.winDescent;
+ rFont.m_nLeading = rFont.m_nAscend + rFont.m_nDescend - 1000;
+ }
+
+ // last try: font bounding box
+ if( rFont.m_nAscend == 0 )
+ rFont.m_nAscend = aInfo.yMax;
+ if( rFont.m_nDescend == 0 )
+ rFont.m_nDescend = -aInfo.yMin;
+ if( rFont.m_nLeading == 0 )
+ rFont.m_nLeading = 15 * (rFont.m_nAscend+rFont.m_nDescend) / 100;
+
+ // get bounding box
+ rFont.m_nXMin = aInfo.xMin;
+ rFont.m_nYMin = aInfo.yMin;
+ rFont.m_nXMax = aInfo.xMax;
+ rFont.m_nYMax = aInfo.yMax;
+
+ CloseTTFont( pTTFont );
+ bSuccess = true;
+ }
+ else
+ SAL_WARN("vcl.fonts", "Could not OpenTTFont \"" << aFile << "\": " << int(e));
+
+ return bSuccess;
+}
+
+void PrintFontManager::initialize()
+{
+ #ifdef CALLGRIND_COMPILE
+ CALLGRIND_TOGGLE_COLLECT();
+ CALLGRIND_ZERO_STATS();
+ #endif
+
+ // initialize can be called more than once, e.g.
+ // gtk-fontconfig-timestamp changes to reflect new font installed and
+ // PrintFontManager::initialize called again
+ {
+ m_nNextFontID = 1;
+ m_aFonts.clear();
+ }
+#if OSL_DEBUG_LEVEL > 1
+ clock_t aStart;
+ clock_t aStep1;
+ clock_t aStep2;
+
+ struct tms tms;
+
+ aStart = times( &tms );
+#endif
+
+ // first try fontconfig
+ initFontconfig();
+
+ // part one - look for downloadable fonts
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ const OUString &rSalPrivatePath = psp::getFontPath();
+
+ // search for the fonts in SAL_PRIVATE_FONTPATH first; those are
+ // the fonts installed with the office
+ if( !rSalPrivatePath.isEmpty() )
+ {
+ OString aPath = OUStringToOString( rSalPrivatePath, aEncoding );
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OString aToken = aPath.getToken( 0, ';', nIndex );
+ normPath( aToken );
+ if (!aToken.isEmpty())
+ addFontconfigDir(aToken);
+ } while( nIndex >= 0 );
+ }
+
+ countFontconfigFonts();
+
+#if OSL_DEBUG_LEVEL > 1
+ aStep1 = times( &tms );
+
+ aStep2 = times( &tms );
+ SAL_INFO("vcl.fonts", "PrintFontManager::initialize: collected "
+ << m_aFonts.size()
+ << " fonts.");
+ double fTick = (double)sysconf( _SC_CLK_TCK );
+ SAL_INFO("vcl.fonts", "Step 1 took "
+ << ((double)(aStep1 - aStart)/fTick)
+ << " seconds.");
+ SAL_INFO("vcl.fonts", "Step 2 took "
+ << ((double)(aStep2 - aStep1)/fTick)
+ << " seconds.");
+#endif
+
+ #ifdef CALLGRIND_COMPILE
+ CALLGRIND_DUMP_STATS();
+ CALLGRIND_TOGGLE_COLLECT();
+ #endif
+}
+
+void PrintFontManager::getFontList( ::std::vector< fontID >& rFontIDs )
+{
+ rFontIDs.clear();
+
+ for (auto const& font : m_aFonts)
+ rFontIDs.push_back(font.first);
+}
+
+void PrintFontManager::fillPrintFontInfo(const PrintFont& rFont, FastPrintFontInfo& rInfo)
+{
+ rInfo.m_aFamilyName = rFont.m_aFamilyName;
+ rInfo.m_aStyleName = rFont.m_aStyleName;
+ rInfo.m_eFamilyStyle = rFont.m_eFamilyStyle;
+ rInfo.m_eItalic = rFont.m_eItalic;
+ rInfo.m_eWidth = rFont.m_eWidth;
+ rInfo.m_eWeight = rFont.m_eWeight;
+ rInfo.m_ePitch = rFont.m_ePitch;
+ rInfo.m_aEncoding = rFont.m_aEncoding;
+ rInfo.m_aAliases = rFont.m_aAliases;
+}
+
+void PrintFontManager::fillPrintFontInfo( PrintFont& rFont, PrintFontInfo& rInfo ) const
+{
+ if (rFont.m_nAscend == 0 && rFont.m_nDescend == 0)
+ {
+ analyzeSfntFile(rFont);
+ }
+
+ fillPrintFontInfo( rFont, static_cast< FastPrintFontInfo& >( rInfo ) );
+
+ rInfo.m_nAscend = rFont.m_nAscend;
+ rInfo.m_nDescend = rFont.m_nDescend;
+}
+
+bool PrintFontManager::getFontInfo( fontID nFontID, PrintFontInfo& rInfo ) const
+{
+ const PrintFont* pFont = getFont( nFontID );
+ if( pFont )
+ {
+ rInfo.m_nID = nFontID;
+ fillPrintFontInfo( *pFont, rInfo );
+ }
+ return pFont != nullptr;
+}
+
+bool PrintFontManager::getFontFastInfo( fontID nFontID, FastPrintFontInfo& rInfo ) const
+{
+ const PrintFont* pFont = getFont( nFontID );
+ if( pFont )
+ {
+ rInfo.m_nID = nFontID;
+ fillPrintFontInfo( *pFont, rInfo );
+ }
+ return pFont != nullptr;
+}
+
+void PrintFontManager::getFontBoundingBox( fontID nFontID, int& xMin, int& yMin, int& xMax, int& yMax )
+{
+ PrintFont* pFont = getFont( nFontID );
+ if( pFont )
+ {
+ if( pFont->m_nXMin == 0 && pFont->m_nYMin == 0 && pFont->m_nXMax == 0 && pFont->m_nYMax == 0 )
+ {
+ analyzeSfntFile(*pFont);
+ }
+ xMin = pFont->m_nXMin;
+ yMin = pFont->m_nYMin;
+ xMax = pFont->m_nXMax;
+ yMax = pFont->m_nYMax;
+ }
+}
+
+int PrintFontManager::getFontFaceNumber( fontID nFontID ) const
+{
+ int nRet = 0;
+ const PrintFont* pFont = getFont( nFontID );
+ if (pFont)
+ {
+ nRet = pFont->m_nCollectionEntry;
+ if (nRet < 0)
+ nRet = 0;
+ }
+ return nRet;
+}
+
+int PrintFontManager::getFontFaceVariation( fontID nFontID ) const
+{
+ int nRet = 0;
+ const PrintFont* pFont = getFont( nFontID );
+ if (pFont)
+ {
+ nRet = pFont->m_nVariationEntry;
+ if (nRet < 0)
+ nRet = 0;
+ }
+ return nRet;
+}
+
+FontFamily PrintFontManager::matchFamilyName( std::u16string_view rFamily )
+{
+ struct family_t {
+ const char* mpName;
+ sal_uInt16 mnLength;
+ FontFamily meType;
+ };
+
+#define InitializeClass( p, a ) p, sizeof(p) - 1, a
+ static const family_t pFamilyMatch[] = {
+ { InitializeClass( "arial", FAMILY_SWISS ) },
+ { InitializeClass( "arioso", FAMILY_SCRIPT ) },
+ { InitializeClass( "avant garde", FAMILY_SWISS ) },
+ { InitializeClass( "avantgarde", FAMILY_SWISS ) },
+ { InitializeClass( "bembo", FAMILY_ROMAN ) },
+ { InitializeClass( "bookman", FAMILY_ROMAN ) },
+ { InitializeClass( "conga", FAMILY_ROMAN ) },
+ { InitializeClass( "courier", FAMILY_MODERN ) },
+ { InitializeClass( "curl", FAMILY_SCRIPT ) },
+ { InitializeClass( "fixed", FAMILY_MODERN ) },
+ { InitializeClass( "gill", FAMILY_SWISS ) },
+ { InitializeClass( "helmet", FAMILY_MODERN ) },
+ { InitializeClass( "helvetica", FAMILY_SWISS ) },
+ { InitializeClass( "international", FAMILY_MODERN ) },
+ { InitializeClass( "lucida", FAMILY_SWISS ) },
+ { InitializeClass( "new century schoolbook", FAMILY_ROMAN ) },
+ { InitializeClass( "palatino", FAMILY_ROMAN ) },
+ { InitializeClass( "roman", FAMILY_ROMAN ) },
+ { InitializeClass( "sans serif", FAMILY_SWISS ) },
+ { InitializeClass( "sansserif", FAMILY_SWISS ) },
+ { InitializeClass( "serf", FAMILY_ROMAN ) },
+ { InitializeClass( "serif", FAMILY_ROMAN ) },
+ { InitializeClass( "times", FAMILY_ROMAN ) },
+ { InitializeClass( "utopia", FAMILY_ROMAN ) },
+ { InitializeClass( "zapf chancery", FAMILY_SCRIPT ) },
+ { InitializeClass( "zapfchancery", FAMILY_SCRIPT ) }
+ };
+
+ OString aFamily = OUStringToOString( rFamily, RTL_TEXTENCODING_ASCII_US );
+ sal_uInt32 nLower = 0;
+ sal_uInt32 nUpper = SAL_N_ELEMENTS(pFamilyMatch);
+
+ while( nLower < nUpper )
+ {
+ sal_uInt32 nCurrent = (nLower + nUpper) / 2;
+ const family_t* pHaystack = pFamilyMatch + nCurrent;
+ sal_Int32 nComparison =
+ rtl_str_compareIgnoreAsciiCase_WithLength
+ (
+ aFamily.getStr(), aFamily.getLength(),
+ pHaystack->mpName, pHaystack->mnLength
+ );
+
+ if( nComparison < 0 )
+ nUpper = nCurrent;
+ else
+ if( nComparison > 0 )
+ nLower = nCurrent + 1;
+ else
+ return pHaystack->meType;
+ }
+
+ return FAMILY_DONTKNOW;
+}
+
+OString PrintFontManager::getFontFile(const PrintFont& rFont) const
+{
+ std::unordered_map< int, OString >::const_iterator it = m_aAtomToDir.find(rFont.m_nDirectory);
+ OString aPath = it->second + "/" + rFont.m_aFontFile;
+ return aPath;
+}
+
+OUString PrintFontManager::getPSName( fontID nFontID )
+{
+ PrintFont* pFont = getFont( nFontID );
+ if (pFont && pFont->m_aPSName.isEmpty())
+ {
+ analyzeSfntFile(*pFont);
+ }
+
+ return pFont ? pFont->m_aPSName : OUString();
+}
+
+int PrintFontManager::getFontAscend( fontID nFontID )
+{
+ PrintFont* pFont = getFont( nFontID );
+ if (pFont && pFont->m_nAscend == 0 && pFont->m_nDescend == 0)
+ {
+ analyzeSfntFile(*pFont);
+ }
+ return pFont ? pFont->m_nAscend : 0;
+}
+
+int PrintFontManager::getFontDescend( fontID nFontID )
+{
+ PrintFont* pFont = getFont( nFontID );
+ if (pFont && pFont->m_nAscend == 0 && pFont->m_nDescend == 0)
+ {
+ analyzeSfntFile(*pFont);
+ }
+ return pFont ? pFont->m_nDescend : 0;
+}
+
+// TODO: move most of this stuff into the central font-subsetting code
+bool PrintFontManager::createFontSubset(
+ FontSubsetInfo& rInfo,
+ fontID nFont,
+ const OUString& rOutFile,
+ const sal_GlyphId* pGlyphIds,
+ const sal_uInt8* pNewEncoding,
+ sal_Int32* pWidths,
+ int nGlyphs
+ )
+{
+ PrintFont* pFont = getFont( nFont );
+ if( !pFont )
+ return false;
+
+ rInfo.m_nFontType = FontType::SFNT_TTF;
+
+ // reshuffle array of requested glyphs to make sure glyph0==notdef
+ sal_uInt8 pEnc[256];
+ sal_uInt16 pGID[256];
+ sal_uInt8 pOldIndex[256];
+ memset( pEnc, 0, sizeof( pEnc ) );
+ memset( pGID, 0, sizeof( pGID ) );
+ memset( pOldIndex, 0, sizeof( pOldIndex ) );
+ if( nGlyphs > 256 )
+ return false;
+ int nChar = 1;
+ for( int i = 0; i < nGlyphs; i++ )
+ {
+ if( pNewEncoding[i] == 0 )
+ {
+ pOldIndex[ 0 ] = i;
+ }
+ else
+ {
+ SAL_WARN_IF( (pGlyphIds[i] & 0x007f0000), "vcl.fonts", "overlong glyph id" );
+ SAL_WARN_IF( static_cast<int>(pNewEncoding[i]) >= nGlyphs, "vcl.fonts", "encoding wrong" );
+ SAL_WARN_IF( pEnc[pNewEncoding[i]] != 0 || pGID[pNewEncoding[i]] != 0, "vcl.fonts", "duplicate encoded glyph" );
+ pEnc[ pNewEncoding[i] ] = pNewEncoding[i];
+ pGID[ pNewEncoding[i] ] = static_cast<sal_uInt16>(pGlyphIds[ i ]);
+ pOldIndex[ pNewEncoding[i] ] = i;
+ nChar++;
+ }
+ }
+ nGlyphs = nChar; // either input value or increased by one
+
+ // prepare system name for read access for subset source file
+ // TODO: since this file is usually already mmapped there is no need to open it again
+ const OString aFromFile = getFontFile( *pFont );
+
+ TrueTypeFont* pTTFont = nullptr; // TODO: rename to SfntFont
+ if( OpenTTFontFile( aFromFile.getStr(), pFont->m_nCollectionEntry, &pTTFont ) != SFErrCodes::Ok )
+ return false;
+
+ // prepare system name for write access for subset file target
+ OUString aSysPath;
+ if( osl_File_E_None != osl_getSystemPathFromFileURL( rOutFile.pData, &aSysPath.pData ) )
+ return false;
+ const rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ const OString aToFile( OUStringToOString( aSysPath, aEncoding ) );
+
+ // do CFF subsetting if possible
+ sal_uInt32 nCffLength = 0;
+ const sal_uInt8* pCffBytes = pTTFont->table(vcl::O_CFF, nCffLength);
+ if (pCffBytes)
+ {
+ rInfo.LoadFont( FontType::CFF_FONT, pCffBytes, nCffLength );
+#if 1 // TODO: remove 16bit->long conversion when related methods handle non-16bit glyphids
+ sal_GlyphId aRequestedGlyphIds[256];
+ for( int i = 0; i < nGlyphs; ++i )
+ aRequestedGlyphIds[i] = pGID[i];
+#endif
+ // create subset file at requested path
+ FILE* pOutFile = fopen( aToFile.getStr(), "wb" );
+ if (!pOutFile)
+ {
+ CloseTTFont( pTTFont );
+ return false;
+ }
+ // create font subset
+ const char* const pGlyphSetName = nullptr; // TODO: better name?
+ const bool bOK = rInfo.CreateFontSubset(
+ FontType::TYPE1_PFB,
+ pOutFile, pGlyphSetName,
+ aRequestedGlyphIds, pEnc, nGlyphs, pWidths );
+ fclose( pOutFile );
+ // For OTC, values from hhea or OS2 are better
+ psp::PrintFontInfo aFontInfo;
+ if( getFontInfo( nFont, aFontInfo ) )
+ {
+ rInfo.m_nAscent = aFontInfo.m_nAscend;
+ rInfo.m_nDescent = -aFontInfo.m_nDescend;
+ }
+ // cleanup before early return
+ CloseTTFont( pTTFont );
+ return bOK;
+ }
+
+ // do TTF->Type42 or Type3 subsetting
+ // fill in font info
+ psp::PrintFontInfo aFontInfo;
+ if( ! getFontInfo( nFont, aFontInfo ) )
+ return false;
+
+ rInfo.m_nAscent = aFontInfo.m_nAscend;
+ rInfo.m_nDescent = aFontInfo.m_nDescend;
+ rInfo.m_aPSName = getPSName( nFont );
+
+ int xMin, yMin, xMax, yMax;
+ getFontBoundingBox( nFont, xMin, yMin, xMax, yMax );
+ rInfo.m_aFontBBox = tools::Rectangle( Point( xMin, yMin ), Size( xMax-xMin, yMax-yMin ) );
+ rInfo.m_nCapHeight = yMax; // Well ...
+
+ if (pWidths)
+ {
+ // fill in glyph advance widths
+ std::unique_ptr<sal_uInt16[]> pMetrics = GetTTSimpleGlyphMetrics( pTTFont,
+ pGID,
+ nGlyphs,
+ false/*bVertical*/ );
+ if( pMetrics )
+ {
+ for( int i = 0; i < nGlyphs; i++ )
+ pWidths[pOldIndex[i]] = pMetrics[i];
+ pMetrics.reset();
+ }
+ else
+ {
+ CloseTTFont( pTTFont );
+ return false;
+ }
+ }
+
+ bool bSuccess = ( SFErrCodes::Ok == CreateTTFromTTGlyphs( pTTFont,
+ aToFile.getStr(),
+ pGID,
+ pEnc,
+ nGlyphs ) );
+ CloseTTFont( pTTFont );
+
+ return bSuccess;
+}
+
+void PrintFontManager::getGlyphWidths( fontID nFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ std::map< sal_Unicode, sal_uInt32 >& rUnicodeEnc )
+{
+ PrintFont* pFont = getFont( nFont );
+ if (!pFont)
+ return;
+ TrueTypeFont* pTTFont = nullptr;
+ OString aFromFile = getFontFile( *pFont );
+ if( OpenTTFontFile( aFromFile.getStr(), pFont->m_nCollectionEntry, &pTTFont ) != SFErrCodes::Ok )
+ return;
+ int nGlyphs = pTTFont->glyphCount();
+ if (nGlyphs > 0)
+ {
+ rWidths.resize(nGlyphs);
+ std::vector<sal_uInt16> aGlyphIds(nGlyphs);
+ for (int i = 0; i < nGlyphs; i++)
+ aGlyphIds[i] = sal_uInt16(i);
+ std::unique_ptr<sal_uInt16[]> pMetrics = GetTTSimpleGlyphMetrics(pTTFont,
+ aGlyphIds.data(),
+ nGlyphs,
+ bVertical);
+ if (pMetrics)
+ {
+ for (int i = 0; i< nGlyphs; i++)
+ rWidths[i] = pMetrics[i];
+ pMetrics.reset();
+ rUnicodeEnc.clear();
+ }
+
+ // fill the unicode map
+ // TODO: isn't this map already available elsewhere in the fontmanager?
+ sal_uInt32 nCmapSize = 0;
+ const sal_uInt8* pCmapData = pTTFont->table(O_cmap, nCmapSize);
+ if (pCmapData)
+ {
+ CmapResult aCmapResult;
+ if (ParseCMAP(pCmapData, nCmapSize, aCmapResult))
+ {
+ FontCharMapRef xFontCharMap(new FontCharMap(aCmapResult));
+ for (sal_uInt32 cOld = 0;;)
+ {
+ // get next unicode covered by font
+ const sal_uInt32 c = xFontCharMap->GetNextChar(cOld);
+ if (c == cOld)
+ break;
+ cOld = c;
+#if 1 // TODO: remove when sal_Unicode covers all of unicode
+ if (c > sal_Unicode(~0))
+ break;
+#endif
+ // get the matching glyph index
+ const sal_GlyphId aGlyphId = xFontCharMap->GetGlyphIndex(c);
+ // update the requested map
+ rUnicodeEnc[static_cast<sal_Unicode>(c)] = aGlyphId;
+ }
+ }
+ }
+ }
+ CloseTTFont(pTTFont);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/fontmanager/fontsubst.cxx b/vcl/unx/generic/fontmanager/fontsubst.cxx
new file mode 100644
index 000000000..afbe53dd4
--- /dev/null
+++ b/vcl/unx/generic/fontmanager/fontsubst.cxx
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <unx/geninst.h>
+#include <font/PhysicalFontCollection.hxx>
+#include <font/fontsubstitution.hxx>
+#include <unx/fontmanager.hxx>
+
+// platform specific font substitution hooks
+
+namespace {
+
+class FcPreMatchSubstitution
+: public vcl::font::PreMatchFontSubstitution
+{
+public:
+ bool FindFontSubstitute( vcl::font::FontSelectPattern& ) const override;
+ typedef ::std::pair<vcl::font::FontSelectPattern, vcl::font::FontSelectPattern> value_type;
+private:
+ typedef ::std::list<value_type> CachedFontMapType;
+ mutable CachedFontMapType maCachedFontMap;
+};
+
+class FcGlyphFallbackSubstitution
+: public vcl::font::GlyphFallbackFontSubstitution
+{
+ // TODO: add a cache
+public:
+ bool FindFontSubstitute(vcl::font::FontSelectPattern&, LogicalFontInstance* pLogicalFont, OUString& rMissingCodes) const override;
+};
+
+}
+
+void SalGenericInstance::RegisterFontSubstitutors(vcl::font::PhysicalFontCollection* pFontCollection)
+{
+ // register font fallback substitutions
+ static FcPreMatchSubstitution aSubstPreMatch;
+ pFontCollection->SetPreMatchHook( &aSubstPreMatch );
+
+ // register glyph fallback substitutions
+ static FcGlyphFallbackSubstitution aSubstFallback;
+ pFontCollection->SetFallbackHook( &aSubstFallback );
+}
+
+static vcl::font::FontSelectPattern GetFcSubstitute(const vcl::font::FontSelectPattern &rFontSelData, OUString& rMissingCodes)
+{
+ vcl::font::FontSelectPattern aSubstituted(rFontSelData);
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ rMgr.Substitute(aSubstituted, rMissingCodes);
+ return aSubstituted;
+}
+
+namespace
+{
+ bool uselessmatch(const vcl::font::FontSelectPattern &rOrig, const vcl::font::FontSelectPattern &rNew)
+ {
+ return
+ (
+ rOrig.maTargetName == rNew.maSearchName &&
+ rOrig.GetWeight() == rNew.GetWeight() &&
+ rOrig.GetItalic() == rNew.GetItalic() &&
+ rOrig.GetPitch() == rNew.GetPitch() &&
+ rOrig.GetWidthType() == rNew.GetWidthType()
+ );
+ }
+
+ class equal
+ {
+ private:
+ const vcl::font::FontSelectPattern& mrAttributes;
+ public:
+ explicit equal(const vcl::font::FontSelectPattern& rAttributes)
+ : mrAttributes(rAttributes)
+ {
+ }
+ bool operator()(const FcPreMatchSubstitution::value_type& rOther) const
+ { return rOther.first == mrAttributes; }
+ };
+}
+
+bool FcPreMatchSubstitution::FindFontSubstitute(vcl::font::FontSelectPattern &rFontSelData) const
+{
+ // We don't actually want to talk to Fontconfig at all for symbol fonts
+ if( rFontSelData.IsSymbolFont() )
+ return false;
+ // StarSymbol is a unicode font, but it still deserves the symbol flag
+ if ( IsStarSymbol(rFontSelData.maSearchName) )
+ return false;
+
+ //see fdo#41556 and fdo#47636
+ //fontconfig can return e.g. an italic font for a non-italic input and/or
+ //different fonts depending on fontsize, bold, etc settings so don't cache
+ //just on the name, cache map all the input and all the output not just map
+ //from original selection to output fontname
+ vcl::font::FontSelectPattern& rPatternAttributes = rFontSelData;
+ CachedFontMapType &rCachedFontMap = maCachedFontMap;
+ CachedFontMapType::iterator itr = std::find_if(rCachedFontMap.begin(), rCachedFontMap.end(), equal(rPatternAttributes));
+ if (itr != rCachedFontMap.end())
+ {
+ // Cached substitution
+ rFontSelData = itr->second;
+ if (itr != rCachedFontMap.begin())
+ {
+ // MRU, move it to the front
+ rCachedFontMap.splice(rCachedFontMap.begin(), rCachedFontMap, itr);
+ }
+ return true;
+ }
+
+ OUString aDummy;
+ const vcl::font::FontSelectPattern aOut = GetFcSubstitute( rFontSelData, aDummy );
+
+ if( aOut.maSearchName.isEmpty() )
+ return false;
+
+ const bool bHaveSubstitute = !uselessmatch( rFontSelData, aOut );
+
+#ifdef DEBUG
+ std::ostringstream oss;
+ oss << "FcPreMatchSubstitution \""
+ << rFontSelData.maTargetName
+ << "\" bipw="
+ << rFontSelData.GetWeight()
+ << rFontSelData.GetItalic()
+ << rFontSelData.GetPitch()
+ << rFontSelData.GetWidthType()
+ << " -> ";
+ if( !bHaveSubstitute )
+ oss << "no substitute available.";
+ else
+ oss << "\""
+ << aOut.maSearchName
+ << "\" bipw="
+ << aOut.GetWeight()
+ << aOut.GetItalic()
+ << aOut.GetPitch()
+ << aOut.GetWidthType();
+ SAL_INFO("vcl.fonts", oss.str());
+#endif
+
+ if( bHaveSubstitute )
+ {
+ rCachedFontMap.push_front(value_type(rFontSelData, aOut));
+ // Fairly arbitrary limit in this case, but I recall measuring max 8
+ // fonts as the typical max amount of fonts in medium sized documents, so make it
+ // a fair chunk larger to accommodate weird documents./
+ if (rCachedFontMap.size() > 256)
+ rCachedFontMap.pop_back();
+ rFontSelData = aOut;
+ }
+
+ return bHaveSubstitute;
+}
+
+bool FcGlyphFallbackSubstitution::FindFontSubstitute(vcl::font::FontSelectPattern& rFontSelData,
+ LogicalFontInstance* /*pLogicalFont*/,
+ OUString& rMissingCodes ) const
+{
+ // We don't actually want to talk to Fontconfig at all for symbol fonts
+ if( rFontSelData.IsSymbolFont() )
+ return false;
+ // StarSymbol is a unicode font, but it still deserves the symbol flag
+ if ( IsStarSymbol(rFontSelData.maSearchName) )
+ return false;
+
+ const vcl::font::FontSelectPattern aOut = GetFcSubstitute( rFontSelData, rMissingCodes );
+ // TODO: cache the unicode + srcfont specific result
+ // FC doing it would be preferable because it knows the invariables
+ // e.g. FC knows the FC rule that all Arial gets replaced by LiberationSans
+ // whereas we would have to check for every size or attribute
+ if( aOut.maSearchName.isEmpty() )
+ return false;
+
+ const bool bHaveSubstitute = !uselessmatch( rFontSelData, aOut );
+
+#ifdef DEBUG
+ std::ostringstream oss;
+ oss << "FcGFSubstitution \""
+ << rFontSelData.maTargetName
+ << "\" bipw="
+ << rFontSelData.GetWeight()
+ << rFontSelData.GetItalic()
+ << rFontSelData.GetPitch()
+ << rFontSelData.GetWidthType()
+ << " -> ";
+ if( !bHaveSubstitute )
+ oss << "no substitute available.";
+ else
+ oss << "\""
+ << aOut.maSearchName
+ << "\" bipw="
+ << aOut.GetWeight()
+ << aOut.GetItalic()
+ << aOut.GetPitch()
+ << aOut.GetWidthType();
+ SAL_INFO("vcl.fonts", oss.str());
+#endif
+
+ if( bHaveSubstitute )
+ rFontSelData = aOut;
+
+ return bHaveSubstitute;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/fontmanager/helper.cxx b/vcl/unx/generic/fontmanager/helper.cxx
new file mode 100644
index 000000000..6cea0998e
--- /dev/null
+++ b/vcl/unx/generic/fontmanager/helper.cxx
@@ -0,0 +1,262 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_folders.h>
+
+#include <sys/stat.h>
+#include <limits.h>
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <osl/thread.h>
+#include <rtl/bootstrap.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <tools/urlobj.hxx>
+#include <unx/helper.hxx>
+
+#include <tuple>
+
+using ::rtl::Bootstrap;
+
+namespace psp {
+
+OUString getOfficePath( whichOfficePath ePath )
+{
+ static const auto aPaths = [] {
+ OUString sRoot, sUser, sConfig;
+ Bootstrap::get("BRAND_BASE_DIR", sRoot);
+ Bootstrap aBootstrap(sRoot + "/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap"));
+ aBootstrap.getFrom("UserInstallation", sUser);
+ aBootstrap.getFrom("CustomDataUrl", sConfig);
+ OUString aUPath = sUser + "/user/psprint";
+ if (sRoot.startsWith("file://"))
+ {
+ OUString aSysPath;
+ if (osl::FileBase::getSystemPathFromFileURL(sRoot, aSysPath) == osl::FileBase::E_None)
+ sRoot = aSysPath;
+ }
+ if (sUser.startsWith("file://"))
+ {
+ OUString aSysPath;
+ if (osl::FileBase::getSystemPathFromFileURL(sUser, aSysPath) == osl::FileBase::E_None)
+ sUser = aSysPath;
+ }
+ if (sConfig.startsWith("file://"))
+ {
+ OUString aSysPath;
+ if (osl::FileBase::getSystemPathFromFileURL(sConfig, aSysPath) == osl::FileBase::E_None)
+ sConfig = aSysPath;
+ }
+
+ // ensure user path exists
+ SAL_INFO("vcl.fonts", "Trying to create: " << aUPath);
+ osl::Directory::createPath(aUPath);
+
+ return std::make_tuple(sRoot, sUser, sConfig);
+ }();
+ const auto& [aInstallationRootPath, aUserPath, aConfigPath] = aPaths;
+
+ switch( ePath )
+ {
+ case whichOfficePath::ConfigPath: return aConfigPath;
+ case whichOfficePath::InstallationRootPath: return aInstallationRootPath;
+ case whichOfficePath::UserPath: return aUserPath;
+ }
+ return OUString();
+}
+
+static OString getEnvironmentPath( const char* pKey )
+{
+ OString aPath;
+
+ const char* pValue = getenv( pKey );
+ if( pValue && *pValue )
+ {
+ aPath = OString( pValue );
+ }
+ return aPath;
+}
+
+} // namespace psp
+
+void psp::getPrinterPathList( std::vector< OUString >& rPathList, const char* pSubDir )
+{
+ rPathList.clear();
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+
+ OUStringBuffer aPathBuffer( 256 );
+
+ // append net path
+ aPathBuffer.append( getOfficePath( whichOfficePath::InstallationRootPath ) );
+ if( !aPathBuffer.isEmpty() )
+ {
+ aPathBuffer.append( "/" LIBO_SHARE_FOLDER "/psprint" );
+ if( pSubDir )
+ {
+ aPathBuffer.append( '/' );
+ aPathBuffer.appendAscii( pSubDir );
+ }
+ rPathList.push_back( aPathBuffer.makeStringAndClear() );
+ }
+ // append user path
+ aPathBuffer.append( getOfficePath( whichOfficePath::UserPath ) );
+ if( !aPathBuffer.isEmpty() )
+ {
+ aPathBuffer.append( "/user/psprint" );
+ if( pSubDir )
+ {
+ aPathBuffer.append( '/' );
+ aPathBuffer.appendAscii( pSubDir );
+ }
+ rPathList.push_back( aPathBuffer.makeStringAndClear() );
+ }
+
+ OString aPath( getEnvironmentPath("SAL_PSPRINT") );
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OString aDir( aPath.getToken( 0, ':', nIndex ) );
+ if( aDir.isEmpty() )
+ continue;
+
+ if( pSubDir )
+ {
+ aDir += OString::Concat("/") + pSubDir;
+ }
+ struct stat aStat;
+ if( stat( aDir.getStr(), &aStat ) || ! S_ISDIR( aStat.st_mode ) )
+ continue;
+
+ rPathList.push_back( OStringToOUString( aDir, aEncoding ) );
+ } while( nIndex != -1 );
+
+ #ifdef SYSTEM_PPD_DIR
+ if( pSubDir && rtl_str_compare( pSubDir, PRINTER_PPDDIR ) == 0 )
+ {
+ rPathList.push_back( OStringToOUString( OString( SYSTEM_PPD_DIR ), RTL_TEXTENCODING_UTF8 ) );
+ }
+ #endif
+
+ if( !rPathList.empty() )
+ return;
+
+ // last resort: next to program file (mainly for setup)
+ OUString aExe;
+ if( osl_getExecutableFile( &aExe.pData ) == osl_Process_E_None )
+ {
+ INetURLObject aDir( aExe );
+ aDir.removeSegment();
+ aExe = aDir.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ OUString aSysPath;
+ if( osl_getSystemPathFromFileURL( aExe.pData, &aSysPath.pData ) == osl_File_E_None )
+ {
+ rPathList.push_back( aSysPath );
+ }
+ }
+}
+
+OUString const & psp::getFontPath()
+{
+ static OUString aPath;
+
+ if (aPath.isEmpty())
+ {
+ OUStringBuffer aPathBuffer( 512 );
+
+ OUString aConfigPath( getOfficePath( whichOfficePath::ConfigPath ) );
+ OUString aInstallationRootPath( getOfficePath( whichOfficePath::InstallationRootPath ) );
+ OUString aUserPath( getOfficePath( whichOfficePath::UserPath ) );
+ if (!aInstallationRootPath.isEmpty())
+ {
+ // internal font resources, required for normal operation, like OpenSymbol
+ aPathBuffer.append(aInstallationRootPath
+ + "/" LIBO_SHARE_RESOURCE_FOLDER "/common/fonts;");
+ }
+ if( !aConfigPath.isEmpty() )
+ {
+ // #i53530# Path from CustomDataUrl will completely
+ // replace net share and user paths if the path exists
+ OUString sPath = aConfigPath + "/" LIBO_SHARE_FOLDER "/fonts";
+ // check existence of config path
+ struct stat aStat;
+ if( 0 != stat( OUStringToOString( sPath, osl_getThreadTextEncoding() ).getStr(), &aStat )
+ || ! S_ISDIR( aStat.st_mode ) )
+ aConfigPath.clear();
+ else
+ {
+ aPathBuffer.append(sPath);
+ }
+ }
+ if( aConfigPath.isEmpty() )
+ {
+ if( !aInstallationRootPath.isEmpty() )
+ {
+ aPathBuffer.append( aInstallationRootPath );
+ aPathBuffer.append( "/" LIBO_SHARE_FOLDER "/fonts/truetype;");
+ }
+ if( !aUserPath.isEmpty() )
+ {
+ aPathBuffer.append( aUserPath );
+ aPathBuffer.append( "/user/fonts" );
+ }
+ }
+
+ aPath = aPathBuffer.makeStringAndClear();
+ SAL_INFO("vcl.fonts", "Initializing font path to: " << aPath);
+ }
+ return aPath;
+}
+
+void psp::normPath( OString& rPath )
+{
+ char buf[PATH_MAX];
+
+ // double slashes and slash at end are probably
+ // removed by realpath anyway, but since this runs
+ // on many different platforms let's play it safe
+ OString aPath = rPath.replaceAll("//", "/");
+
+ if( aPath.endsWith("/") )
+ aPath = aPath.copy(0, aPath.getLength()-1);
+
+ if( ( aPath.indexOf("./") != -1 ||
+ aPath.indexOf( '~' ) != -1 )
+ && realpath( aPath.getStr(), buf ) )
+ {
+ rPath = buf;
+ }
+ else
+ {
+ rPath = aPath;
+ }
+}
+
+void psp::splitPath( OString& rPath, OString& rDir, OString& rBase )
+{
+ normPath( rPath );
+ sal_Int32 nIndex = rPath.lastIndexOf( '/' );
+ if( nIndex > 0 )
+ rDir = rPath.copy( 0, nIndex );
+ else if( nIndex == 0 ) // root dir
+ rDir = rPath.copy( 0, 1 );
+ if( rPath.getLength() > nIndex+1 )
+ rBase = rPath.copy( nIndex+1 );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.cxx b/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.cxx
new file mode 100644
index 000000000..2109e19b9
--- /dev/null
+++ b/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.cxx
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "X11CairoSalGraphicsImpl.hxx"
+
+#if ENABLE_CAIRO_CANVAS
+
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+
+X11CairoSalGraphicsImpl::X11CairoSalGraphicsImpl(X11SalGraphics& rParent, X11Common& rX11Common)
+ : X11SalGraphicsImpl(rParent)
+ , mrX11Common(rX11Common)
+ , mnPenColor(SALCOLOR_NONE)
+ , mnFillColor(SALCOLOR_NONE)
+{
+}
+
+bool X11CairoSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+ if (fTransparency >= 1.0)
+ {
+ return true;
+ }
+
+ if (rPolyPolygon.count() == 0)
+ {
+ return true;
+ }
+
+ // Fallback: Transform to DeviceCoordinates
+ basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
+ aPolyPolygon.transform(rObjectToDevice);
+
+ if (SALCOLOR_NONE == mnFillColor && SALCOLOR_NONE == mnPenColor)
+ {
+ return true;
+ }
+
+ // enable by setting to something
+ static const char* pUseCairoForPolygons(getenv("SAL_ENABLE_USE_CAIRO_FOR_POLYGONS"));
+
+ if (nullptr != pUseCairoForPolygons && mrX11Common.SupportsCairo())
+ {
+ // snap to raster if requested
+ const bool bSnapPoints(!getAntiAlias());
+
+ if (bSnapPoints)
+ {
+ aPolyPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygon);
+ }
+
+ cairo_t* cr = mrX11Common.getCairoContext();
+ clipRegion(cr);
+
+ for (auto const& rPolygon : std::as_const(aPolyPolygon))
+ {
+ const sal_uInt32 nPointCount(rPolygon.count());
+
+ if (nPointCount)
+ {
+ const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nPointCount : nPointCount - 1);
+
+ if (nEdgeCount)
+ {
+ basegfx::B2DCubicBezier aEdge;
+
+ for (sal_uInt32 b = 0; b < nEdgeCount; ++b)
+ {
+ rPolygon.getBezierSegment(b, aEdge);
+
+ if (!b)
+ {
+ const basegfx::B2DPoint aStart(aEdge.getStartPoint());
+ cairo_move_to(cr, aStart.getX(), aStart.getY());
+ }
+
+ const basegfx::B2DPoint aEnd(aEdge.getEndPoint());
+
+ if (aEdge.isBezier())
+ {
+ const basegfx::B2DPoint aCP1(aEdge.getControlPointA());
+ const basegfx::B2DPoint aCP2(aEdge.getControlPointB());
+ cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(),
+ aEnd.getX(), aEnd.getY());
+ }
+ else
+ {
+ cairo_line_to(cr, aEnd.getX(), aEnd.getY());
+ }
+ }
+
+ cairo_close_path(cr);
+ }
+ }
+ }
+
+ if (SALCOLOR_NONE != mnFillColor)
+ {
+ cairo_set_source_rgba(cr, mnFillColor.GetRed() / 255.0, mnFillColor.GetGreen() / 255.0,
+ mnFillColor.GetBlue() / 255.0, 1.0 - fTransparency);
+ cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
+ cairo_fill_preserve(cr);
+ }
+
+ if (SALCOLOR_NONE != mnPenColor)
+ {
+ cairo_set_source_rgba(cr, mnPenColor.GetRed() / 255.0, mnPenColor.GetGreen() / 255.0,
+ mnPenColor.GetBlue() / 255.0, 1.0 - fTransparency);
+ cairo_stroke_preserve(cr);
+ }
+
+ X11Common::releaseCairoContext(cr);
+ return true;
+ }
+
+ return X11SalGraphicsImpl::drawPolyPolygon(rObjectToDevice, rPolyPolygon, fTransparency);
+}
+
+bool X11CairoSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& rPolygon,
+ double fTransparency, double fLineWidth,
+ const std::vector<double>* pStroke,
+ basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle, bool bPixelSnapHairline)
+{
+ if (0 == rPolygon.count())
+ {
+ return true;
+ }
+
+ if (fTransparency >= 1.0)
+ {
+ return true;
+ }
+
+ // disable by setting to something
+ static const char* pUseCairoForFatLines(getenv("SAL_DISABLE_USE_CAIRO_FOR_FATLINES"));
+
+ if (nullptr == pUseCairoForFatLines && mrX11Common.SupportsCairo())
+ {
+ cairo_t* cr = mrX11Common.getCairoContext();
+ clipRegion(cr);
+
+ // Use the now available static drawPolyLine from the Cairo-Headless-Fallback
+ // that will take care of all needed stuff
+ const bool bRetval(CairoCommon::drawPolyLine(
+ cr, nullptr, mnPenColor, getAntiAlias(), rObjectToDevice, rPolygon, fTransparency,
+ fLineWidth, pStroke, eLineJoin, eLineCap, fMiterMinimumAngle, bPixelSnapHairline));
+
+ X11Common::releaseCairoContext(cr);
+
+ if (bRetval)
+ {
+ return true;
+ }
+ }
+
+ return X11SalGraphicsImpl::drawPolyLine(rObjectToDevice, rPolygon, fTransparency, fLineWidth,
+ pStroke, eLineJoin, eLineCap, fMiterMinimumAngle,
+ bPixelSnapHairline);
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.hxx b/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.hxx
new file mode 100644
index 000000000..beb07e3ff
--- /dev/null
+++ b/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.hxx
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_features.h>
+#include <config_cairo_canvas.h>
+
+#if ENABLE_CAIRO_CANVAS
+
+#include <cairo-xlib.h>
+#include <unx/salgdi.h>
+#include "gdiimpl.hxx"
+#include "cairo_xlib_cairo.hxx"
+
+#include <headless/CairoCommon.hxx>
+
+class X11CairoSalGraphicsImpl : public X11SalGraphicsImpl
+{
+private:
+ X11Common& mrX11Common;
+ vcl::Region maClipRegion;
+ Color mnPenColor;
+ Color mnFillColor;
+
+ using X11SalGraphicsImpl::drawPolyPolygon;
+ using X11SalGraphicsImpl::drawPolyLine;
+
+public:
+ X11CairoSalGraphicsImpl(X11SalGraphics& rParent, X11Common& rX11Common);
+
+ void ResetClipRegion() override
+ {
+ maClipRegion.SetNull();
+ X11SalGraphicsImpl::ResetClipRegion();
+ }
+
+ bool setClipRegion(const vcl::Region& i_rClip) override
+ {
+ maClipRegion = i_rClip;
+ return X11SalGraphicsImpl::setClipRegion(i_rClip);
+ }
+
+ void SetLineColor() override
+ {
+ mnPenColor = SALCOLOR_NONE;
+ X11SalGraphicsImpl::SetLineColor();
+ }
+
+ void SetLineColor(Color nColor) override
+ {
+ mnPenColor = nColor;
+ X11SalGraphicsImpl::SetLineColor(nColor);
+ }
+
+ void SetFillColor() override
+ {
+ mnFillColor = SALCOLOR_NONE;
+ X11SalGraphicsImpl::SetFillColor();
+ }
+
+ void SetFillColor(Color nColor) override
+ {
+ mnFillColor = nColor;
+ X11SalGraphicsImpl::SetFillColor(nColor);
+ }
+
+ void clipRegion(cairo_t* cr) { CairoCommon::clipRegion(cr, maClipRegion); }
+
+ bool drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency) override;
+
+ bool drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& rPolygon, double fTransparency, double fLineWidth,
+ const std::vector<double>* pStroke, basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
+ bool bPixelSnapHairline) override;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/cairo_xlib_cairo.cxx b/vcl/unx/generic/gdi/cairo_xlib_cairo.cxx
new file mode 100644
index 000000000..17b34442b
--- /dev/null
+++ b/vcl/unx/generic/gdi/cairo_xlib_cairo.cxx
@@ -0,0 +1,307 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrender.h>
+
+#include "cairo_xlib_cairo.hxx"
+
+#include <vcl/sysdata.hxx>
+#include <vcl/bitmap.hxx>
+#include <vcl/virdev.hxx>
+#include <sal/log.hxx>
+
+#include <cairo-xlib.h>
+#include <cairo-xlib-xrender.h>
+
+namespace
+{
+ Pixmap limitXCreatePixmap(Display *display, Drawable d, unsigned int width, unsigned int height, unsigned int depth)
+ {
+ // The X protocol request CreatePixmap puts an upper bound
+ // of 16 bit to the size. And in practice some drivers
+ // fall over with values close to the max.
+
+ // see, e.g. moz#424333, fdo#48961, rhbz#1086714
+ // we've a duplicate of this in vcl :-(
+ if (width > SAL_MAX_INT16-10 || height > SAL_MAX_INT16-10)
+ {
+ SAL_WARN("canvas", "overlarge pixmap: " << width << " x " << height);
+ return None;
+ }
+ return XCreatePixmap(display, d, width, height, depth);
+ }
+}
+
+namespace cairo
+{
+
+ X11SysData::X11SysData() :
+ pDisplay(nullptr),
+ hDrawable(0),
+ pVisual(nullptr),
+ nScreen(0),
+ pRenderFormat(nullptr)
+ {}
+
+ X11SysData::X11SysData( const SystemGraphicsData& pSysDat ) :
+ pDisplay(pSysDat.pDisplay),
+ hDrawable(pSysDat.hDrawable),
+ pVisual(pSysDat.pVisual),
+ nScreen(pSysDat.nScreen),
+ pRenderFormat(pSysDat.pXRenderFormat)
+ {}
+
+ X11SysData::X11SysData( const SystemEnvData& pSysDat, const SalFrame* pReference ) :
+ pDisplay(pSysDat.pDisplay),
+ hDrawable(pSysDat.GetWindowHandle(pReference)),
+ pVisual(pSysDat.pVisual),
+ nScreen(pSysDat.nScreen),
+ pRenderFormat(nullptr)
+ {}
+
+ X11Pixmap::~X11Pixmap()
+ {
+ if( mpDisplay && mhDrawable )
+ XFreePixmap( static_cast<Display*>(mpDisplay), mhDrawable );
+ }
+
+ /**
+ * Surface::Surface: Create Canvas surface with existing data
+ * @param pSysData Platform native system environment data (struct SystemEnvData in vcl/inc/sysdata.hxx)
+ * @param pSurface Cairo surface
+ *
+ * pSysData contains the platform native Drawable reference
+ * This constructor only stores data, it does no processing.
+ * It is used by e.g. Surface::getSimilar()
+ *
+ * Set the mpSurface as pSurface
+ **/
+ X11Surface::X11Surface( const X11SysData& rSysData,
+ const X11PixmapSharedPtr& rPixmap,
+ const CairoSurfaceSharedPtr& pSurface ) :
+ maSysData(rSysData),
+ mpPixmap(rPixmap),
+ mpSurface(pSurface)
+ {}
+
+ /**
+ * Surface::Surface: Create generic Canvas surface using given Cairo Surface
+ *
+ * @param pSurface Cairo Surface
+ *
+ * This constructor only stores data, it does no processing.
+ * It is used with e.g. cairo_image_surface_create_for_data()
+ * Unlike other constructors, mpSysData is set to NULL
+ *
+ * Set the mpSurface as pSurface
+ **/
+ X11Surface::X11Surface( const CairoSurfaceSharedPtr& pSurface ) :
+ maSysData(),
+ mpSurface(pSurface)
+ {}
+
+ /**
+ * Surface::Surface: Create Canvas surface from Window reference.
+ * @param pSysData Platform native system environment data (struct SystemEnvData in vcl/inc/sysdata.hxx)
+ * @param x horizontal location of the new surface
+ * @param y vertical location of the new surface
+ * @param width width of the new surface
+ * @param height height of the new surface
+ *
+ * pSysData contains the platform native Window reference.
+ *
+ * pSysData is used to create a surface on the Window
+ *
+ * Set the mpSurface to the new surface or NULL
+ **/
+ X11Surface::X11Surface( const X11SysData& rSysData, int x, int y, int width, int height ) :
+ maSysData(rSysData),
+ mpSurface(
+ cairo_xlib_surface_create( static_cast<Display*>(rSysData.pDisplay),
+ rSysData.hDrawable,
+ static_cast<Visual*>(rSysData.pVisual),
+ width + x, height + y ),
+ &cairo_surface_destroy)
+ {
+ cairo_surface_set_device_offset(mpSurface.get(), x, y );
+ }
+
+ /**
+ * Surface::Surface: Create platform native Canvas surface from BitmapSystemData
+ * @param pSysData Platform native system environment data (struct SystemEnvData in vcl/inc/sysdata.hxx)
+ * @param pBmpData Platform native image data (struct BitmapSystemData in vcl/inc/bitmap.hxx)
+ * @param width width of the new surface
+ * @param height height of the new surface
+ *
+ * The pBmpData provides the imagedata that the created surface should contain.
+ *
+ * Set the mpSurface to the new surface or NULL
+ **/
+ X11Surface::X11Surface( const X11SysData& rSysData,
+ const BitmapSystemData& rData ) :
+ maSysData( rSysData ),
+ mpSurface(
+ cairo_xlib_surface_create( static_cast<Display*>(rSysData.pDisplay),
+ reinterpret_cast<Drawable>(rData.aPixmap),
+ static_cast<Visual*>(rSysData.pVisual),
+ rData.mnWidth, rData.mnHeight ),
+ &cairo_surface_destroy)
+ {
+ }
+
+ /**
+ * Surface::getCairo: Create Cairo (drawing object) for the Canvas surface
+ *
+ * @return new Cairo or NULL
+ **/
+ CairoSharedPtr X11Surface::getCairo() const
+ {
+ return CairoSharedPtr( cairo_create(mpSurface.get()),
+ &cairo_destroy );
+ }
+
+ /**
+ * Surface::getSimilar: Create new similar Canvas surface
+ * @param cairo_content_type format of the new surface (cairo_content_t from cairo/src/cairo.h)
+ * @param width width of the new surface
+ * @param height height of the new surface
+ *
+ * Creates a new Canvas surface. This normally creates platform native surface, even though
+ * generic function is used.
+ *
+ * Cairo surface from cairo_content_type (cairo_content_t)
+ *
+ * @return new surface or NULL
+ **/
+ SurfaceSharedPtr X11Surface::getSimilar(int cairo_content_type, int width, int height ) const
+ {
+ if( maSysData.pDisplay && maSysData.hDrawable )
+ {
+ XRenderPictFormat* pFormat;
+ int nFormat;
+
+ switch (cairo_content_type)
+ {
+ case CAIRO_CONTENT_ALPHA:
+ nFormat = PictStandardA8;
+ break;
+ case CAIRO_CONTENT_COLOR:
+ nFormat = PictStandardRGB24;
+ break;
+ case CAIRO_CONTENT_COLOR_ALPHA:
+ default:
+ nFormat = PictStandardARGB32;
+ break;
+ }
+
+ pFormat = XRenderFindStandardFormat( static_cast<Display*>(maSysData.pDisplay), nFormat );
+ Pixmap hPixmap = limitXCreatePixmap( static_cast<Display*>(maSysData.pDisplay), maSysData.hDrawable,
+ width > 0 ? width : 1, height > 0 ? height : 1,
+ pFormat->depth );
+
+ X11SysData aSysData(maSysData);
+ aSysData.pRenderFormat = pFormat;
+ return SurfaceSharedPtr(
+ new X11Surface( aSysData,
+ std::make_shared<X11Pixmap>(hPixmap, maSysData.pDisplay),
+ CairoSurfaceSharedPtr(
+ cairo_xlib_surface_create_with_xrender_format(
+ static_cast<Display*>(maSysData.pDisplay),
+ hPixmap,
+ ScreenOfDisplay(static_cast<Display *>(maSysData.pDisplay), maSysData.nScreen),
+ pFormat, width, height ),
+ &cairo_surface_destroy) ));
+ }
+ else
+ return SurfaceSharedPtr(
+ new X11Surface( maSysData,
+ X11PixmapSharedPtr(),
+ CairoSurfaceSharedPtr(
+ cairo_surface_create_similar( mpSurface.get(),
+ static_cast<cairo_content_t>(cairo_content_type), width, height ),
+ &cairo_surface_destroy )));
+ }
+
+ VclPtr<VirtualDevice> X11Surface::createVirtualDevice() const
+ {
+ SystemGraphicsData aSystemGraphicsData;
+
+ cairo_surface_t* pSurface = mpSurface.get();
+
+ aSystemGraphicsData.nSize = sizeof(SystemGraphicsData);
+ aSystemGraphicsData.hDrawable = mpPixmap ? mpPixmap->mhDrawable : maSysData.hDrawable;
+ aSystemGraphicsData.pXRenderFormat = maSysData.pRenderFormat;
+ aSystemGraphicsData.pSurface = pSurface;
+
+ int width = cairo_xlib_surface_get_width(pSurface);
+ int height = cairo_xlib_surface_get_height(pSurface);
+
+ return VclPtr<VirtualDevice>::Create(aSystemGraphicsData,
+ Size(width, height),
+ getFormat());
+ }
+
+ /**
+ * Surface::Resize: Resizes the Canvas surface.
+ * @param width new width of the surface
+ * @param height new height of the surface
+ *
+ * Only used on X11.
+ *
+ * @return The new surface or NULL
+ **/
+ bool X11Surface::Resize(int width, int height)
+ {
+ cairo_xlib_surface_set_size(mpSurface.get(), width, height);
+ return true;
+ }
+
+ void X11Surface::flush() const
+ {
+ XSync( static_cast<Display*>(maSysData.pDisplay), false );
+ }
+
+ /**
+ * Surface::getDepth: Get the color depth of the Canvas surface.
+ *
+ * @return color depth
+ **/
+ int X11Surface::getDepth() const
+ {
+ if (maSysData.pRenderFormat)
+ return static_cast<XRenderPictFormat*>(maSysData.pRenderFormat)->depth;
+ return -1;
+ }
+
+ /**
+ * Surface::getFormat: Get the device format of the Canvas surface.
+ *
+ * @return color format
+ **/
+ DeviceFormat X11Surface::getFormat() const
+ {
+ if (!maSysData.pRenderFormat)
+ return DeviceFormat::DEFAULT;
+ assert (static_cast<XRenderPictFormat*>(maSysData.pRenderFormat)->depth != 1 && "unsupported");
+ return DeviceFormat::DEFAULT;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/cairo_xlib_cairo.hxx b/vcl/unx/generic/gdi/cairo_xlib_cairo.hxx
new file mode 100644
index 000000000..c44fde437
--- /dev/null
+++ b/vcl/unx/generic/gdi/cairo_xlib_cairo.hxx
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+#include <vcl/cairo.hxx>
+#include <vcl/salgtype.hxx>
+
+struct BitmapSystemData;
+class SalFrame;
+struct SystemEnvData;
+struct SystemGraphicsData;
+
+namespace cairo {
+
+ /// Holds all X11-output relevant data
+ struct X11SysData
+ {
+ X11SysData();
+ explicit X11SysData( const SystemGraphicsData& );
+ explicit X11SysData( const SystemEnvData&, const SalFrame* pReference );
+
+ void* pDisplay; // the relevant display connection
+ Drawable hDrawable; // a drawable
+ void* pVisual; // the visual in use
+ int nScreen; // the current screen of the drawable
+ void* pRenderFormat; // render format for drawable
+ };
+
+ /// RAII wrapper for a pixmap
+ struct X11Pixmap
+ {
+ void* mpDisplay; // the relevant display connection
+ Pixmap mhDrawable; // a drawable
+
+ X11Pixmap( Pixmap hDrawable, void* pDisplay ) :
+ mpDisplay(pDisplay),
+ mhDrawable(hDrawable)
+ {}
+
+ ~X11Pixmap();
+ };
+
+ typedef std::shared_ptr<X11Pixmap> X11PixmapSharedPtr;
+
+ class X11Surface : public Surface
+ {
+ const X11SysData maSysData;
+ X11PixmapSharedPtr mpPixmap;
+ CairoSurfaceSharedPtr mpSurface;
+
+ X11Surface( const X11SysData& rSysData, const X11PixmapSharedPtr& rPixmap, const CairoSurfaceSharedPtr& pSurface );
+
+ public:
+ /// takes over ownership of passed cairo_surface
+ explicit X11Surface( const CairoSurfaceSharedPtr& pSurface );
+ /// create surface on subarea of given drawable
+ X11Surface( const X11SysData& rSysData, int x, int y, int width, int height );
+ /// create surface for given bitmap data
+ X11Surface( const X11SysData& rSysData, const BitmapSystemData& rBmpData );
+
+ // Surface interface
+ virtual CairoSharedPtr getCairo() const override;
+ virtual CairoSurfaceSharedPtr getCairoSurface() const override { return mpSurface; }
+ virtual SurfaceSharedPtr getSimilar(int cairo_content_type, int width, int height) const override;
+
+ virtual VclPtr<VirtualDevice> createVirtualDevice() const override;
+
+ virtual bool Resize( int width, int height ) override;
+
+ virtual void flush() const override;
+
+ int getDepth() const;
+ DeviceFormat getFormat() const;
+ const X11PixmapSharedPtr& getPixmap() const { return mpPixmap; }
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/cairotextrender.cxx b/vcl/unx/generic/gdi/cairotextrender.cxx
new file mode 100644
index 000000000..898110fee
--- /dev/null
+++ b/vcl/unx/generic/gdi/cairotextrender.cxx
@@ -0,0 +1,382 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <unx/cairotextrender.hxx>
+
+#include <unx/fc_fontoptions.hxx>
+#include <unx/freetype_glyphcache.hxx>
+#include <vcl/svapp.hxx>
+#include <sallayout.hxx>
+#include <salinst.hxx>
+
+#include <cairo.h>
+#include <cairo-ft.h>
+#if defined(CAIRO_HAS_SVG_SURFACE)
+#include <cairo-svg.h>
+#elif defined(CAIRO_HAS_PDF_SURFACE)
+#include <cairo-pdf.h>
+#endif
+
+#include <deque>
+
+namespace {
+
+typedef struct FT_FaceRec_* FT_Face;
+
+class CairoFontsCache
+{
+public:
+ struct CacheId
+ {
+ FT_Face maFace;
+ const FontConfigFontOptions *mpOptions;
+ bool mbEmbolden;
+ bool mbVerticalMetrics;
+ bool operator ==(const CacheId& rOther) const
+ {
+ return maFace == rOther.maFace &&
+ mpOptions == rOther.mpOptions &&
+ mbEmbolden == rOther.mbEmbolden &&
+ mbVerticalMetrics == rOther.mbVerticalMetrics;
+ }
+ };
+
+private:
+ typedef std::deque< std::pair<cairo_font_face_t*, CacheId> > LRUFonts;
+ static LRUFonts maLRUFonts;
+public:
+ CairoFontsCache() = delete;
+
+ static void CacheFont(cairo_font_face_t* pFont, const CacheId &rId);
+ static cairo_font_face_t* FindCachedFont(const CacheId &rId);
+};
+
+CairoFontsCache::LRUFonts CairoFontsCache::maLRUFonts;
+
+void CairoFontsCache::CacheFont(cairo_font_face_t* pFont, const CairoFontsCache::CacheId &rId)
+{
+ maLRUFonts.push_front( std::pair<cairo_font_face_t*, CairoFontsCache::CacheId>(pFont, rId) );
+ if (maLRUFonts.size() > 8)
+ {
+ cairo_font_face_destroy(maLRUFonts.back().first);
+ maLRUFonts.pop_back();
+ }
+}
+
+cairo_font_face_t* CairoFontsCache::FindCachedFont(const CairoFontsCache::CacheId &rId)
+{
+ auto aI = std::find_if(maLRUFonts.begin(), maLRUFonts.end(),
+ [&rId](const LRUFonts::value_type& rFont) { return rFont.second == rId; });
+ if (aI != maLRUFonts.end())
+ return aI->first;
+ return nullptr;
+}
+
+}
+
+namespace
+{
+ bool hasRotation(int nRotation)
+ {
+ return nRotation != 0;
+ }
+
+ double toRadian(Degree10 nDegree10th)
+ {
+ return toRadians(3600_deg10 - nDegree10th);
+ }
+
+ cairo_t* syncCairoContext(cairo_t* cr)
+ {
+ //rhbz#1283420 tdf#117413 bodge to force a read from the underlying surface which has
+ //the side effect of making the mysterious xrender related problem go away
+ cairo_surface_t *target = cairo_get_target(cr);
+ if (cairo_surface_get_type(target) == CAIRO_SURFACE_TYPE_XLIB)
+ {
+ cairo_surface_t *throw_away = cairo_surface_create_similar(target, cairo_surface_get_content(target), 1, 1);
+ cairo_t *force_read_cr = cairo_create(throw_away);
+ cairo_set_source_surface(force_read_cr, target, 0, 0);
+ cairo_paint(force_read_cr);
+ cairo_destroy(force_read_cr);
+ cairo_surface_destroy(throw_away);
+ }
+ return cr;
+ }
+}
+
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+extern "C"
+{
+ __attribute__((weak)) void __lsan_disable();
+ __attribute__((weak)) void __lsan_enable();
+}
+#endif
+
+CairoTextRender::CairoTextRender()
+{
+ // https://gitlab.freedesktop.org/cairo/cairo/-/merge_requests/235
+ // I don't want to have CAIRO_ROUND_GLYPH_POS_ON set in the cairo surfaces
+ // font_options when trying subpixel rendering, but that's a private
+ // feature of cairo_font_options_t, so tricky to achieve. Hack this by
+ // getting the font options of a backend known to set this private feature
+ // to CAIRO_ROUND_GLYPH_POS_OFF and then set to defaults the public
+ // features and the result can be merged with new font options to set
+ // CAIRO_ROUND_GLYPH_POS_OFF in those
+ mpRoundGlyphPosOffOptions = cairo_font_options_create();
+#if defined(CAIRO_HAS_SVG_SURFACE)
+ // svg, pdf and ps backends have CAIRO_ROUND_GLYPH_POS_OFF by default
+ cairo_surface_t* hack = cairo_svg_surface_create(nullptr, 1, 1);
+#elif defined(CAIRO_HAS_PDF_SURFACE)
+ cairo_surface_t* hack = cairo_pdf_surface_create(nullptr, 1, 1);
+#endif
+ cairo_surface_get_font_options(hack, mpRoundGlyphPosOffOptions);
+ cairo_surface_destroy(hack);
+ cairo_font_options_set_antialias(mpRoundGlyphPosOffOptions, CAIRO_ANTIALIAS_DEFAULT);
+ cairo_font_options_set_subpixel_order(mpRoundGlyphPosOffOptions, CAIRO_SUBPIXEL_ORDER_DEFAULT);
+ cairo_font_options_set_hint_style(mpRoundGlyphPosOffOptions, CAIRO_HINT_STYLE_DEFAULT);
+ cairo_font_options_set_hint_metrics(mpRoundGlyphPosOffOptions, CAIRO_HINT_METRICS_DEFAULT);
+}
+
+CairoTextRender::~CairoTextRender()
+{
+ cairo_font_options_destroy(mpRoundGlyphPosOffOptions);
+}
+
+void CairoTextRender::DrawTextLayout(const GenericSalLayout& rLayout, const SalGraphics& rGraphics)
+{
+ const FreetypeFontInstance& rInstance = static_cast<FreetypeFontInstance&>(rLayout.GetFont());
+ const FreetypeFont& rFont = rInstance.GetFreetypeFont();
+
+ const bool bResolutionIndependentLayoutEnabled = rGraphics.getTextRenderModeForResolutionIndependentLayoutEnabled();
+
+ std::vector<cairo_glyph_t> cairo_glyphs;
+ std::vector<int> glyph_extrarotation;
+ cairo_glyphs.reserve( 256 );
+
+ DevicePoint aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ cairo_glyph_t aGlyph;
+ aGlyph.index = pGlyph->glyphId();
+ aGlyph.x = aPos.getX();
+ aGlyph.y = aPos.getY();
+
+ const bool bVertical = pGlyph->IsVertical();
+ glyph_extrarotation.push_back(bVertical ? 1 : 0);
+
+ // tdf#150507 like skia even when subpixel rendering pixel snap y
+ if (bResolutionIndependentLayoutEnabled)
+ {
+ if (!bVertical)
+ aGlyph.y = std::floor(aGlyph.y + 0.5);
+ else
+ aGlyph.x = std::floor(aGlyph.x + 0.5);
+ }
+
+ cairo_glyphs.push_back(aGlyph);
+ }
+
+ if (cairo_glyphs.empty())
+ return;
+
+ const vcl::font::FontSelectPattern& rFSD = rInstance.GetFontSelectPattern();
+ int nHeight = rFSD.mnHeight;
+ int nWidth = rFSD.mnWidth ? rFSD.mnWidth : nHeight;
+ if (nWidth == 0 || nHeight == 0)
+ return;
+
+ if (nHeight > SAL_MAX_UINT16)
+ {
+ // as seen with freetype 2.11.0, so cairo surface status is "fail"
+ // ("error occurred in libfreetype") and no further operations are
+ // executed, so this error then leads to later leaks
+ SAL_WARN("vcl", "rendering text would fail with height: " << nHeight);
+ return;
+ }
+
+ int nRatio = nWidth * 10 / nHeight;
+ if (FreetypeFont::AlmostHorizontalDrainsRenderingPool(nRatio, rFSD))
+ return;
+
+ /*
+ * It might be ideal to cache surface and cairo context between calls and
+ * only destroy it when the drawable changes, but to do that we need to at
+ * least change the SalFrame etc impls to dtor the SalGraphics *before* the
+ * destruction of the windows they reference
+ */
+ cairo_t *cr = syncCairoContext(getCairoContext());
+ if (!cr)
+ {
+ SAL_WARN("vcl", "no cairo context for text");
+ return;
+ }
+
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+ if (__lsan_disable)
+ __lsan_disable();
+#endif
+
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ const bool bDisableAA = !rStyleSettings.GetUseFontAAFromSystem() && !rGraphics.getAntiAlias();
+
+ const cairo_font_options_t* pFontOptions = GetSalInstance()->GetCairoFontOptions();
+ if (pFontOptions || bDisableAA || bResolutionIndependentLayoutEnabled)
+ {
+ cairo_hint_style_t eHintStyle = pFontOptions ? cairo_font_options_get_hint_style(pFontOptions) : CAIRO_HINT_STYLE_DEFAULT;
+ bool bAllowedHintStyle = !bResolutionIndependentLayoutEnabled || (eHintStyle == CAIRO_HINT_STYLE_NONE || eHintStyle == CAIRO_HINT_STYLE_SLIGHT);
+
+ if (bDisableAA || !bAllowedHintStyle || bResolutionIndependentLayoutEnabled)
+ {
+ // Disable font AA in case global AA setting is supposed to affect
+ // font rendering (not the default) and AA is disabled.
+ cairo_font_options_t* pOptions = pFontOptions ? cairo_font_options_copy(pFontOptions) : cairo_font_options_create();
+
+ if (bDisableAA)
+ cairo_font_options_set_antialias(pOptions, CAIRO_ANTIALIAS_NONE);
+ if (!bAllowedHintStyle)
+ cairo_font_options_set_hint_style(pOptions, CAIRO_HINT_STYLE_SLIGHT);
+ // Disable private CAIRO_ROUND_GLYPH_POS_ON by merging with font options known to have
+ // CAIRO_ROUND_GLYPH_POS_OFF
+ if (bResolutionIndependentLayoutEnabled)
+ cairo_font_options_merge(pOptions, mpRoundGlyphPosOffOptions);
+ cairo_set_font_options(cr, pOptions);
+ cairo_font_options_destroy(pOptions);
+ }
+ else if (pFontOptions)
+ cairo_set_font_options(cr, pFontOptions);
+ }
+
+ double nDX, nDY;
+ getSurfaceOffset(nDX, nDY);
+ cairo_translate(cr, nDX, nDY);
+
+ clipRegion(cr);
+
+ cairo_set_source_rgb(cr,
+ mnTextColor.GetRed()/255.0,
+ mnTextColor.GetGreen()/255.0,
+ mnTextColor.GetBlue()/255.0);
+
+ FT_Face aFace = rFont.GetFtFace();
+ CairoFontsCache::CacheId aId;
+ aId.maFace = aFace;
+ aId.mpOptions = rFont.GetFontOptions();
+ aId.mbEmbolden = rFont.NeedsArtificialBold();
+
+ cairo_matrix_t m;
+
+ std::vector<int>::const_iterator aEnd = glyph_extrarotation.end();
+ std::vector<int>::const_iterator aStart = glyph_extrarotation.begin();
+ std::vector<int>::const_iterator aI = aStart;
+ while (aI != aEnd)
+ {
+ int nGlyphRotation = *aI;
+
+ std::vector<int>::const_iterator aNext = nGlyphRotation?(aI+1):std::find_if(aI+1, aEnd, hasRotation);
+
+ size_t nStartIndex = std::distance(aStart, aI);
+ size_t nLen = std::distance(aI, aNext);
+
+ aId.mbVerticalMetrics = nGlyphRotation != 0.0;
+ cairo_font_face_t* font_face = CairoFontsCache::FindCachedFont(aId);
+ if (!font_face)
+ {
+ const FontConfigFontOptions *pOptions = aId.mpOptions;
+ FcPattern *pPattern = pOptions->GetPattern();
+ font_face = cairo_ft_font_face_create_for_pattern(pPattern);
+ CairoFontsCache::CacheFont(font_face, aId);
+ }
+ cairo_set_font_face(cr, font_face);
+
+ cairo_set_font_size(cr, nHeight);
+
+ cairo_matrix_init_identity(&m);
+
+ if (rLayout.GetOrientation())
+ cairo_matrix_rotate(&m, toRadian(rLayout.GetOrientation()));
+
+ cairo_matrix_scale(&m, nWidth, nHeight);
+
+ if (nGlyphRotation)
+ {
+ cairo_matrix_rotate(&m, toRadian(Degree10(nGlyphRotation * 900)));
+
+ cairo_matrix_t em_square;
+ cairo_matrix_init_identity(&em_square);
+ cairo_get_matrix(cr, &em_square);
+
+ cairo_matrix_scale(&em_square, aFace->units_per_EM,
+ aFace->units_per_EM);
+ cairo_set_matrix(cr, &em_square);
+
+ cairo_font_extents_t font_extents;
+ cairo_font_extents(cr, &font_extents);
+
+ cairo_matrix_init_identity(&em_square);
+ cairo_set_matrix(cr, &em_square);
+ }
+
+ if (rFont.NeedsArtificialItalic())
+ {
+ cairo_matrix_t shear;
+ cairo_matrix_init_identity(&shear);
+ shear.xy = -shear.xx * 0x6000L / 0x10000L;
+ cairo_matrix_multiply(&m, &shear, &m);
+ }
+
+ cairo_set_font_matrix(cr, &m);
+ cairo_show_glyphs(cr, &cairo_glyphs[nStartIndex], nLen);
+ if (cairo_status(cr) != CAIRO_STATUS_SUCCESS)
+ {
+ SAL_WARN("vcl", "rendering text failed with stretch ratio of: " << nRatio << ", " << cairo_status_to_string(cairo_status(cr)));
+ }
+
+#if OSL_DEBUG_LEVEL > 2
+ //draw origin
+ cairo_save (cr);
+ cairo_rectangle (cr, cairo_glyphs[nStartIndex].x, cairo_glyphs[nStartIndex].y, 5, 5);
+ cairo_set_source_rgba (cr, 1, 0, 0, 0.80);
+ cairo_fill (cr);
+ cairo_restore (cr);
+#endif
+
+ aI = aNext;
+ }
+
+ releaseCairoContext(cr);
+
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+ if (__lsan_enable)
+ __lsan_enable();
+#endif
+}
+
+void FontConfigFontOptions::cairo_font_options_substitute(FcPattern* pPattern)
+{
+ const cairo_font_options_t* pFontOptions = GetSalInstance()->GetCairoFontOptions();
+ if( !pFontOptions )
+ return;
+ cairo_ft_font_options_substitute(pFontOptions, pPattern);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/font.cxx b/vcl/unx/generic/gdi/font.cxx
new file mode 100644
index 000000000..57ff99a1c
--- /dev/null
+++ b/vcl/unx/generic/gdi/font.cxx
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <vcl/sysdata.hxx>
+#include <vcl/fontcharmap.hxx>
+
+#include <unx/salgdi.h>
+#include <textrender.hxx>
+#include <sallayout.hxx>
+
+void X11SalGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+ mxTextRenderImpl->DrawTextLayout(rLayout, *this);
+}
+
+FontCharMapRef X11SalGraphics::GetFontCharMap() const
+{
+ return mxTextRenderImpl->GetFontCharMap();
+}
+
+bool X11SalGraphics::GetFontCapabilities(vcl::FontCapabilities &rGetImplFontCapabilities) const
+{
+ return mxTextRenderImpl->GetFontCapabilities(rGetImplFontCapabilities);
+}
+
+// SalGraphics
+void X11SalGraphics::SetFont(LogicalFontInstance* pEntry, int nFallbackLevel)
+{
+ mxTextRenderImpl->SetFont(pEntry, nFallbackLevel);
+}
+
+void
+X11SalGraphics::SetTextColor( Color nColor )
+{
+ mxTextRenderImpl->SetTextColor(nColor);
+}
+
+bool X11SalGraphics::AddTempDevFont( vcl::font::PhysicalFontCollection* pFontCollection,
+ const OUString& rFileURL,
+ const OUString& rFontName )
+{
+ return mxTextRenderImpl->AddTempDevFont(pFontCollection, rFileURL, rFontName);
+}
+
+void X11SalGraphics::ClearDevFontCache()
+{
+ mxTextRenderImpl->ClearDevFontCache();
+}
+
+void X11SalGraphics::GetDevFontList( vcl::font::PhysicalFontCollection* pFontCollection )
+{
+ mxTextRenderImpl->GetDevFontList(pFontCollection);
+}
+
+void
+X11SalGraphics::GetFontMetric( ImplFontMetricDataRef &rxFontMetric, int nFallbackLevel )
+{
+ mxTextRenderImpl->GetFontMetric(rxFontMetric, nFallbackLevel);
+}
+
+std::unique_ptr<GenericSalLayout> X11SalGraphics::GetTextLayout(int nFallbackLevel)
+{
+ return mxTextRenderImpl->GetTextLayout(nFallbackLevel);
+}
+
+bool X11SalGraphics::CreateFontSubset(
+ const OUString& rToFile,
+ const vcl::font::PhysicalFontFace* pFont,
+ const sal_GlyphId* pGlyphIds,
+ const sal_uInt8* pEncoding,
+ sal_Int32* pWidths,
+ int nGlyphCount,
+ FontSubsetInfo& rInfo
+ )
+{
+ return mxTextRenderImpl->CreateFontSubset(rToFile, pFont,
+ pGlyphIds, pEncoding, pWidths, nGlyphCount, rInfo);
+}
+
+const void* X11SalGraphics::GetEmbedFontData(const vcl::font::PhysicalFontFace* pFont, tools::Long* pDataLen)
+{
+ return mxTextRenderImpl->GetEmbedFontData(pFont, pDataLen);
+}
+
+void X11SalGraphics::FreeEmbedFontData( const void* pData, tools::Long nLen )
+{
+ mxTextRenderImpl->FreeEmbedFontData(pData, nLen);
+}
+
+void X11SalGraphics::GetGlyphWidths( const vcl::font::PhysicalFontFace* pFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc )
+{
+ mxTextRenderImpl->GetGlyphWidths(pFont, bVertical, rWidths, rUnicodeEnc);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/freetypetextrender.cxx b/vcl/unx/generic/gdi/freetypetextrender.cxx
new file mode 100644
index 000000000..ea8474e69
--- /dev/null
+++ b/vcl/unx/generic/gdi/freetypetextrender.cxx
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <unx/freetypetextrender.hxx>
+
+#include <unotools/configmgr.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <sal/log.hxx>
+
+#include <unx/genpspgraphics.h>
+#include <unx/geninst.h>
+#include <unx/glyphcache.hxx>
+#include <unx/fc_fontoptions.hxx>
+#include <unx/freetype_glyphcache.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <impfontmetricdata.hxx>
+
+#include <sallayout.hxx>
+
+FreeTypeTextRenderImpl::FreeTypeTextRenderImpl()
+ : mnTextColor(Color(0x00, 0x00, 0x00)) //black
+{
+}
+
+FreeTypeTextRenderImpl::~FreeTypeTextRenderImpl()
+{
+ ReleaseFonts();
+}
+
+void FreeTypeTextRenderImpl::SetFont(LogicalFontInstance *pEntry, int nFallbackLevel)
+{
+ // release all no longer needed font resources
+ for( int i = nFallbackLevel; i < MAX_FALLBACK; ++i )
+ {
+ // old server side font is no longer referenced
+ mpFreetypeFont[i] = nullptr;
+ }
+
+ // return early if there is no new font
+ if( !pEntry )
+ return;
+
+ FreetypeFontInstance* pFreetypeFont = static_cast<FreetypeFontInstance*>(pEntry);
+ mpFreetypeFont[ nFallbackLevel ] = pFreetypeFont;
+
+ // ignore fonts with e.g. corrupted font files
+ if (!mpFreetypeFont[nFallbackLevel]->GetFreetypeFont().TestFont())
+ mpFreetypeFont[nFallbackLevel] = nullptr;
+}
+
+FontCharMapRef FreeTypeTextRenderImpl::GetFontCharMap() const
+{
+ if (!mpFreetypeFont[0])
+ return nullptr;
+ return mpFreetypeFont[0]->GetFreetypeFont().GetFontCharMap();
+}
+
+bool FreeTypeTextRenderImpl::GetFontCapabilities(vcl::FontCapabilities &rGetImplFontCapabilities) const
+{
+ if (!mpFreetypeFont[0])
+ return false;
+ return mpFreetypeFont[0]->GetFreetypeFont().GetFontCapabilities(rGetImplFontCapabilities);
+}
+
+// SalGraphics
+void
+FreeTypeTextRenderImpl::SetTextColor( Color nColor )
+{
+ if( mnTextColor != nColor )
+ {
+ mnTextColor = nColor;
+ }
+}
+
+bool FreeTypeTextRenderImpl::AddTempDevFont( vcl::font::PhysicalFontCollection* pFontCollection,
+ const OUString& rFileURL,
+ const OUString& rFontName )
+{
+ return GenPspGraphics::AddTempDevFontHelper(pFontCollection, rFileURL, rFontName);
+}
+
+void FreeTypeTextRenderImpl::ClearDevFontCache()
+{
+ FreetypeManager::get().ClearFontCache();
+}
+
+void FreeTypeTextRenderImpl::GetDevFontList( vcl::font::PhysicalFontCollection* pFontCollection )
+{
+ // prepare the FreetypeManager using psprint's font infos
+ FreetypeManager& rFreetypeManager = FreetypeManager::get();
+
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ ::std::vector< psp::fontID > aList;
+ psp::FastPrintFontInfo aInfo;
+ rMgr.getFontList( aList );
+ for (auto const& elem : aList)
+ {
+ if( !rMgr.getFontFastInfo( elem, aInfo ) )
+ continue;
+
+ // normalize face number to the FreetypeManager
+ int nFaceNum = rMgr.getFontFaceNumber( aInfo.m_nID );
+ int nVariantNum = rMgr.getFontFaceVariation( aInfo.m_nID );
+
+ // inform FreetypeManager about this font provided by the PsPrint subsystem
+ FontAttributes aDFA = GenPspGraphics::Info2FontAttributes( aInfo );
+ aDFA.IncreaseQualityBy( 4096 );
+ const OString& rFileName = rMgr.getFontFileSysPath( aInfo.m_nID );
+ rFreetypeManager.AddFontFile(rFileName, nFaceNum, nVariantNum, aInfo.m_nID, aDFA);
+ }
+
+ // announce glyphcache fonts
+ rFreetypeManager.AnnounceFonts(pFontCollection);
+
+ // register platform specific font substitutions if available
+ if (!utl::ConfigManager::IsFuzzing())
+ SalGenericInstance::RegisterFontSubstitutors( pFontCollection );
+}
+
+void FreeTypeTextRenderImpl::GetFontMetric( ImplFontMetricDataRef& rxFontMetric, int nFallbackLevel )
+{
+ if( nFallbackLevel >= MAX_FALLBACK )
+ return;
+
+ if (mpFreetypeFont[nFallbackLevel])
+ mpFreetypeFont[nFallbackLevel]->GetFreetypeFont().GetFontMetric(rxFontMetric);
+}
+
+std::unique_ptr<GenericSalLayout> FreeTypeTextRenderImpl::GetTextLayout(int nFallbackLevel)
+{
+ assert(mpFreetypeFont[nFallbackLevel]);
+ if (!mpFreetypeFont[nFallbackLevel])
+ return nullptr;
+ return std::make_unique<GenericSalLayout>(*mpFreetypeFont[nFallbackLevel]);
+}
+
+bool FreeTypeTextRenderImpl::CreateFontSubset(
+ const OUString& rToFile,
+ const vcl::font::PhysicalFontFace* pFont,
+ const sal_GlyphId* pGlyphIds,
+ const sal_uInt8* pEncoding,
+ sal_Int32* pWidths,
+ int nGlyphCount,
+ FontSubsetInfo& rInfo
+ )
+{
+ // in this context the pFont->GetFontId() is a valid PSP
+ // font since they are the only ones left after the PDF
+ // export has filtered its list of subsettable fonts (for
+ // which this method was created). The correct way would
+ // be to have the FreetypeManager search for the PhysicalFontFace pFont
+ psp::fontID aFont = pFont->GetFontId();
+
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ bool bSuccess = rMgr.createFontSubset( rInfo,
+ aFont,
+ rToFile,
+ pGlyphIds,
+ pEncoding,
+ pWidths,
+ nGlyphCount );
+ return bSuccess;
+}
+
+const void* FreeTypeTextRenderImpl::GetEmbedFontData(const vcl::font::PhysicalFontFace* pFont, tools::Long* pDataLen)
+{
+ // in this context the pFont->GetFontId() is a valid PSP
+ // font since they are the only ones left after the PDF
+ // export has filtered its list of subsettable fonts (for
+ // which this method was created). The correct way would
+ // be to have the FreetypeManager search for the PhysicalFontFace pFont
+ psp::fontID aFont = pFont->GetFontId();
+ return GenPspGraphics::DoGetEmbedFontData(aFont, pDataLen);
+}
+
+void FreeTypeTextRenderImpl::FreeEmbedFontData( const void* pData, tools::Long nLen )
+{
+ GenPspGraphics::DoFreeEmbedFontData( pData, nLen );
+}
+
+void FreeTypeTextRenderImpl::GetGlyphWidths( const vcl::font::PhysicalFontFace* pFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc )
+{
+ // in this context the pFont->GetFontId() is a valid PSP
+ // font since they are the only ones left after the PDF
+ // export has filtered its list of subsettable fonts (for
+ // which this method was created). The correct way would
+ // be to have the FreetypeManager search for the PhysicalFontFace pFont
+ psp::fontID aFont = pFont->GetFontId();
+ GenPspGraphics::DoGetGlyphWidths( aFont, bVertical, rWidths, rUnicodeEnc );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/gdiimpl.cxx b/vcl/unx/generic/gdi/gdiimpl.cxx
new file mode 100644
index 000000000..d5fc4d6c1
--- /dev/null
+++ b/vcl/unx/generic/gdi/gdiimpl.cxx
@@ -0,0 +1,2015 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <numeric>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/Xrender.h>
+#include <X11/Xproto.h>
+
+#include "gdiimpl.hxx"
+
+#include <vcl/gradient.hxx>
+#include <sal/log.hxx>
+
+#include <unx/saldisp.hxx>
+#include <unx/salbmp.h>
+#include <unx/salgdi.h>
+#include <unx/salvd.h>
+#include <unx/x11/xlimits.hxx>
+#include <salframe.hxx>
+#include <unx/x11/xrender_peer.hxx>
+
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolygonclipper.hxx>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dtrapezoid.hxx>
+#include <basegfx/utils/systemdependentdata.hxx>
+
+#undef SALGDI2_TESTTRANS
+
+#if (OSL_DEBUG_LEVEL > 1) && defined SALGDI2_TESTTRANS
+#define DBG_TESTTRANS( _def_drawable ) \
+{ \
+ XCopyArea( pXDisp, _def_drawable, aDrawable, GetCopyGC(), \
+ 0, 0, \
+ rPosAry.mnDestWidth, rPosAry.mnDestHeight, \
+ 0, 0 ); \
+}
+#else // (OSL_DEBUG_LEVEL > 1) && defined SALGDI2_TESTTRANS
+#define DBG_TESTTRANS( _def_drawable )
+#endif // (OSL_DEBUG_LEVEL > 1) && defined SALGDI2_TESTTRANS
+
+/* From <X11/Intrinsic.h> */
+typedef unsigned long Pixel;
+
+class SalPolyLine
+{
+ std::vector<XPoint> Points_;
+public:
+ SalPolyLine(sal_uLong nPoints, const Point *p)
+ : Points_(nPoints+1)
+ {
+ for (sal_uLong i = 0; i < nPoints; ++i)
+ {
+ Points_[i].x = static_cast<short>(p[i].getX());
+ Points_[i].y = static_cast<short>(p[i].getY());
+ }
+ Points_[nPoints] = Points_[0]; // close polyline
+ }
+
+ const XPoint &operator[](sal_uLong n) const
+ {
+ return Points_[n];
+ }
+
+ XPoint &operator[](sal_uLong n)
+ {
+ return Points_[n];
+ }
+};
+
+namespace
+{
+ void setForeBack(XGCValues& rValues, const SalColormap& rColMap, const SalBitmap& rSalBitmap)
+ {
+ rValues.foreground = rColMap.GetWhitePixel();
+ rValues.background = rColMap.GetBlackPixel();
+
+ //fdo#33455 and fdo#80160 handle 1 bit depth pngs with palette entries
+ //to set fore/back colors
+ SalBitmap& rBitmap = const_cast<SalBitmap&>(rSalBitmap);
+ BitmapBuffer* pBitmapBuffer = rBitmap.AcquireBuffer(BitmapAccessMode::Read);
+ if (!pBitmapBuffer)
+ return;
+
+ const BitmapPalette& rPalette = pBitmapBuffer->maPalette;
+ if (rPalette.GetEntryCount() == 2)
+ {
+ const BitmapColor aWhite(rPalette[rPalette.GetBestIndex(COL_WHITE)]);
+ rValues.foreground = rColMap.GetPixel(aWhite);
+
+ const BitmapColor aBlack(rPalette[rPalette.GetBestIndex(COL_BLACK)]);
+ rValues.background = rColMap.GetPixel(aBlack);
+ }
+ rBitmap.ReleaseBuffer(pBitmapBuffer, BitmapAccessMode::Read);
+ }
+}
+
+X11SalGraphicsImpl::X11SalGraphicsImpl(X11SalGraphics& rParent):
+ mrParent(rParent),
+ mnBrushColor( 0xFF, 0xFF, 0XFF ),
+ mpBrushGC(nullptr),
+ mnBrushPixel(0),
+ mbPenGC(false),
+ mbBrushGC(false),
+ mbCopyGC(false),
+ mbInvertGC(false),
+ mbInvert50GC(false),
+ mbStippleGC(false),
+ mbTrackingGC(false),
+ mbDitherBrush(false),
+ mbXORMode(false),
+ mpPenGC(nullptr),
+ mnPenColor( 0x00, 0x00, 0x00 ),
+ mnPenPixel(0),
+ mpMonoGC(nullptr),
+ mpCopyGC(nullptr),
+ mpMaskGC(nullptr),
+ mpInvertGC(nullptr),
+ mpInvert50GC(nullptr),
+ mpStippleGC(nullptr),
+ mpTrackingGC(nullptr)
+{
+}
+
+X11SalGraphicsImpl::~X11SalGraphicsImpl()
+{
+}
+
+void X11SalGraphicsImpl::Init()
+{
+ mnPenPixel = mrParent.GetPixel( mnPenColor );
+ mnBrushPixel = mrParent.GetPixel( mnBrushColor );
+}
+
+XID X11SalGraphicsImpl::GetXRenderPicture()
+{
+ XRenderPeer& rRenderPeer = XRenderPeer::GetInstance();
+
+ if( !mrParent.m_aXRenderPicture )
+ {
+ // check xrender support for matching visual
+ XRenderPictFormat* pXRenderFormat = mrParent.GetXRenderFormat();
+ if( !pXRenderFormat )
+ return 0;
+ // get the matching xrender target for drawable
+ mrParent.m_aXRenderPicture = rRenderPeer.CreatePicture( mrParent.GetDrawable(), pXRenderFormat, 0, nullptr );
+ }
+
+ {
+ // reset clip region
+ // TODO: avoid clip reset if already done
+ XRenderPictureAttributes aAttr;
+ aAttr.clip_mask = None;
+ rRenderPeer.ChangePicture( mrParent.m_aXRenderPicture, CPClipMask, &aAttr );
+ }
+
+ return mrParent.m_aXRenderPicture;
+}
+
+static void freeGC(Display *pDisplay, GC& rGC)
+{
+ if( rGC )
+ {
+ XFreeGC( pDisplay, rGC );
+ rGC = None;
+ }
+}
+
+void X11SalGraphicsImpl::freeResources()
+{
+ Display *pDisplay = mrParent.GetXDisplay();
+
+ freeGC( pDisplay, mpPenGC );
+ freeGC( pDisplay, mpBrushGC );
+ freeGC( pDisplay, mpMonoGC );
+ freeGC( pDisplay, mpTrackingGC );
+ freeGC( pDisplay, mpCopyGC );
+ freeGC( pDisplay, mpMaskGC );
+ freeGC( pDisplay, mpInvertGC );
+ freeGC( pDisplay, mpInvert50GC );
+ freeGC( pDisplay, mpStippleGC );
+ mbTrackingGC = mbPenGC = mbBrushGC = mbCopyGC = mbInvertGC = mbInvert50GC = mbStippleGC = false;
+}
+
+GC X11SalGraphicsImpl::CreateGC( Drawable hDrawable, unsigned long nMask )
+{
+ XGCValues values;
+
+ values.graphics_exposures = False;
+ values.foreground = mrParent.GetColormap().GetBlackPixel()
+ ^ mrParent.GetColormap().GetWhitePixel();
+ values.function = GXxor;
+ values.line_width = 1;
+ values.fill_style = FillStippled;
+ values.stipple = mrParent.GetDisplay()->GetInvert50( mrParent.m_nXScreen );
+ values.subwindow_mode = ClipByChildren;
+
+ return XCreateGC( mrParent.GetXDisplay(), hDrawable, nMask | GCSubwindowMode, &values );
+}
+
+inline GC X11SalGraphicsImpl::GetCopyGC()
+{
+ if( mbXORMode ) return GetInvertGC();
+
+ if( !mpCopyGC )
+ mpCopyGC = CreateGC( mrParent.GetDrawable() );
+
+ if( !mbCopyGC )
+ {
+ mrParent.SetClipRegion( mpCopyGC );
+ mbCopyGC = true;
+ }
+ return mpCopyGC;
+}
+
+GC X11SalGraphicsImpl::GetTrackingGC()
+{
+ if( !mpTrackingGC )
+ {
+ XGCValues values;
+
+ values.graphics_exposures = False;
+ values.foreground = mrParent.GetColormap().GetBlackPixel()
+ ^ mrParent.GetColormap().GetWhitePixel();
+ values.function = GXxor;
+ values.line_width = 1;
+ values.line_style = LineOnOffDash;
+
+ mpTrackingGC = XCreateGC( mrParent.GetXDisplay(), mrParent.GetDrawable(),
+ GCGraphicsExposures | GCForeground | GCFunction
+ | GCLineWidth | GCLineStyle,
+ &values );
+ const char dash_list[2] = {2, 2};
+ XSetDashes( mrParent.GetXDisplay(), mpTrackingGC, 0, dash_list, 2 );
+ }
+
+ if( !mbTrackingGC )
+ {
+ mrParent.SetClipRegion( mpTrackingGC );
+ mbTrackingGC = true;
+ }
+
+ return mpTrackingGC;
+}
+
+GC X11SalGraphicsImpl::GetInvertGC()
+{
+ if( !mpInvertGC )
+ mpInvertGC = CreateGC( mrParent.GetDrawable(),
+ GCGraphicsExposures
+ | GCForeground
+ | GCFunction
+ | GCLineWidth );
+
+ if( !mbInvertGC )
+ {
+ mrParent.SetClipRegion( mpInvertGC );
+ mbInvertGC = true;
+ }
+ return mpInvertGC;
+}
+
+GC X11SalGraphicsImpl::GetInvert50GC()
+{
+ if( !mpInvert50GC )
+ {
+ XGCValues values;
+
+ values.graphics_exposures = False;
+ values.foreground = mrParent.GetColormap().GetWhitePixel();
+ values.background = mrParent.GetColormap().GetBlackPixel();
+ values.function = GXinvert;
+ values.line_width = 1;
+ values.line_style = LineSolid;
+ unsigned long const nValueMask =
+ GCGraphicsExposures
+ | GCForeground
+ | GCBackground
+ | GCFunction
+ | GCLineWidth
+ | GCLineStyle
+ | GCFillStyle
+ | GCStipple;
+
+ values.fill_style = FillStippled;
+ values.stipple = mrParent.GetDisplay()->GetInvert50( mrParent.m_nXScreen );
+
+ mpInvert50GC = XCreateGC( mrParent.GetXDisplay(), mrParent.GetDrawable(),
+ nValueMask,
+ &values );
+ }
+
+ if( !mbInvert50GC )
+ {
+ mrParent.SetClipRegion( mpInvert50GC );
+ mbInvert50GC = true;
+ }
+ return mpInvert50GC;
+}
+
+inline GC X11SalGraphicsImpl::GetStippleGC()
+{
+ if( !mpStippleGC )
+ mpStippleGC = CreateGC( mrParent.GetDrawable(),
+ GCGraphicsExposures
+ | GCFillStyle
+ | GCLineWidth );
+
+ if( !mbStippleGC )
+ {
+ XSetFunction( mrParent.GetXDisplay(), mpStippleGC, mbXORMode ? GXxor : GXcopy );
+ mrParent.SetClipRegion( mpStippleGC );
+ mbStippleGC = true;
+ }
+
+ return mpStippleGC;
+}
+
+GC X11SalGraphicsImpl::SelectBrush()
+{
+ Display *pDisplay = mrParent.GetXDisplay();
+
+ SAL_WARN_IF( mnBrushColor == SALCOLOR_NONE, "vcl", "Brush Transparent" );
+
+ if( !mpBrushGC )
+ {
+ XGCValues values;
+ values.subwindow_mode = ClipByChildren;
+ values.fill_rule = EvenOddRule; // Pict import/ Gradient
+ values.graphics_exposures = False;
+
+ mpBrushGC = XCreateGC( pDisplay, mrParent.GetDrawable(),
+ GCSubwindowMode | GCFillRule | GCGraphicsExposures,
+ &values );
+ }
+
+ if( !mbBrushGC )
+ {
+ if( !mbDitherBrush )
+ {
+ XSetFillStyle ( pDisplay, mpBrushGC, FillSolid );
+ XSetForeground( pDisplay, mpBrushGC, mnBrushPixel );
+ }
+ else
+ {
+ XSetFillStyle ( pDisplay, mpBrushGC, FillTiled );
+ XSetTile ( pDisplay, mpBrushGC, mrParent.hBrush_ );
+ }
+ XSetFunction ( pDisplay, mpBrushGC, mbXORMode ? GXxor : GXcopy );
+ mrParent.SetClipRegion( mpBrushGC );
+
+ mbBrushGC = true;
+ }
+
+ return mpBrushGC;
+}
+
+GC X11SalGraphicsImpl::SelectPen()
+{
+ Display *pDisplay = mrParent.GetXDisplay();
+
+ if( !mpPenGC )
+ {
+ XGCValues values;
+ values.subwindow_mode = ClipByChildren;
+ values.fill_rule = EvenOddRule; // Pict import/ Gradient
+ values.graphics_exposures = False;
+
+ mpPenGC = XCreateGC( pDisplay, mrParent.GetDrawable(),
+ GCSubwindowMode | GCFillRule | GCGraphicsExposures,
+ &values );
+ }
+
+ if( !mbPenGC )
+ {
+ if( mnPenColor != SALCOLOR_NONE )
+ XSetForeground( pDisplay, mpPenGC, mnPenPixel );
+ XSetFunction ( pDisplay, mpPenGC, mbXORMode ? GXxor : GXcopy );
+ mrParent.SetClipRegion( mpPenGC );
+ mbPenGC = true;
+ }
+
+ return mpPenGC;
+}
+
+void X11SalGraphicsImpl::DrawLines(sal_uInt32 nPoints,
+ const SalPolyLine &rPoints,
+ GC pGC,
+ bool bClose)
+{
+ // calculate how many lines XWindow can draw in one go
+ sal_uLong nMaxLines = (mrParent.GetDisplay()->GetMaxRequestSize() - sizeof(xPolyPointReq))
+ / sizeof(xPoint);
+ if( nMaxLines > nPoints ) nMaxLines = nPoints;
+
+ // print all lines that XWindows can draw
+ sal_uLong n;
+ for( n = 0; nPoints - n > nMaxLines; n += nMaxLines - 1 )
+ XDrawLines( mrParent.GetXDisplay(),
+ mrParent.GetDrawable(),
+ pGC,
+ const_cast<XPoint*>(&rPoints[n]),
+ nMaxLines,
+ CoordModeOrigin );
+
+ if( n < nPoints )
+ XDrawLines( mrParent.GetXDisplay(),
+ mrParent.GetDrawable(),
+ pGC,
+ const_cast<XPoint*>(&rPoints[n]),
+ nPoints - n,
+ CoordModeOrigin );
+ if( bClose )
+ {
+ if( rPoints[nPoints-1].x != rPoints[0].x || rPoints[nPoints-1].y != rPoints[0].y )
+ drawLine( rPoints[nPoints-1].x, rPoints[nPoints-1].y, rPoints[0].x, rPoints[0].y );
+ }
+}
+
+void X11SalGraphicsImpl::copyBits( const SalTwoRect& rPosAry,
+ SalGraphics *pSSrcGraphics )
+{
+ X11SalGraphics* pSrcGraphics = pSSrcGraphics
+ ? static_cast<X11SalGraphics*>(pSSrcGraphics)
+ : &mrParent;
+
+ if( rPosAry.mnSrcWidth <= 0
+ || rPosAry.mnSrcHeight <= 0
+ || rPosAry.mnDestWidth <= 0
+ || rPosAry.mnDestHeight <= 0 )
+ {
+ return;
+ }
+
+ int n;
+ if( pSrcGraphics == &mrParent )
+ {
+ n = 2;
+ }
+ else if( pSrcGraphics->bWindow_ )
+ {
+ // window or compatible virtual device
+ if( pSrcGraphics->GetDisplay() == mrParent.GetDisplay() &&
+ pSrcGraphics->m_nXScreen == mrParent.m_nXScreen &&
+ pSrcGraphics->GetVisual().GetDepth() == mrParent.GetVisual().GetDepth()
+ )
+ n = 2; // same Display
+ else
+ n = 1; // printer or other display
+ }
+ else if( pSrcGraphics->bVirDev_ )
+ {
+ n = 1; // window or compatible virtual device
+ }
+ else
+ n = 0;
+
+ if( n == 2
+ && rPosAry.mnSrcWidth == rPosAry.mnDestWidth
+ && rPosAry.mnSrcHeight == rPosAry.mnDestHeight
+ )
+ {
+ // #i60699# Need to generate graphics exposures (to repaint
+ // obscured areas beneath overlapping windows), src and dest
+ // are the same window.
+ const bool bNeedGraphicsExposures( pSrcGraphics == &mrParent &&
+ !mrParent.bVirDev_ &&
+ pSrcGraphics->bWindow_ );
+
+ GC pCopyGC = GetCopyGC();
+
+ if( bNeedGraphicsExposures )
+ XSetGraphicsExposures( mrParent.GetXDisplay(),
+ pCopyGC,
+ True );
+
+ XCopyArea( mrParent.GetXDisplay(),
+ pSrcGraphics->GetDrawable(), // source
+ mrParent.GetDrawable(), // destination
+ pCopyGC, // destination clipping
+ rPosAry.mnSrcX, rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth, rPosAry.mnSrcHeight,
+ rPosAry.mnDestX, rPosAry.mnDestY );
+
+ if( bNeedGraphicsExposures )
+ {
+ mrParent.YieldGraphicsExpose();
+
+ if( pCopyGC )
+ XSetGraphicsExposures( mrParent.GetXDisplay(),
+ pCopyGC,
+ False );
+ }
+ }
+ else if( n )
+ {
+ // #i60699# No chance to handle graphics exposures - we copy
+ // to a temp bitmap first, into which no repaints are
+ // technically possible.
+ std::shared_ptr<SalBitmap> xDDB(pSrcGraphics->getBitmap( rPosAry.mnSrcX,
+ rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth,
+ rPosAry.mnSrcHeight ));
+
+ if( !xDDB )
+ {
+ SAL_WARN( "vcl", "SalGraphics::CopyBits !pSrcGraphics->GetBitmap()" );
+ return;
+ }
+
+ SalTwoRect aPosAry( rPosAry );
+
+ aPosAry.mnSrcX = 0;
+ aPosAry.mnSrcY = 0;
+ drawBitmap( aPosAry, *xDDB );
+ }
+ else {
+ SAL_WARN( "vcl", "X11SalGraphicsImpl::CopyBits from Printer not yet implemented" );
+ }
+}
+
+void X11SalGraphicsImpl::copyArea ( tools::Long nDestX, tools::Long nDestY,
+ tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight,
+ bool /*bWindowInvalidate*/)
+{
+ SalTwoRect aPosAry(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
+ copyBits(aPosAry, nullptr);
+}
+
+void X11SalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap )
+{
+ const SalDisplay* pSalDisp = mrParent.GetDisplay();
+ Display* pXDisp = pSalDisp->GetDisplay();
+ const Drawable aDrawable( mrParent.GetDrawable() );
+ const SalColormap& rColMap = pSalDisp->GetColormap( mrParent.m_nXScreen );
+ const tools::Long nDepth = mrParent.GetDisplay()->GetVisual( mrParent.m_nXScreen ).GetDepth();
+ GC aGC( GetCopyGC() );
+ XGCValues aOldVal, aNewVal;
+ int nValues = GCForeground | GCBackground;
+
+ if( rSalBitmap.GetBitCount() == 1 )
+ {
+ // set foreground/background values for 1Bit bitmaps
+ XGetGCValues( pXDisp, aGC, nValues, &aOldVal );
+ setForeBack(aNewVal, rColMap, rSalBitmap);
+ XChangeGC( pXDisp, aGC, nValues, &aNewVal );
+ }
+
+ static_cast<const X11SalBitmap&>(rSalBitmap).ImplDraw( aDrawable, mrParent.m_nXScreen, nDepth, rPosAry, aGC );
+
+ if( rSalBitmap.GetBitCount() == 1 )
+ XChangeGC( pXDisp, aGC, nValues, &aOldVal );
+ XFlush( pXDisp );
+}
+
+void X11SalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSrcBitmap,
+ const SalBitmap& rMaskBitmap )
+{
+ // decide if alpha masking or transparency masking is needed
+ BitmapBuffer* pAlphaBuffer = const_cast<SalBitmap&>(rMaskBitmap).AcquireBuffer( BitmapAccessMode::Read );
+ if( pAlphaBuffer != nullptr )
+ {
+ ScanlineFormat nMaskFormat = pAlphaBuffer->mnFormat;
+ const_cast<SalBitmap&>(rMaskBitmap).ReleaseBuffer( pAlphaBuffer, BitmapAccessMode::Read );
+ if( nMaskFormat == ScanlineFormat::N8BitPal )
+ drawAlphaBitmap( rPosAry, rSrcBitmap, rMaskBitmap );
+ }
+
+ drawMaskedBitmap( rPosAry, rSrcBitmap, rMaskBitmap );
+}
+
+void X11SalGraphicsImpl::drawMaskedBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransBitmap )
+{
+ const SalDisplay* pSalDisp = mrParent.GetDisplay();
+ Display* pXDisp = pSalDisp->GetDisplay();
+ Drawable aDrawable( mrParent.GetDrawable() );
+
+ // figure work mode depth. If this is a VDev Drawable, use its
+ // bitdepth to create pixmaps for, otherwise, XCopyArea will
+ // refuse to work.
+ const sal_uInt16 nDepth( mrParent.m_pVDev ?
+ static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetDepth() :
+ pSalDisp->GetVisual( mrParent.m_nXScreen ).GetDepth() );
+ Pixmap aFG( limitXCreatePixmap( pXDisp, aDrawable, rPosAry.mnDestWidth,
+ rPosAry.mnDestHeight, nDepth ) );
+ Pixmap aBG( limitXCreatePixmap( pXDisp, aDrawable, rPosAry.mnDestWidth,
+ rPosAry.mnDestHeight, nDepth ) );
+
+ if( aFG && aBG )
+ {
+ GC aTmpGC;
+ XGCValues aValues;
+ setForeBack(aValues, pSalDisp->GetColormap(mrParent.m_nXScreen), rSalBitmap);
+ const int nValues = GCFunction | GCForeground | GCBackground;
+ SalTwoRect aTmpRect( rPosAry ); aTmpRect.mnDestX = aTmpRect.mnDestY = 0;
+
+ // draw paint bitmap in pixmap #1
+ aValues.function = GXcopy;
+ aTmpGC = XCreateGC( pXDisp, aFG, nValues, &aValues );
+ static_cast<const X11SalBitmap&>(rSalBitmap).ImplDraw( aFG, mrParent.m_nXScreen, nDepth, aTmpRect, aTmpGC );
+ DBG_TESTTRANS( aFG );
+
+ // draw background in pixmap #2
+ XCopyArea( pXDisp, aDrawable, aBG, aTmpGC,
+ rPosAry.mnDestX, rPosAry.mnDestY,
+ rPosAry.mnDestWidth, rPosAry.mnDestHeight,
+ 0, 0 );
+
+ DBG_TESTTRANS( aBG );
+
+ // mask out paint bitmap in pixmap #1 (transparent areas 0)
+ aValues.function = GXand;
+ aValues.foreground = 0x00000000;
+ aValues.background = 0xffffffff;
+ XChangeGC( pXDisp, aTmpGC, nValues, &aValues );
+ static_cast<const X11SalBitmap&>(rTransBitmap).ImplDraw( aFG, mrParent.m_nXScreen, 1, aTmpRect, aTmpGC );
+
+ DBG_TESTTRANS( aFG );
+
+ // #105055# For XOR mode, keep background behind bitmap intact
+ if( !mbXORMode )
+ {
+ // mask out background in pixmap #2 (nontransparent areas 0)
+ aValues.function = GXand;
+ aValues.foreground = 0xffffffff;
+ aValues.background = 0x00000000;
+ XChangeGC( pXDisp, aTmpGC, nValues, &aValues );
+ static_cast<const X11SalBitmap&>(rTransBitmap).ImplDraw( aBG, mrParent.m_nXScreen, 1, aTmpRect, aTmpGC );
+
+ DBG_TESTTRANS( aBG );
+ }
+
+ // merge pixmap #1 and pixmap #2 in pixmap #2
+ aValues.function = GXxor;
+ aValues.foreground = 0xffffffff;
+ aValues.background = 0x00000000;
+ XChangeGC( pXDisp, aTmpGC, nValues, &aValues );
+ XCopyArea( pXDisp, aFG, aBG, aTmpGC,
+ 0, 0,
+ rPosAry.mnDestWidth, rPosAry.mnDestHeight,
+ 0, 0 );
+ DBG_TESTTRANS( aBG );
+
+ // #105055# Disable XOR temporarily
+ bool bOldXORMode( mbXORMode );
+ mbXORMode = false;
+
+ // copy pixmap #2 (result) to background
+ XCopyArea( pXDisp, aBG, aDrawable, GetCopyGC(),
+ 0, 0,
+ rPosAry.mnDestWidth, rPosAry.mnDestHeight,
+ rPosAry.mnDestX, rPosAry.mnDestY );
+
+ DBG_TESTTRANS( aBG );
+
+ mbXORMode = bOldXORMode;
+
+ XFreeGC( pXDisp, aTmpGC );
+ XFlush( pXDisp );
+ }
+ else
+ drawBitmap( rPosAry, rSalBitmap );
+
+ if( aFG )
+ XFreePixmap( pXDisp, aFG );
+
+ if( aBG )
+ XFreePixmap( pXDisp, aBG );
+}
+
+bool X11SalGraphicsImpl::blendBitmap( const SalTwoRect&,
+ const SalBitmap& )
+{
+ return false;
+}
+
+bool X11SalGraphicsImpl::blendAlphaBitmap( const SalTwoRect&,
+ const SalBitmap&, const SalBitmap&, const SalBitmap& )
+{
+ return false;
+}
+
+bool X11SalGraphicsImpl::drawAlphaBitmap( const SalTwoRect& rTR,
+ const SalBitmap& rSrcBitmap, const SalBitmap& rAlphaBmp )
+{
+ // non 8-bit alpha not implemented yet
+ if( rAlphaBmp.GetBitCount() != 8 )
+ return false;
+ // #i75531# the workaround below can go when
+ // X11SalGraphics::drawAlphaBitmap()'s render acceleration
+ // can handle the bitmap depth mismatch directly
+ if( rSrcBitmap.GetBitCount() < rAlphaBmp.GetBitCount() )
+ return false;
+
+ // horizontal mirroring not implemented yet
+ if( rTR.mnDestWidth < 0 )
+ return false;
+
+ // stretched conversion is not implemented yet
+ if( rTR.mnDestWidth != rTR.mnSrcWidth )
+ return false;
+ if( rTR.mnDestHeight!= rTR.mnSrcHeight )
+ return false;
+
+ // create destination picture
+ Picture aDstPic = GetXRenderPicture();
+ if( !aDstPic )
+ return false;
+
+ const SalDisplay* pSalDisp = mrParent.GetDisplay();
+ const SalVisual& rSalVis = pSalDisp->GetVisual( mrParent.m_nXScreen );
+ Display* pXDisplay = pSalDisp->GetDisplay();
+
+ // create source Picture
+ int nDepth = mrParent.m_pVDev ? static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetDepth() : rSalVis.GetDepth();
+ const X11SalBitmap& rSrcX11Bmp = static_cast<const X11SalBitmap&>( rSrcBitmap );
+ ImplSalDDB* pSrcDDB = rSrcX11Bmp.ImplGetDDB( mrParent.GetDrawable(), mrParent.m_nXScreen, nDepth, rTR );
+ if( !pSrcDDB )
+ return false;
+
+ //#i75249# workaround for ImplGetDDB() giving us back a different depth than
+ // we requested. E.g. mask pixmaps are always compatible with the drawable
+ // TODO: find an appropriate picture format for these cases
+ // then remove the workaround below and the one for #i75531#
+ if( nDepth != pSrcDDB->ImplGetDepth() )
+ return false;
+
+ Pixmap aSrcPM = pSrcDDB->ImplGetPixmap();
+ if( !aSrcPM )
+ return false;
+
+ // create source picture
+ // TODO: use scoped picture
+ Visual* pSrcXVisual = rSalVis.GetVisual();
+ XRenderPeer& rPeer = XRenderPeer::GetInstance();
+ XRenderPictFormat* pSrcVisFmt = rPeer.FindVisualFormat( pSrcXVisual );
+ if( !pSrcVisFmt )
+ return false;
+ Picture aSrcPic = rPeer.CreatePicture( aSrcPM, pSrcVisFmt, 0, nullptr );
+ if( !aSrcPic )
+ return false;
+
+ // create alpha Picture
+
+ // TODO: use SalX11Bitmap functionality and caching for the Alpha Pixmap
+ // problem is that they don't provide an 8bit Pixmap on a non-8bit display
+ BitmapBuffer* pAlphaBuffer = const_cast<SalBitmap&>(rAlphaBmp).AcquireBuffer( BitmapAccessMode::Read );
+
+ // an XImage needs its data top_down
+ // TODO: avoid wrongly oriented images in upper layers!
+ const int nImageSize = pAlphaBuffer->mnHeight * pAlphaBuffer->mnScanlineSize;
+ const char* pSrcBits = reinterpret_cast<char*>(pAlphaBuffer->mpBits);
+ char* pAlphaBits = new char[ nImageSize ];
+ if( pAlphaBuffer->mnFormat & ScanlineFormat::TopDown )
+ memcpy( pAlphaBits, pSrcBits, nImageSize );
+ else
+ {
+ char* pDstBits = pAlphaBits + nImageSize;
+ const int nLineSize = pAlphaBuffer->mnScanlineSize;
+ for(; (pDstBits -= nLineSize) >= pAlphaBits; pSrcBits += nLineSize )
+ memcpy( pDstBits, pSrcBits, nLineSize );
+ }
+
+ // the alpha values need to be inverted for XRender
+ // TODO: make upper layers use standard alpha
+ tools::Long* pLDst = reinterpret_cast<long*>(pAlphaBits);
+ for( int i = nImageSize/sizeof(long); --i >= 0; ++pLDst )
+ *pLDst = ~*pLDst;
+
+ char* pCDst = reinterpret_cast<char*>(pLDst);
+ for( int i = nImageSize & (sizeof(long)-1); --i >= 0; ++pCDst )
+ *pCDst = ~*pCDst;
+
+ const XRenderPictFormat* pAlphaFormat = rPeer.GetStandardFormatA8();
+ XImage* pAlphaImg = XCreateImage( pXDisplay, pSrcXVisual, 8, ZPixmap, 0,
+ pAlphaBits, pAlphaBuffer->mnWidth, pAlphaBuffer->mnHeight,
+ pAlphaFormat->depth, pAlphaBuffer->mnScanlineSize );
+
+ Pixmap aAlphaPM = limitXCreatePixmap( pXDisplay, mrParent.GetDrawable(),
+ rTR.mnDestWidth, rTR.mnDestHeight, 8 );
+
+ XGCValues aAlphaGCV;
+ aAlphaGCV.function = GXcopy;
+ GC aAlphaGC = XCreateGC( pXDisplay, aAlphaPM, GCFunction, &aAlphaGCV );
+ XPutImage( pXDisplay, aAlphaPM, aAlphaGC, pAlphaImg,
+ rTR.mnSrcX, rTR.mnSrcY, 0, 0, rTR.mnDestWidth, rTR.mnDestHeight );
+ XFreeGC( pXDisplay, aAlphaGC );
+ XFree( pAlphaImg );
+ if( pAlphaBits != reinterpret_cast<char*>(pAlphaBuffer->mpBits) )
+ delete[] pAlphaBits;
+
+ const_cast<SalBitmap&>(rAlphaBmp).ReleaseBuffer( pAlphaBuffer, BitmapAccessMode::Read );
+
+ XRenderPictureAttributes aAttr;
+ aAttr.repeat = int(true);
+ Picture aAlphaPic = rPeer.CreatePicture( aAlphaPM, pAlphaFormat, CPRepeat, &aAttr );
+ if( !aAlphaPic )
+ return false;
+
+ // set clipping
+ if( mrParent.mpClipRegion && !XEmptyRegion( mrParent.mpClipRegion ) )
+ rPeer.SetPictureClipRegion( aDstPic, mrParent.mpClipRegion );
+
+ // paint source * mask over destination picture
+ rPeer.CompositePicture( PictOpOver, aSrcPic, aAlphaPic, aDstPic,
+ rTR.mnSrcX, rTR.mnSrcY,
+ rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight );
+
+ rPeer.FreePicture( aAlphaPic );
+ XFreePixmap(pXDisplay, aAlphaPM);
+ rPeer.FreePicture( aSrcPic );
+ return true;
+}
+
+bool X11SalGraphicsImpl::drawTransformedBitmap(
+ const basegfx::B2DPoint&,
+ const basegfx::B2DPoint&,
+ const basegfx::B2DPoint&,
+ const SalBitmap&,
+ const SalBitmap*,
+ double)
+{
+ // here direct support for transformed bitmaps can be implemented
+ return false;
+}
+
+bool X11SalGraphicsImpl::hasFastDrawTransformedBitmap() const
+{
+ return false;
+}
+
+bool X11SalGraphicsImpl::drawAlphaRect( tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, sal_uInt8 nTransparency )
+{
+ if( ! mrParent.m_pFrame && ! mrParent.m_pVDev )
+ return false;
+
+ if( mbPenGC || !mbBrushGC || mbXORMode )
+ return false; // can only perform solid fills without XOR.
+
+ if( mrParent.m_pVDev && static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetDepth() < 8 )
+ return false;
+
+ Picture aDstPic = GetXRenderPicture();
+ if( !aDstPic )
+ return false;
+
+ const double fTransparency = (100 - nTransparency) * (1.0/100);
+ const XRenderColor aRenderColor = GetXRenderColor( mnBrushColor , fTransparency);
+
+ XRenderPeer& rPeer = XRenderPeer::GetInstance();
+ rPeer.FillRectangle( PictOpOver,
+ aDstPic,
+ &aRenderColor,
+ nX, nY,
+ nWidth, nHeight );
+
+ return true;
+}
+
+void X11SalGraphicsImpl::drawMask( const SalTwoRect& rPosAry,
+ const SalBitmap &rSalBitmap,
+ Color nMaskColor )
+{
+ const SalDisplay* pSalDisp = mrParent.GetDisplay();
+ Display* pXDisp = pSalDisp->GetDisplay();
+ Drawable aDrawable( mrParent.GetDrawable() );
+ Pixmap aStipple( limitXCreatePixmap( pXDisp, aDrawable,
+ rPosAry.mnDestWidth,
+ rPosAry.mnDestHeight, 1 ) );
+
+ if( aStipple )
+ {
+ SalTwoRect aTwoRect( rPosAry ); aTwoRect.mnDestX = aTwoRect.mnDestY = 0;
+ GC aTmpGC;
+ XGCValues aValues;
+
+ // create a stipple bitmap first (set bits are changed to unset bits and vice versa)
+ aValues.function = GXcopyInverted;
+ aValues.foreground = 1;
+ aValues.background = 0;
+ aTmpGC = XCreateGC( pXDisp, aStipple, GCFunction | GCForeground | GCBackground, &aValues );
+ static_cast<const X11SalBitmap&>(rSalBitmap).ImplDraw( aStipple, mrParent.m_nXScreen, 1, aTwoRect, aTmpGC );
+
+ XFreeGC( pXDisp, aTmpGC );
+
+ // Set stipple and draw rectangle
+ GC aStippleGC( GetStippleGC() );
+ int nX = rPosAry.mnDestX, nY = rPosAry.mnDestY;
+
+ XSetStipple( pXDisp, aStippleGC, aStipple );
+ XSetTSOrigin( pXDisp, aStippleGC, nX, nY );
+ XSetForeground( pXDisp, aStippleGC, mrParent.GetPixel( nMaskColor ) );
+ XFillRectangle( pXDisp, aDrawable, aStippleGC,
+ nX, nY,
+ rPosAry.mnDestWidth, rPosAry.mnDestHeight );
+ XFreePixmap( pXDisp, aStipple );
+ XFlush( pXDisp );
+ }
+ else
+ drawBitmap( rPosAry, rSalBitmap );
+}
+
+void X11SalGraphicsImpl::ResetClipRegion()
+{
+ if( !mrParent.mpClipRegion )
+ return;
+
+ mbPenGC = false;
+ mbBrushGC = false;
+ mbCopyGC = false;
+ mbInvertGC = false;
+ mbInvert50GC = false;
+ mbStippleGC = false;
+ mbTrackingGC = false;
+
+ XDestroyRegion( mrParent.mpClipRegion );
+ mrParent.mpClipRegion = nullptr;
+}
+
+bool X11SalGraphicsImpl::setClipRegion( const vcl::Region& i_rClip )
+{
+ if( mrParent.mpClipRegion )
+ XDestroyRegion( mrParent.mpClipRegion );
+ mrParent.mpClipRegion = XCreateRegion();
+
+ RectangleVector aRectangles;
+ i_rClip.GetRegionRectangles(aRectangles);
+
+ for (auto const& rectangle : aRectangles)
+ {
+ const tools::Long nW(rectangle.GetWidth());
+
+ if(nW)
+ {
+ const tools::Long nH(rectangle.GetHeight());
+
+ if(nH)
+ {
+ XRectangle aRect;
+
+ aRect.x = static_cast<short>(rectangle.Left());
+ aRect.y = static_cast<short>(rectangle.Top());
+ aRect.width = static_cast<unsigned short>(nW);
+ aRect.height = static_cast<unsigned short>(nH);
+ XUnionRectWithRegion(&aRect, mrParent.mpClipRegion, mrParent.mpClipRegion);
+ }
+ }
+ }
+
+ //ImplRegionInfo aInfo;
+ //long nX, nY, nW, nH;
+ //bool bRegionRect = i_rClip.ImplGetFirstRect(aInfo, nX, nY, nW, nH );
+ //while( bRegionRect )
+ //{
+ // if ( nW && nH )
+ // {
+ // XRectangle aRect;
+ // aRect.x = (short)nX;
+ // aRect.y = (short)nY;
+ // aRect.width = (unsigned short)nW;
+ // aRect.height = (unsigned short)nH;
+
+ // XUnionRectWithRegion( &aRect, mrParent.mpClipRegion, mrParent.mpClipRegion );
+ // }
+ // bRegionRect = i_rClip.ImplGetNextRect( aInfo, nX, nY, nW, nH );
+ //}
+
+ // done, invalidate GCs
+ mbPenGC = false;
+ mbBrushGC = false;
+ mbCopyGC = false;
+ mbInvertGC = false;
+ mbInvert50GC = false;
+ mbStippleGC = false;
+ mbTrackingGC = false;
+
+ if( XEmptyRegion( mrParent.mpClipRegion ) )
+ {
+ XDestroyRegion( mrParent.mpClipRegion );
+ mrParent.mpClipRegion= nullptr;
+ }
+ return true;
+}
+
+void X11SalGraphicsImpl::SetLineColor()
+{
+ if( mnPenColor != SALCOLOR_NONE )
+ {
+ mnPenColor = SALCOLOR_NONE;
+ mbPenGC = false;
+ }
+}
+
+void X11SalGraphicsImpl::SetLineColor( Color nColor )
+{
+ if( mnPenColor != nColor )
+ {
+ mnPenColor = nColor;
+ mnPenPixel = mrParent.GetPixel( nColor );
+ mbPenGC = false;
+ }
+}
+
+void X11SalGraphicsImpl::SetFillColor()
+{
+ if( mnBrushColor != SALCOLOR_NONE )
+ {
+ mbDitherBrush = false;
+ mnBrushColor = SALCOLOR_NONE;
+ mbBrushGC = false;
+ }
+}
+
+void X11SalGraphicsImpl::SetFillColor( Color nColor )
+{
+ if( mnBrushColor == nColor )
+ return;
+
+ mbDitherBrush = false;
+ mnBrushColor = nColor;
+ mnBrushPixel = mrParent.GetPixel( nColor );
+ if( TrueColor != mrParent.GetColormap().GetVisual().GetClass()
+ && mrParent.GetColormap().GetColor( mnBrushPixel ) != mnBrushColor
+ && nColor != Color( 0x00, 0x00, 0x00 ) // black
+ && nColor != Color( 0x00, 0x00, 0x80 ) // blue
+ && nColor != Color( 0x00, 0x80, 0x00 ) // green
+ && nColor != Color( 0x00, 0x80, 0x80 ) // cyan
+ && nColor != Color( 0x80, 0x00, 0x00 ) // red
+ && nColor != Color( 0x80, 0x00, 0x80 ) // magenta
+ && nColor != Color( 0x80, 0x80, 0x00 ) // brown
+ && nColor != Color( 0x80, 0x80, 0x80 ) // gray
+ && nColor != Color( 0xC0, 0xC0, 0xC0 ) // light gray
+ && nColor != Color( 0x00, 0x00, 0xFF ) // light blue
+ && nColor != Color( 0x00, 0xFF, 0x00 ) // light green
+ && nColor != Color( 0x00, 0xFF, 0xFF ) // light cyan
+ && nColor != Color( 0xFF, 0x00, 0x00 ) // light red
+ && nColor != Color( 0xFF, 0x00, 0xFF ) // light magenta
+ && nColor != Color( 0xFF, 0xFF, 0x00 ) // light brown
+ && nColor != Color( 0xFF, 0xFF, 0xFF ) )
+ mbDitherBrush = mrParent.GetDitherPixmap(nColor);
+ mbBrushGC = false;
+}
+
+void X11SalGraphicsImpl::SetROPLineColor( SalROPColor nROPColor )
+{
+ switch( nROPColor )
+ {
+ case SalROPColor::N0 : // 0
+ mnPenPixel = Pixel(0);
+ break;
+ case SalROPColor::N1 : // 1
+ mnPenPixel = static_cast<Pixel>(1 << mrParent.GetVisual().GetDepth()) - 1;
+ break;
+ case SalROPColor::Invert : // 2
+ mnPenPixel = static_cast<Pixel>(1 << mrParent.GetVisual().GetDepth()) - 1;
+ break;
+ }
+ mnPenColor = mrParent.GetColormap().GetColor( mnPenPixel );
+ mbPenGC = false;
+}
+
+void X11SalGraphicsImpl::SetROPFillColor( SalROPColor nROPColor )
+{
+ switch( nROPColor )
+ {
+ case SalROPColor::N0 : // 0
+ mnBrushPixel = Pixel(0);
+ break;
+ case SalROPColor::N1 : // 1
+ mnBrushPixel = static_cast<Pixel>(1 << mrParent.GetVisual().GetDepth()) - 1;
+ break;
+ case SalROPColor::Invert : // 2
+ mnBrushPixel = static_cast<Pixel>(1 << mrParent.GetVisual().GetDepth()) - 1;
+ break;
+ }
+ mbDitherBrush = false;
+ mnBrushColor = mrParent.GetColormap().GetColor( mnBrushPixel );
+ mbBrushGC = false;
+}
+
+void X11SalGraphicsImpl::SetXORMode( bool bSet, bool )
+{
+ if (mbXORMode != bSet)
+ {
+ mbXORMode = bSet;
+ mbPenGC = false;
+ mbBrushGC = false;
+ mbCopyGC = false;
+ mbInvertGC = false;
+ mbInvert50GC = false;
+ mbStippleGC = false;
+ mbTrackingGC = false;
+ }
+}
+
+void X11SalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY )
+{
+ if( mnPenColor != SALCOLOR_NONE )
+ XDrawPoint( mrParent.GetXDisplay(), mrParent.GetDrawable(), SelectPen(), nX, nY );
+}
+
+void X11SalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY, Color nColor )
+{
+ if( nColor == SALCOLOR_NONE )
+ return;
+
+ Display *pDisplay = mrParent.GetXDisplay();
+
+ if( (mnPenColor == SALCOLOR_NONE) && !mbPenGC )
+ {
+ SetLineColor( nColor );
+ XDrawPoint( pDisplay, mrParent.GetDrawable(), SelectPen(), nX, nY );
+ mnPenColor = SALCOLOR_NONE;
+ mbPenGC = False;
+ }
+ else
+ {
+ GC pGC = SelectPen();
+
+ if( nColor != mnPenColor )
+ XSetForeground( pDisplay, pGC, mrParent.GetPixel( nColor ) );
+
+ XDrawPoint( pDisplay, mrParent.GetDrawable(), pGC, nX, nY );
+
+ if( nColor != mnPenColor )
+ XSetForeground( pDisplay, pGC, mnPenPixel );
+ }
+}
+
+void X11SalGraphicsImpl::drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 )
+{
+ if( mnPenColor != SALCOLOR_NONE )
+ {
+ XDrawLine( mrParent.GetXDisplay(), mrParent.GetDrawable(),SelectPen(),
+ nX1, nY1, nX2, nY2 );
+ }
+}
+
+void X11SalGraphicsImpl::drawRect( tools::Long nX, tools::Long nY, tools::Long nDX, tools::Long nDY )
+{
+ if( mnBrushColor != SALCOLOR_NONE )
+ {
+ XFillRectangle( mrParent.GetXDisplay(),
+ mrParent.GetDrawable(),
+ SelectBrush(),
+ nX, nY, nDX, nDY );
+ }
+ // description DrawRect is wrong; thus -1
+ if( mnPenColor != SALCOLOR_NONE )
+ XDrawRectangle( mrParent.GetXDisplay(),
+ mrParent.GetDrawable(),
+ SelectPen(),
+ nX, nY, nDX-1, nDY-1 );
+}
+
+void X11SalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const Point *pPtAry )
+{
+ internalDrawPolyLine( nPoints, pPtAry, false );
+}
+
+void X11SalGraphicsImpl::internalDrawPolyLine( sal_uInt32 nPoints, const Point *pPtAry, bool bClose )
+{
+ if( mnPenColor != SALCOLOR_NONE )
+ {
+ SalPolyLine Points( nPoints, pPtAry );
+
+ DrawLines( nPoints, Points, SelectPen(), bClose );
+ }
+}
+
+void X11SalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const Point* pPtAry )
+{
+ if( nPoints == 0 )
+ return;
+
+ if( nPoints < 3 )
+ {
+ if( !mbXORMode )
+ {
+ if( 1 == nPoints )
+ drawPixel( pPtAry[0].getX(), pPtAry[0].getY() );
+ else
+ drawLine( pPtAry[0].getX(), pPtAry[0].getY(),
+ pPtAry[1].getX(), pPtAry[1].getY() );
+ }
+ return;
+ }
+
+ SalPolyLine Points( nPoints, pPtAry );
+
+ nPoints++;
+
+ /* WORKAROUND: some Xservers (Xorg, VIA chipset in this case)
+ * do not draw the visible part of a polygon
+ * if it overlaps to the left of screen 0,y.
+ * This happens to be the case in the gradient drawn in the
+ * menubar background. workaround for the special case of
+ * of a rectangle overlapping to the left.
+ */
+ if (nPoints == 5 &&
+ Points[ 0 ].x == Points[ 1 ].x &&
+ Points[ 1 ].y == Points[ 2 ].y &&
+ Points[ 2 ].x == Points[ 3 ].x &&
+ Points[ 0 ].x == Points[ 4 ].x && Points[ 0 ].y == Points[ 4 ].y
+ )
+ {
+ bool bLeft = false;
+ bool bRight = false;
+ for(unsigned int i = 0; i < nPoints; i++ )
+ {
+ if( Points[i].x < 0 )
+ bLeft = true;
+ else
+ bRight= true;
+ }
+ if( bLeft && ! bRight )
+ return;
+ if( bLeft && bRight )
+ {
+ for( unsigned int i = 0; i < nPoints; i++ )
+ if( Points[i].x < 0 )
+ Points[i].x = 0;
+ }
+ }
+
+ if( mnBrushColor != SALCOLOR_NONE )
+ XFillPolygon( mrParent.GetXDisplay(),
+ mrParent.GetDrawable(),
+ SelectBrush(),
+ &Points[0], nPoints,
+ Complex, CoordModeOrigin );
+
+ if( mnPenColor != SALCOLOR_NONE )
+ DrawLines( nPoints, Points, SelectPen(), true );
+}
+
+void X11SalGraphicsImpl::drawPolyPolygon( sal_uInt32 nPoly,
+ const sal_uInt32 *pPoints,
+ const Point* *pPtAry )
+{
+ if( mnBrushColor != SALCOLOR_NONE )
+ {
+ sal_uInt32 i, n;
+ Region pXRegA = nullptr;
+
+ for( i = 0; i < nPoly; i++ ) {
+ n = pPoints[i];
+ SalPolyLine Points( n, pPtAry[i] );
+ if( n > 2 )
+ {
+ Region pXRegB = XPolygonRegion( &Points[0], n+1, WindingRule );
+ if( !pXRegA )
+ pXRegA = pXRegB;
+ else
+ {
+ XXorRegion( pXRegA, pXRegB, pXRegA );
+ XDestroyRegion( pXRegB );
+ }
+ }
+ }
+
+ if( pXRegA )
+ {
+ XRectangle aXRect;
+ XClipBox( pXRegA, &aXRect );
+
+ GC pGC = SelectBrush();
+ mrParent.SetClipRegion( pGC, pXRegA ); // ??? twice
+ XDestroyRegion( pXRegA );
+ mbBrushGC = false;
+
+ XFillRectangle( mrParent.GetXDisplay(),
+ mrParent.GetDrawable(),
+ pGC,
+ aXRect.x, aXRect.y, aXRect.width, aXRect.height );
+ }
+ }
+
+ if( mnPenColor != SALCOLOR_NONE )
+ for( sal_uInt32 i = 0; i < nPoly; i++ )
+ internalDrawPolyLine( pPoints[i], pPtAry[i], true );
+}
+
+bool X11SalGraphicsImpl::drawPolyLineBezier( sal_uInt32, const Point*, const PolyFlags* )
+{
+ return false;
+}
+
+bool X11SalGraphicsImpl::drawPolygonBezier( sal_uInt32, const Point*, const PolyFlags* )
+{
+ return false;
+}
+
+bool X11SalGraphicsImpl::drawPolyPolygonBezier( sal_uInt32, const sal_uInt32*,
+ const Point* const*, const PolyFlags* const* )
+{
+ return false;
+}
+
+void X11SalGraphicsImpl::invert( tools::Long nX,
+ tools::Long nY,
+ tools::Long nDX,
+ tools::Long nDY,
+ SalInvert nFlags )
+{
+ GC pGC;
+ if( SalInvert::N50 & nFlags )
+ {
+ pGC = GetInvert50GC();
+ XFillRectangle( mrParent.GetXDisplay(), mrParent.GetDrawable(), pGC, nX, nY, nDX, nDY );
+ }
+ else
+ {
+ if ( SalInvert::TrackFrame & nFlags )
+ {
+ pGC = GetTrackingGC();
+ XDrawRectangle( mrParent.GetXDisplay(), mrParent.GetDrawable(), pGC, nX, nY, nDX, nDY );
+ }
+ else
+ {
+ pGC = GetInvertGC();
+ XFillRectangle( mrParent.GetXDisplay(), mrParent.GetDrawable(), pGC, nX, nY, nDX, nDY );
+ }
+ }
+}
+
+void X11SalGraphicsImpl::invert( sal_uInt32 nPoints,
+ const Point* pPtAry,
+ SalInvert nFlags )
+{
+ SalPolyLine Points ( nPoints, pPtAry );
+
+ GC pGC;
+ if( SalInvert::N50 & nFlags )
+ pGC = GetInvert50GC();
+ else
+ if ( SalInvert::TrackFrame & nFlags )
+ pGC = GetTrackingGC();
+ else
+ pGC = GetInvertGC();
+
+ if( SalInvert::TrackFrame & nFlags )
+ DrawLines ( nPoints, Points, pGC, true );
+ else
+ XFillPolygon( mrParent.GetXDisplay(),
+ mrParent.GetDrawable(),
+ pGC,
+ &Points[0], nPoints,
+ Complex, CoordModeOrigin );
+}
+
+bool X11SalGraphicsImpl::drawEPS( tools::Long,tools::Long,tools::Long,tools::Long,void*,sal_uInt32 )
+{
+ return false;
+}
+
+// draw a poly-polygon
+bool X11SalGraphicsImpl::drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fTransparency)
+{
+ // nothing to do for empty polypolygons
+ const int nOrigPolyCount = rPolyPolygon.count();
+ if( nOrigPolyCount <= 0 )
+ return true;
+
+ // nothing to do if everything is transparent
+ if( (mnBrushColor == SALCOLOR_NONE)
+ && (mnPenColor == SALCOLOR_NONE) )
+ return true;
+
+ // cannot handle pencolor!=brushcolor yet
+ if( (mnPenColor != SALCOLOR_NONE)
+ && (mnPenColor != mnBrushColor) )
+ return false;
+
+ // TODO: remove the env-variable when no longer needed
+ static const char* pRenderEnv = getenv( "SAL_DISABLE_RENDER_POLY" );
+ if( pRenderEnv )
+ return false;
+
+ // Fallback: Transform to DeviceCoordinates
+ basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
+ aPolyPolygon.transform(rObjectToDevice);
+
+ // snap to raster if requested
+ const bool bSnapToRaster = !mrParent.getAntiAlias();
+ if( bSnapToRaster )
+ aPolyPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges( aPolyPolygon );
+
+ // don't bother with polygons outside of visible area
+ const basegfx::B2DRange aViewRange( 0, 0, GetGraphicsWidth(), GetGraphicsHeight() );
+ aPolyPolygon = basegfx::utils::clipPolyPolygonOnRange( aPolyPolygon, aViewRange, true, false );
+ if( !aPolyPolygon.count() )
+ return true;
+
+ // tessellate the polypolygon into trapezoids
+ basegfx::B2DTrapezoidVector aB2DTrapVector;
+ basegfx::utils::trapezoidSubdivide( aB2DTrapVector, aPolyPolygon );
+ const int nTrapCount = aB2DTrapVector.size();
+ if( !nTrapCount )
+ return true;
+ const bool bDrawn = drawFilledTrapezoids( aB2DTrapVector.data(), nTrapCount, fTransparency );
+ return bDrawn;
+}
+
+tools::Long X11SalGraphicsImpl::GetGraphicsHeight() const
+{
+ if( mrParent.m_pFrame )
+ return mrParent.m_pFrame->maGeometry.nHeight;
+ else if( mrParent.m_pVDev )
+ return static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetHeight();
+ else
+ return 0;
+}
+
+bool X11SalGraphicsImpl::drawFilledTrapezoids( const basegfx::B2DTrapezoid* pB2DTraps, int nTrapCount, double fTransparency )
+{
+ if( nTrapCount <= 0 )
+ return true;
+
+ Picture aDstPic = GetXRenderPicture();
+ // check xrender support for this drawable
+ if( !aDstPic )
+ return false;
+
+ // convert the B2DTrapezoids into XRender-Trapezoids
+ std::vector<XTrapezoid> aTrapVector( nTrapCount );
+ const basegfx::B2DTrapezoid* pB2DTrap = pB2DTraps;
+ for( int i = 0; i < nTrapCount; ++pB2DTrap, ++i )
+ {
+ XTrapezoid& rTrap = aTrapVector[ i ] ;
+
+ // set y-coordinates
+ const double fY1 = pB2DTrap->getTopY();
+ rTrap.left.p1.y = rTrap.right.p1.y = rTrap.top = XDoubleToFixed( fY1 );
+ const double fY2 = pB2DTrap->getBottomY();
+ rTrap.left.p2.y = rTrap.right.p2.y = rTrap.bottom = XDoubleToFixed( fY2 );
+
+ // set x-coordinates
+ const double fXL1 = pB2DTrap->getTopXLeft();
+ rTrap.left.p1.x = XDoubleToFixed( fXL1 );
+ const double fXR1 = pB2DTrap->getTopXRight();
+ rTrap.right.p1.x = XDoubleToFixed( fXR1 );
+ const double fXL2 = pB2DTrap->getBottomXLeft();
+ rTrap.left.p2.x = XDoubleToFixed( fXL2 );
+ const double fXR2 = pB2DTrap->getBottomXRight();
+ rTrap.right.p2.x = XDoubleToFixed( fXR2 );
+ }
+
+ // get xrender Picture for polygon foreground
+ // TODO: cache it like the target picture which uses GetXRenderPicture()
+ XRenderPeer& rRenderPeer = XRenderPeer::GetInstance();
+ SalDisplay::RenderEntry& rEntry = mrParent.GetDisplay()->GetRenderEntries( mrParent.m_nXScreen )[ 32 ];
+ if( !rEntry.m_aPicture )
+ {
+ Display* pXDisplay = mrParent.GetXDisplay();
+
+ rEntry.m_aPixmap = limitXCreatePixmap( pXDisplay, mrParent.GetDrawable(), 1, 1, 32 );
+ XRenderPictureAttributes aAttr;
+ aAttr.repeat = int(true);
+
+ XRenderPictFormat* pXRPF = rRenderPeer.FindStandardFormat( PictStandardARGB32 );
+ rEntry.m_aPicture = rRenderPeer.CreatePicture( rEntry.m_aPixmap, pXRPF, CPRepeat, &aAttr );
+ }
+
+ // set polygon foreground color and opacity
+ XRenderColor aRenderColor = GetXRenderColor( mnBrushColor , fTransparency );
+ rRenderPeer.FillRectangle( PictOpSrc, rEntry.m_aPicture, &aRenderColor, 0, 0, 1, 1 );
+
+ // set clipping
+ // TODO: move into GetXRenderPicture?
+ if( mrParent.mpClipRegion && !XEmptyRegion( mrParent.mpClipRegion ) )
+ rRenderPeer.SetPictureClipRegion( aDstPic, mrParent.mpClipRegion );
+
+ // render the trapezoids
+ const XRenderPictFormat* pMaskFormat = rRenderPeer.GetStandardFormatA8();
+ rRenderPeer.CompositeTrapezoids( PictOpOver,
+ rEntry.m_aPicture, aDstPic, pMaskFormat, 0, 0, aTrapVector.data(), aTrapVector.size() );
+
+ return true;
+}
+
+bool X11SalGraphicsImpl::drawFilledTriangles(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::triangulator::B2DTriangleVector& rTriangles,
+ double fTransparency)
+{
+ if(rTriangles.empty())
+ return true;
+
+ Picture aDstPic = GetXRenderPicture();
+ // check xrender support for this drawable
+ if( !aDstPic )
+ {
+ return false;
+ }
+
+ // prepare transformation for ObjectToDevice coordinate system
+ basegfx::B2DHomMatrix aObjectToDevice = basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5) * rObjectToDevice;
+
+ // convert the Triangles into XRender-Triangles
+ std::vector<XTriangle> aTriVector(rTriangles.size());
+ sal_uInt32 nIndex(0);
+
+ for(const auto& rCandidate : rTriangles)
+ {
+ const basegfx::B2DPoint aP1(aObjectToDevice * rCandidate.getA());
+ const basegfx::B2DPoint aP2(aObjectToDevice * rCandidate.getB());
+ const basegfx::B2DPoint aP3(aObjectToDevice * rCandidate.getC());
+ XTriangle& rTri(aTriVector[nIndex++]);
+
+ rTri.p1.x = XDoubleToFixed(aP1.getX());
+ rTri.p1.y = XDoubleToFixed(aP1.getY());
+
+ rTri.p2.x = XDoubleToFixed(aP2.getX());
+ rTri.p2.y = XDoubleToFixed(aP2.getY());
+
+ rTri.p3.x = XDoubleToFixed(aP3.getX());
+ rTri.p3.y = XDoubleToFixed(aP3.getY());
+ }
+
+ // get xrender Picture for polygon foreground
+ // TODO: cache it like the target picture which uses GetXRenderPicture()
+ XRenderPeer& rRenderPeer = XRenderPeer::GetInstance();
+ SalDisplay::RenderEntry& rEntry = mrParent.GetDisplay()->GetRenderEntries( mrParent.m_nXScreen )[ 32 ];
+ if( !rEntry.m_aPicture )
+ {
+ Display* pXDisplay = mrParent.GetXDisplay();
+
+ rEntry.m_aPixmap = limitXCreatePixmap( pXDisplay, mrParent.GetDrawable(), 1, 1, 32 );
+ XRenderPictureAttributes aAttr;
+ aAttr.repeat = int(true);
+
+ XRenderPictFormat* pXRPF = rRenderPeer.FindStandardFormat( PictStandardARGB32 );
+ rEntry.m_aPicture = rRenderPeer.CreatePicture( rEntry.m_aPixmap, pXRPF, CPRepeat, &aAttr );
+ }
+
+ // set polygon foreground color and opacity
+ XRenderColor aRenderColor = GetXRenderColor( mnBrushColor , fTransparency );
+ rRenderPeer.FillRectangle( PictOpSrc, rEntry.m_aPicture, &aRenderColor, 0, 0, 1, 1 );
+
+ // set clipping
+ // TODO: move into GetXRenderPicture?
+ if( mrParent.mpClipRegion && !XEmptyRegion( mrParent.mpClipRegion ) )
+ rRenderPeer.SetPictureClipRegion( aDstPic, mrParent.mpClipRegion );
+
+ // render the trapezoids
+ const XRenderPictFormat* pMaskFormat = rRenderPeer.GetStandardFormatA8();
+ rRenderPeer.CompositeTriangles( PictOpOver,
+ rEntry.m_aPicture, aDstPic, pMaskFormat, 0, 0, aTriVector.data(), aTriVector.size() );
+
+ return true;
+}
+
+namespace {
+
+class SystemDependentData_Triangulation : public basegfx::SystemDependentData
+{
+private:
+ // the triangulation itself
+ basegfx::triangulator::B2DTriangleVector maTriangles;
+
+ // all other values the triangulation is based on and
+ // need to be compared with to check for data validity
+ double mfLineWidth;
+ basegfx::B2DLineJoin meJoin;
+ css::drawing::LineCap meCap;
+ double mfMiterMinimumAngle;
+ std::vector< double > maStroke;
+
+public:
+ SystemDependentData_Triangulation(
+ basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+ basegfx::triangulator::B2DTriangleVector&& rTriangles,
+ double fLineWidth,
+ basegfx::B2DLineJoin eJoin,
+ css::drawing::LineCap eCap,
+ double fMiterMinimumAngle,
+ const std::vector< double >* pStroke); // MM01
+
+ // read access
+ const basegfx::triangulator::B2DTriangleVector& getTriangles() const { return maTriangles; }
+ double getLineWidth() const { return mfLineWidth; }
+ const basegfx::B2DLineJoin& getJoin() const { return meJoin; }
+ const css::drawing::LineCap& getCap() const { return meCap; }
+ double getMiterMinimumAngle() const { return mfMiterMinimumAngle; }
+ const std::vector< double >& getStroke() const { return maStroke; }
+
+ virtual sal_Int64 estimateUsageInBytes() const override;
+};
+
+}
+
+SystemDependentData_Triangulation::SystemDependentData_Triangulation(
+ basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+ basegfx::triangulator::B2DTriangleVector&& rTriangles,
+ double fLineWidth,
+ basegfx::B2DLineJoin eJoin,
+ css::drawing::LineCap eCap,
+ double fMiterMinimumAngle,
+ const std::vector< double >* pStroke)
+: basegfx::SystemDependentData(rSystemDependentDataManager),
+ maTriangles(std::move(rTriangles)),
+ mfLineWidth(fLineWidth),
+ meJoin(eJoin),
+ meCap(eCap),
+ mfMiterMinimumAngle(fMiterMinimumAngle)
+{
+ if(nullptr != pStroke)
+ {
+ maStroke = *pStroke;
+ }
+}
+
+sal_Int64 SystemDependentData_Triangulation::estimateUsageInBytes() const
+{
+ sal_Int64 nRetval(0);
+
+ if(!maTriangles.empty())
+ {
+ nRetval = maTriangles.size() * sizeof(basegfx::triangulator::B2DTriangle);
+ }
+
+ return nRetval;
+}
+
+bool X11SalGraphicsImpl::drawPolyLine(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon& rPolygon,
+ double fTransparency,
+ double fLineWidth,
+ const std::vector< double >* pStroke, // MM01
+ basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle,
+ bool bPixelSnapHairline)
+{
+ // short circuit if there is nothing to do
+ if(0 == rPolygon.count() || fTransparency < 0.0 || fTransparency >= 1.0)
+ {
+ return true;
+ }
+
+ // need to check/handle LineWidth when ObjectToDevice transformation is used
+ const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity());
+ basegfx::B2DHomMatrix aObjectToDeviceInv;
+
+ // tdf#124848 calculate-back logical LineWidth for a hairline.
+ // This implementation does not hand over the transformation to
+ // the graphic sub-system, but the triangulation data is prepared
+ // view-independent based on the logic LineWidth, so we need to
+ // know it
+ if(fLineWidth == 0)
+ {
+ fLineWidth = 1.0;
+
+ if(!bObjectToDeviceIsIdentity)
+ {
+ if(aObjectToDeviceInv.isIdentity())
+ {
+ aObjectToDeviceInv = rObjectToDevice;
+ aObjectToDeviceInv.invert();
+ }
+
+ fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(fLineWidth, 0)).getLength();
+ }
+ }
+
+ // try to access buffered data
+ std::shared_ptr<SystemDependentData_Triangulation> pSystemDependentData_Triangulation(
+ rPolygon.getSystemDependentData<SystemDependentData_Triangulation>());
+
+ // MM01 need to do line dashing as fallback stuff here now
+ const double fDotDashLength(nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0);
+ const bool bStrokeUsed(0.0 != fDotDashLength);
+ assert(!bStrokeUsed || (bStrokeUsed && pStroke));
+
+ if(pSystemDependentData_Triangulation)
+ {
+ // MM01 - check on stroke change. Used against not used, or if oth used,
+ // equal or different? Triangulation geometry creation depends heavily
+ // on stroke, independent of being transformation independent
+ const bool bStrokeWasUsed(!pSystemDependentData_Triangulation->getStroke().empty());
+
+ if(bStrokeWasUsed != bStrokeUsed
+ || (bStrokeUsed && *pStroke != pSystemDependentData_Triangulation->getStroke()))
+ {
+ // data invalid, forget
+ pSystemDependentData_Triangulation.reset();
+ }
+ }
+
+ if(pSystemDependentData_Triangulation)
+ {
+ // check data validity (I)
+ if(pSystemDependentData_Triangulation->getJoin() != eLineJoin
+ || pSystemDependentData_Triangulation->getCap() != eLineCap
+ || pSystemDependentData_Triangulation->getMiterMinimumAngle() != fMiterMinimumAngle)
+ {
+ // data invalid, forget
+ pSystemDependentData_Triangulation.reset();
+ }
+ }
+
+ if(pSystemDependentData_Triangulation)
+ {
+ // check data validity (II)
+ if(pSystemDependentData_Triangulation->getLineWidth() != fLineWidth)
+ {
+ // sometimes small inconsistencies, use a percentage tolerance
+ const double fFactor(basegfx::fTools::equalZero(fLineWidth)
+ ? 0.0
+ : fabs(1.0 - (pSystemDependentData_Triangulation->getLineWidth() / fLineWidth)));
+ // compare with 5.0% tolerance
+ if(basegfx::fTools::more(fFactor, 0.05))
+ {
+ // data invalid, forget
+ pSystemDependentData_Triangulation.reset();
+ }
+ }
+ }
+
+ if(!pSystemDependentData_Triangulation)
+ {
+ // MM01 need to do line dashing as fallback stuff here now
+ basegfx::B2DPolyPolygon aPolyPolygonLine;
+
+ if(bStrokeUsed)
+ {
+ // apply LineStyle
+ basegfx::utils::applyLineDashing(
+ rPolygon, // source
+ *pStroke, // pattern
+ &aPolyPolygonLine, // target for lines
+ nullptr, // target for gaps
+ fDotDashLength); // full length if available
+ }
+ else
+ {
+ // no line dashing, just copy
+ aPolyPolygonLine.append(rPolygon);
+ }
+
+ // try to create data
+ if(bPixelSnapHairline)
+ {
+ // Do NOT transform, but keep device-independent. To
+ // do so, transform to device for snap, but back again after
+ if(!bObjectToDeviceIsIdentity)
+ {
+ aPolyPolygonLine.transform(rObjectToDevice);
+ }
+
+ aPolyPolygonLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygonLine);
+
+ if(!bObjectToDeviceIsIdentity)
+ {
+ if(aObjectToDeviceInv.isIdentity())
+ {
+ aObjectToDeviceInv = rObjectToDevice;
+ aObjectToDeviceInv.invert();
+ }
+
+ aPolyPolygonLine.transform(aObjectToDeviceInv);
+ }
+ }
+
+ basegfx::triangulator::B2DTriangleVector aTriangles;
+
+ // MM01 checked/verified for X11 (linux)
+ for(sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
+ {
+ const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
+ // MM01 upps - commit 51b5b93092d6231615de470c62494c24e54828a1 removed
+ // this *central* geometry-creating lines (!) probably due to aAreaPolyPoly
+ // *not* being used - that's true, but the work is inside of filling
+ // aTriangles data (!)
+ basegfx::utils::createAreaGeometry(
+ aPolyLine,
+ 0.5 * fLineWidth,
+ eLineJoin,
+ eLineCap,
+ basegfx::deg2rad(12.5),
+ 0.4,
+ fMiterMinimumAngle,
+ &aTriangles); // CAUTION! This is *needed* since it creates the data!
+ }
+
+ if(!aTriangles.empty())
+ {
+ // Add to buffering mechanism
+ // Add all values the triangulation is based off, too, to check for
+ // validity (see above)
+ pSystemDependentData_Triangulation = rPolygon.addOrReplaceSystemDependentData<SystemDependentData_Triangulation>(
+ ImplGetSystemDependentDataManager(),
+ std::move(aTriangles),
+ fLineWidth,
+ eLineJoin,
+ eLineCap,
+ fMiterMinimumAngle,
+ pStroke);
+ }
+ }
+
+ if(!pSystemDependentData_Triangulation)
+ {
+ return false;
+ }
+
+ // temporarily adjust brush color to pen color
+ // since the line is drawn as an area-polygon
+ const Color aKeepBrushColor = mnBrushColor;
+ mnBrushColor = mnPenColor;
+
+ // create the area-polygon for the line
+ const bool bDrawnOk(
+ drawFilledTriangles(
+ rObjectToDevice,
+ pSystemDependentData_Triangulation->getTriangles(),
+ fTransparency));
+
+ // restore the original brush GC
+ mnBrushColor = aKeepBrushColor;
+ return bDrawnOk;
+}
+
+Color X11SalGraphicsImpl::getPixel( tools::Long nX, tools::Long nY )
+{
+ if( mrParent.bWindow_ && !mrParent.bVirDev_ )
+ {
+ XWindowAttributes aAttrib;
+
+ XGetWindowAttributes( mrParent.GetXDisplay(), mrParent.GetDrawable(), &aAttrib );
+ if( aAttrib.map_state != IsViewable )
+ {
+ SAL_WARN( "vcl", "X11SalGraphics::GetPixel drawable not viewable" );
+ return 0;
+ }
+ }
+
+ XImage *pXImage = XGetImage( mrParent.GetXDisplay(),
+ mrParent.GetDrawable(),
+ nX, nY,
+ 1, 1,
+ AllPlanes,
+ ZPixmap );
+ if( !pXImage )
+ {
+ SAL_WARN( "vcl", "X11SalGraphics::GetPixel !XGetImage()" );
+ return 0;
+ }
+
+ XColor aXColor;
+
+ aXColor.pixel = XGetPixel( pXImage, 0, 0 );
+ XDestroyImage( pXImage );
+
+ return mrParent.GetColormap().GetColor( aXColor.pixel );
+}
+
+std::shared_ptr<SalBitmap> X11SalGraphicsImpl::getBitmap( tools::Long nX, tools::Long nY, tools::Long nDX, tools::Long nDY )
+{
+ bool bFakeWindowBG = false;
+
+ // normalize
+ if( nDX < 0 )
+ {
+ nX += nDX;
+ nDX = -nDX;
+ }
+ if ( nDY < 0 )
+ {
+ nY += nDY;
+ nDY = -nDY;
+ }
+
+ if( mrParent.bWindow_ && !mrParent.bVirDev_ )
+ {
+ XWindowAttributes aAttrib;
+
+ XGetWindowAttributes( mrParent.GetXDisplay(), mrParent.GetDrawable(), &aAttrib );
+ if( aAttrib.map_state != IsViewable )
+ bFakeWindowBG = true;
+ else
+ {
+ tools::Long nOrgDX = nDX, nOrgDY = nDY;
+
+ // clip to window size
+ if ( nX < 0 )
+ {
+ nDX += nX;
+ nX = 0;
+ }
+ if ( nY < 0 )
+ {
+ nDY += nY;
+ nY = 0;
+ }
+ if( nX + nDX > aAttrib.width )
+ nDX = aAttrib.width - nX;
+ if( nY + nDY > aAttrib.height )
+ nDY = aAttrib.height - nY;
+
+ // inside ?
+ if( nDX <= 0 || nDY <= 0 )
+ {
+ bFakeWindowBG = true;
+ nDX = nOrgDX;
+ nDY = nOrgDY;
+ }
+ }
+ }
+
+ std::shared_ptr<X11SalBitmap> pSalBitmap = std::make_shared<X11SalBitmap>();
+ sal_uInt16 nBitCount = GetBitCount();
+ vcl::PixelFormat ePixelFormat = vcl::bitDepthToPixelFormat(nBitCount);
+
+ if( &mrParent.GetDisplay()->GetColormap( mrParent.m_nXScreen ) != &mrParent.GetColormap() )
+ {
+ ePixelFormat = vcl::PixelFormat::N1_BPP;
+ nBitCount = 1;
+ }
+
+ if (nBitCount > 8)
+ ePixelFormat = vcl::PixelFormat::N24_BPP;
+
+ if( ! bFakeWindowBG )
+ pSalBitmap->ImplCreateFromDrawable( mrParent.GetDrawable(), mrParent.m_nXScreen, nBitCount, nX, nY, nDX, nDY );
+ else
+ pSalBitmap->Create( Size( nDX, nDY ), ePixelFormat, BitmapPalette( nBitCount > 8 ? nBitCount : 0 ) );
+
+ return pSalBitmap;
+}
+
+sal_uInt16 X11SalGraphicsImpl::GetBitCount() const
+{
+ return mrParent.GetVisual().GetDepth();
+}
+
+tools::Long X11SalGraphicsImpl::GetGraphicsWidth() const
+{
+ if( mrParent.m_pFrame )
+ return mrParent.m_pFrame->maGeometry.nWidth;
+ else if( mrParent.m_pVDev )
+ return static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetWidth();
+ else
+ return 0;
+}
+
+bool X11SalGraphicsImpl::drawGradient(const tools::PolyPolygon& /*rPolygon*/, const Gradient& /*rGradient*/)
+{
+ return false;
+}
+
+bool X11SalGraphicsImpl::implDrawGradient(basegfx::B2DPolyPolygon const & /*rPolyPolygon*/, SalGradient const & /*rGradient*/)
+{
+ return false;
+}
+
+bool X11SalGraphicsImpl::supportsOperation(OutDevSupportType eType) const
+{
+ bool bRet = false;
+ switch (eType)
+ {
+ case OutDevSupportType::TransparentRect:
+ case OutDevSupportType::B2DDraw:
+ {
+ XRenderPeer& rPeer = XRenderPeer::GetInstance();
+ const SalDisplay* pSalDisp = mrParent.GetDisplay();
+ const SalVisual& rSalVis = pSalDisp->GetVisual(mrParent.GetScreenNumber());
+
+ Visual* pDstXVisual = rSalVis.GetVisual();
+ XRenderPictFormat* pDstVisFmt = rPeer.FindVisualFormat(pDstXVisual);
+ if (pDstVisFmt)
+ bRet = true;
+ }
+ break;
+ default:
+ break;
+ }
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/gdiimpl.hxx b/vcl/unx/generic/gdi/gdiimpl.hxx
new file mode 100644
index 000000000..48211b13d
--- /dev/null
+++ b/vcl/unx/generic/gdi/gdiimpl.hxx
@@ -0,0 +1,297 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <X11/Xlib.h>
+
+#include <unx/x11/x11gdiimpl.h>
+
+#include <salgdiimpl.hxx>
+
+#include <basegfx/polygon/b2dtrapezoid.hxx>
+#include <basegfx/polygon/b2dpolygontriangulator.hxx>
+#include <ControlCacheKey.hxx>
+
+/* From <X11/Intrinsic.h> */
+typedef unsigned long Pixel;
+
+class SalGraphics;
+class SalBitmap;
+class SalPolyLine;
+class X11SalGraphics;
+class Gradient;
+
+class X11SalGraphicsImpl : public SalGraphicsImpl, public X11GraphicsImpl
+{
+private:
+ X11SalGraphics& mrParent;
+
+ Color mnBrushColor;
+ GC mpBrushGC; // Brush attributes
+ Pixel mnBrushPixel;
+
+ bool mbPenGC : 1; // is Pen GC valid
+ bool mbBrushGC : 1; // is Brush GC valid
+ bool mbCopyGC : 1; // is Copy GC valid
+ bool mbInvertGC : 1; // is Invert GC valid
+ bool mbInvert50GC : 1; // is Invert50 GC valid
+ bool mbStippleGC : 1; // is Stipple GC valid
+ bool mbTrackingGC : 1; // is Tracking GC valid
+ bool mbDitherBrush : 1; // is solid or tile
+
+ bool mbXORMode : 1; // is ROP XOR Mode set
+
+ GC mpPenGC; // Pen attributes
+ Color mnPenColor;
+ Pixel mnPenPixel;
+
+
+ GC mpMonoGC;
+ GC mpCopyGC;
+ GC mpMaskGC;
+ GC mpInvertGC;
+ GC mpInvert50GC;
+ GC mpStippleGC;
+ GC mpTrackingGC;
+
+ GC CreateGC( Drawable hDrawable,
+ unsigned long nMask = GCGraphicsExposures );
+
+ GC SelectBrush();
+ GC SelectPen();
+ inline GC GetCopyGC();
+ inline GC GetStippleGC();
+ GC GetTrackingGC();
+ GC GetInvertGC();
+ GC GetInvert50GC();
+
+ void DrawLines( sal_uInt32 nPoints,
+ const SalPolyLine &rPoints,
+ GC pGC,
+ bool bClose
+ );
+
+ XID GetXRenderPicture();
+ bool drawFilledTrapezoids( const basegfx::B2DTrapezoid*, int nTrapCount, double fTransparency );
+ bool drawFilledTriangles(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::triangulator::B2DTriangleVector& rTriangles,
+ double fTransparency);
+
+ tools::Long GetGraphicsHeight() const;
+
+ void drawMaskedBitmap( const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rTransparentBitmap );
+
+ void internalDrawPolyLine( sal_uInt32 nPoints, const Point* pPtAry, bool bClose );
+
+public:
+
+ explicit X11SalGraphicsImpl(X11SalGraphics& rParent);
+
+ virtual void freeResources() override;
+
+ virtual ~X11SalGraphicsImpl() override;
+
+ virtual OUString getRenderBackendName() const override { return "gen"; }
+
+ virtual bool setClipRegion( const vcl::Region& ) override;
+ //
+ // get the depth of the device
+ virtual sal_uInt16 GetBitCount() const override;
+
+ // get the width of the device
+ virtual tools::Long GetGraphicsWidth() const override;
+
+ // set the clip region to empty
+ virtual void ResetClipRegion() override;
+
+ // set the line color to transparent (= don't draw lines)
+
+ virtual void SetLineColor() override;
+
+ // set the line color to a specific color
+ virtual void SetLineColor( Color nColor ) override;
+
+ // set the fill color to transparent (= don't fill)
+ virtual void SetFillColor() override;
+
+ // set the fill color to a specific color, shapes will be
+ // filled accordingly
+ virtual void SetFillColor( Color nColor ) override;
+
+ // enable/disable XOR drawing
+ virtual void SetXORMode( bool bSet, bool bInvertOnly ) override;
+
+ // set line color for raster operations
+ virtual void SetROPLineColor( SalROPColor nROPColor ) override;
+
+ // set fill color for raster operations
+ virtual void SetROPFillColor( SalROPColor nROPColor ) override;
+
+ // draw --> LineColor and FillColor and RasterOp and ClipRegion
+ virtual void drawPixel( tools::Long nX, tools::Long nY ) override;
+ virtual void drawPixel( tools::Long nX, tools::Long nY, Color nColor ) override;
+
+ virtual void drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 ) override;
+
+ virtual void drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+
+ virtual void drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry ) override;
+
+ virtual void drawPolygon( sal_uInt32 nPoints, const Point* pPtAry ) override;
+
+ virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, const Point** pPtAry ) override;
+
+ virtual bool drawPolyPolygon(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolyPolygon&,
+ double fTransparency) override;
+
+ virtual bool drawPolyLine(
+ const basegfx::B2DHomMatrix& rObjectToDevice,
+ const basegfx::B2DPolygon&,
+ double fTransparency,
+ double fLineWidth,
+ const std::vector< double >* pStroke, // MM01
+ basegfx::B2DLineJoin,
+ css::drawing::LineCap,
+ double fMiterMinimumAngle,
+ bool bPixelSnapHairline) override;
+
+ virtual bool drawPolyLineBezier(
+ sal_uInt32 nPoints,
+ const Point* pPtAry,
+ const PolyFlags* pFlgAry ) override;
+
+ virtual bool drawPolygonBezier(
+ sal_uInt32 nPoints,
+ const Point* pPtAry,
+ const PolyFlags* pFlgAry ) override;
+
+ virtual bool drawPolyPolygonBezier(
+ sal_uInt32 nPoly,
+ const sal_uInt32* pPoints,
+ const Point* const* pPtAry,
+ const PolyFlags* const* pFlgAry ) override;
+
+ // CopyArea --> No RasterOp, but ClipRegion
+ virtual void copyArea(
+ tools::Long nDestX, tools::Long nDestY,
+ tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight,
+ bool bWindowInvalidate ) override;
+
+ // CopyBits and DrawBitmap --> RasterOp and ClipRegion
+ // CopyBits() --> pSrcGraphics == NULL, then CopyBits on same Graphics
+ virtual void copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) override;
+
+ virtual void drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap ) override;
+
+ virtual void drawBitmap(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ const SalBitmap& rMaskBitmap ) override;
+
+ virtual void drawMask(
+ const SalTwoRect& rPosAry,
+ const SalBitmap& rSalBitmap,
+ Color nMaskColor ) override;
+
+ virtual std::shared_ptr<SalBitmap> getBitmap( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override;
+
+ virtual Color getPixel( tools::Long nX, tools::Long nY ) override;
+
+ // invert --> ClipRegion (only Windows or VirDevs)
+ virtual void invert(
+ tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ SalInvert nFlags) override;
+
+ virtual void invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags ) override;
+
+ virtual bool drawEPS(
+ tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ void* pPtr,
+ sal_uInt32 nSize ) override;
+
+ /** Blend bitmap with color channels */
+ virtual bool blendBitmap(
+ const SalTwoRect&,
+ const SalBitmap& rBitmap ) override;
+
+ /** Render bitmap by blending using the mask and alpha channel */
+ virtual bool blendAlphaBitmap(
+ const SalTwoRect&,
+ const SalBitmap& rSrcBitmap,
+ const SalBitmap& rMaskBitmap,
+ const SalBitmap& rAlphaBitmap ) override;
+
+ /** Render bitmap with alpha channel
+
+ @param rSourceBitmap
+ Source bitmap to blit
+
+ @param rAlphaBitmap
+ Alpha channel to use for blitting
+
+ @return true, if the operation succeeded, and false
+ otherwise. In this case, clients should try to emulate alpha
+ compositing themselves
+ */
+ virtual bool drawAlphaBitmap(
+ const SalTwoRect&,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap& rAlphaBitmap ) override;
+
+ /** draw transformed bitmap (maybe with alpha) where Null, X, Y define the coordinate system */
+ virtual bool drawTransformedBitmap(
+ const basegfx::B2DPoint& rNull,
+ const basegfx::B2DPoint& rX,
+ const basegfx::B2DPoint& rY,
+ const SalBitmap& rSourceBitmap,
+ const SalBitmap* pAlphaBitmap,
+ double fAlpha) override;
+
+ virtual bool hasFastDrawTransformedBitmap() const override;
+
+ /** Render solid rectangle with given transparency
+
+ @param nTransparency
+ Transparency value (0-255) to use. 0 blits and opaque, 255 a
+ fully transparent rectangle
+ */
+ virtual bool drawAlphaRect(
+ tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ sal_uInt8 nTransparency ) override;
+
+ virtual bool drawGradient(const tools::PolyPolygon& rPolygon, const Gradient& rGradient) override;
+ virtual bool implDrawGradient(basegfx::B2DPolyPolygon const & rPolyPolygon, SalGradient const & rGradient) override;
+
+ virtual bool supportsOperation(OutDevSupportType eType) const override;
+
+public:
+ void Init() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/salbmp.cxx b/vcl/unx/generic/gdi/salbmp.cxx
new file mode 100644
index 000000000..804b50184
--- /dev/null
+++ b/vcl/unx/generic/gdi/salbmp.cxx
@@ -0,0 +1,1001 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <string.h>
+
+#ifdef FREEBSD
+#include <sys/types.h>
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <osl/endian.h>
+#include <sal/log.hxx>
+
+#include <tools/helpers.hxx>
+#include <tools/debug.hxx>
+#include <vcl/bitmap.hxx>
+#include <com/sun/star/beans/XFastPropertySet.hpp>
+
+#include <unx/saldisp.hxx>
+#include <unx/salbmp.h>
+#include <unx/salinst.h>
+#include <unx/x11/xlimits.hxx>
+
+#include <o3tl/safeint.hxx>
+
+#include <config_features.h>
+
+#if defined HAVE_VALGRIND_HEADERS
+#include <valgrind/valgrind.h>
+#endif
+
+#include <memory>
+
+ImplSalBitmapCache* X11SalBitmap::mpCache = nullptr;
+unsigned int X11SalBitmap::mnCacheInstCount = 0;
+
+X11SalBitmap::X11SalBitmap()
+ : mbGrey( false )
+{
+}
+
+X11SalBitmap::~X11SalBitmap()
+{
+ Destroy();
+}
+
+void X11SalBitmap::ImplCreateCache()
+{
+ if( !mnCacheInstCount++ )
+ mpCache = new ImplSalBitmapCache;
+}
+
+void X11SalBitmap::ImplDestroyCache()
+{
+ SAL_WARN_IF( !mnCacheInstCount, "vcl", "X11SalBitmap::ImplDestroyCache(): underflow" );
+
+ if( mnCacheInstCount && !--mnCacheInstCount )
+ {
+ delete mpCache;
+ mpCache = nullptr;
+ }
+}
+
+void X11SalBitmap::ImplRemovedFromCache()
+{
+ mpDDB.reset();
+}
+
+#if defined HAVE_VALGRIND_HEADERS
+namespace
+{
+ void blankExtraSpace(BitmapBuffer* pDIB)
+ {
+ size_t nExtraSpaceInScanLine = pDIB->mnScanlineSize - pDIB->mnWidth * pDIB->mnBitCount / 8;
+ if (nExtraSpaceInScanLine)
+ {
+ for (tools::Long i = 0; i < pDIB->mnHeight; ++i)
+ {
+ sal_uInt8 *pRow = pDIB->mpBits + (i * pDIB->mnScanlineSize);
+ memset(pRow + (pDIB->mnScanlineSize - nExtraSpaceInScanLine), 0, nExtraSpaceInScanLine);
+ }
+ }
+ }
+}
+#endif
+
+std::unique_ptr<BitmapBuffer> X11SalBitmap::ImplCreateDIB(
+ const Size& rSize,
+ vcl::PixelFormat ePixelFormat,
+ const BitmapPalette& rPal)
+{
+ std::unique_ptr<BitmapBuffer> pDIB;
+
+ if( !rSize.Width() || !rSize.Height() )
+ return nullptr;
+
+ try
+ {
+ pDIB.reset(new BitmapBuffer);
+ }
+ catch (const std::bad_alloc&)
+ {
+ return nullptr;
+ }
+
+ pDIB->mnFormat = ScanlineFormat::NONE;
+
+ switch(ePixelFormat)
+ {
+ case vcl::PixelFormat::N1_BPP:
+ pDIB->mnFormat |= ScanlineFormat::N1BitMsbPal;
+ break;
+ case vcl::PixelFormat::N8_BPP:
+ pDIB->mnFormat |= ScanlineFormat::N8BitPal;
+ break;
+ case vcl::PixelFormat::N24_BPP:
+ pDIB->mnFormat |= ScanlineFormat::N24BitTcBgr;
+ break;
+ case vcl::PixelFormat::N32_BPP:
+ default:
+ SAL_WARN("vcl.gdi", "32-bit images not supported, converting to 24-bit");
+ ePixelFormat = vcl::PixelFormat::N24_BPP;
+ pDIB->mnFormat |= ScanlineFormat::N24BitTcBgr;
+ break;
+ }
+
+ sal_uInt16 nColors = 0;
+ if (ePixelFormat <= vcl::PixelFormat::N8_BPP)
+ nColors = vcl::numberOfColors(ePixelFormat);
+
+ pDIB->mnWidth = rSize.Width();
+ pDIB->mnHeight = rSize.Height();
+ tools::Long nScanlineBase;
+ bool bFail = o3tl::checked_multiply<tools::Long>(pDIB->mnWidth, vcl::pixelFormatBitCount(ePixelFormat), nScanlineBase);
+ if (bFail)
+ {
+ SAL_WARN("vcl.gdi", "checked multiply failed");
+ return nullptr;
+ }
+ pDIB->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase);
+ if (pDIB->mnScanlineSize < nScanlineBase/8)
+ {
+ SAL_WARN("vcl.gdi", "scanline calculation wraparound");
+ return nullptr;
+ }
+ pDIB->mnBitCount = vcl::pixelFormatBitCount(ePixelFormat);
+
+ if( nColors )
+ {
+ pDIB->maPalette = rPal;
+ pDIB->maPalette.SetEntryCount( nColors );
+ }
+
+ try
+ {
+ pDIB->mpBits = new sal_uInt8[ pDIB->mnScanlineSize * pDIB->mnHeight ];
+#if defined HAVE_VALGRIND_HEADERS
+ if (RUNNING_ON_VALGRIND)
+ blankExtraSpace(pDIB.get());
+#endif
+ }
+ catch (const std::bad_alloc&)
+ {
+ return nullptr;
+ }
+
+ return pDIB;
+}
+
+std::unique_ptr<BitmapBuffer> X11SalBitmap::ImplCreateDIB(
+ Drawable aDrawable,
+ SalX11Screen nScreen,
+ tools::Long nDrawableDepth,
+ tools::Long nX,
+ tools::Long nY,
+ tools::Long nWidth,
+ tools::Long nHeight,
+ bool bGrey
+) {
+ std::unique_ptr<BitmapBuffer> pDIB;
+
+ if( aDrawable && nWidth && nHeight && nDrawableDepth )
+ {
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ Display* pXDisp = pSalDisp->GetDisplay();
+
+ // do not die on XError here
+ // alternatively one could check the coordinates for being offscreen
+ // but this call can actually work on servers with backing store
+ // defaults even if the rectangle is offscreen
+ // so better catch the XError
+ GetGenericUnixSalData()->ErrorTrapPush();
+ XImage* pImage = XGetImage( pXDisp, aDrawable, nX, nY, nWidth, nHeight, AllPlanes, ZPixmap );
+ bool bWasError = GetGenericUnixSalData()->ErrorTrapPop( false );
+
+ if( ! bWasError && pImage && pImage->data )
+ {
+ const SalTwoRect aTwoRect = { 0, 0, nWidth, nHeight, 0, 0, nWidth, nHeight };
+ BitmapBuffer aSrcBuf;
+ std::optional<BitmapPalette> pDstPal;
+
+ aSrcBuf.mnFormat = ScanlineFormat::TopDown;
+ aSrcBuf.mnWidth = nWidth;
+ aSrcBuf.mnHeight = nHeight;
+ aSrcBuf.mnBitCount = pImage->bits_per_pixel;
+ aSrcBuf.mnScanlineSize = pImage->bytes_per_line;
+ aSrcBuf.mpBits = reinterpret_cast<sal_uInt8*>(pImage->data);
+
+ pImage->red_mask = pSalDisp->GetVisual( nScreen ).red_mask;
+ pImage->green_mask = pSalDisp->GetVisual( nScreen ).green_mask;
+ pImage->blue_mask = pSalDisp->GetVisual( nScreen ).blue_mask;
+
+ switch( aSrcBuf.mnBitCount )
+ {
+ case 1:
+ {
+ aSrcBuf.mnFormat |= ( LSBFirst == pImage->bitmap_bit_order
+ ? ScanlineFormat::N1BitLsbPal
+ : ScanlineFormat::N1BitMsbPal
+ );
+ }
+ break;
+
+ case 8:
+ {
+ aSrcBuf.mnFormat |= ScanlineFormat::N8BitPal;
+ }
+ break;
+
+ case 24:
+ {
+ if( ( LSBFirst == pImage->byte_order ) && ( pImage->red_mask == 0xFF ) )
+ aSrcBuf.mnFormat |= ScanlineFormat::N24BitTcRgb;
+ else
+ aSrcBuf.mnFormat |= ScanlineFormat::N24BitTcBgr;
+ }
+ break;
+
+ case 32:
+ {
+ if( LSBFirst == pImage->byte_order )
+ aSrcBuf.mnFormat |= ( pSalDisp->GetVisual(nScreen).red_mask == 0xFF
+ ? ScanlineFormat::N32BitTcRgba
+ : ScanlineFormat::N32BitTcBgra
+ );
+ else
+ aSrcBuf.mnFormat |= ( pSalDisp->GetVisual(nScreen).red_mask == 0xFF
+ ? ScanlineFormat::N32BitTcAbgr
+ : ScanlineFormat::N32BitTcArgb
+ );
+ }
+ break;
+
+ default: assert(false);
+ }
+
+ BitmapPalette& rPal = aSrcBuf.maPalette;
+
+ if( aSrcBuf.mnBitCount == 1 )
+ {
+ rPal.SetEntryCount( 2 );
+ rPal[ 0 ] = COL_BLACK;
+ rPal[ 1 ] = COL_WHITE;
+ pDstPal = rPal;
+ }
+ else if( pImage->depth == 8 && bGrey )
+ {
+ rPal.SetEntryCount( 256 );
+
+ for( sal_uInt16 i = 0; i < 256; i++ )
+ {
+ BitmapColor& rBmpCol = rPal[ i ];
+
+ rBmpCol.SetRed( i );
+ rBmpCol.SetGreen( i );
+ rBmpCol.SetBlue( i );
+ }
+
+ pDstPal = rPal;
+ }
+ else if( aSrcBuf.mnBitCount <= 8 )
+ {
+ const SalColormap& rColMap = pSalDisp->GetColormap( nScreen );
+ const sal_uInt16 nCols = std::min(static_cast<sal_uLong>(rColMap.GetUsed()),
+ sal_uLong(1) << nDrawableDepth);
+
+ rPal.SetEntryCount( nCols );
+
+ for( sal_uInt16 i = 0; i < nCols; i++ )
+ {
+ const Color nColor( rColMap.GetColor( i ) );
+ BitmapColor& rBmpCol = rPal[ i ];
+
+ rBmpCol.SetRed( nColor.GetRed() );
+ rBmpCol.SetGreen( nColor.GetGreen() );
+ rBmpCol.SetBlue( nColor.GetBlue() );
+ }
+ pDstPal = rPal;
+ }
+
+ pDIB = StretchAndConvert( aSrcBuf, aTwoRect, aSrcBuf.mnFormat,
+ pDstPal, &aSrcBuf.maColorMask );
+ XDestroyImage( pImage );
+ }
+ }
+
+ return pDIB;
+}
+
+XImage* X11SalBitmap::ImplCreateXImage(
+ SalDisplay const *pSalDisp,
+ SalX11Screen nScreen,
+ tools::Long nDepth,
+ const SalTwoRect& rTwoRect
+) const
+{
+ XImage* pImage = nullptr;
+
+ if( !mpDIB && mpDDB )
+ {
+ const_cast<X11SalBitmap*>(this)->mpDIB =
+ ImplCreateDIB( mpDDB->ImplGetPixmap(),
+ mpDDB->ImplGetScreen(),
+ mpDDB->ImplGetDepth(),
+ 0, 0,
+ mpDDB->ImplGetWidth(),
+ mpDDB->ImplGetHeight(),
+ mbGrey );
+ }
+
+ if( mpDIB && mpDIB->mnWidth && mpDIB->mnHeight )
+ {
+ Display* pXDisp = pSalDisp->GetDisplay();
+ tools::Long nWidth = rTwoRect.mnDestWidth;
+ tools::Long nHeight = rTwoRect.mnDestHeight;
+
+ if( 1 == GetBitCount() )
+ nDepth = 1;
+
+ pImage = XCreateImage( pXDisp, pSalDisp->GetVisual( nScreen ).GetVisual(),
+ nDepth, ( 1 == nDepth ) ? XYBitmap :ZPixmap, 0, nullptr,
+ nWidth, nHeight, 32, 0 );
+
+ if( pImage )
+ {
+ std::unique_ptr<BitmapBuffer> pDstBuf;
+ ScanlineFormat nDstFormat = ScanlineFormat::TopDown;
+ std::optional<BitmapPalette> xPal;
+ std::unique_ptr<ColorMask> xMask;
+
+ switch( pImage->bits_per_pixel )
+ {
+ case 1:
+ nDstFormat |= ( LSBFirst == pImage->bitmap_bit_order
+ ? ScanlineFormat::N1BitLsbPal
+ : ScanlineFormat::N1BitMsbPal
+ );
+ break;
+
+ case 8:
+ nDstFormat |= ScanlineFormat::N8BitPal;
+ break;
+
+ case 24:
+ {
+ if( ( LSBFirst == pImage->byte_order ) && ( pImage->red_mask == 0xFF ) )
+ nDstFormat |= ScanlineFormat::N24BitTcRgb;
+ else
+ nDstFormat |= ScanlineFormat::N24BitTcBgr;
+ }
+ break;
+
+ case 32:
+ {
+ if( LSBFirst == pImage->byte_order )
+ nDstFormat |= ( pImage->red_mask == 0xFF
+ ? ScanlineFormat::N32BitTcRgba
+ : ScanlineFormat::N32BitTcBgra
+ );
+ else
+ nDstFormat |= ( pImage->red_mask == 0xFF
+ ? ScanlineFormat::N32BitTcAbgr
+ : ScanlineFormat::N32BitTcArgb
+ );
+ }
+ break;
+
+ default: assert(false);
+ }
+
+ if( pImage->depth == 1 )
+ {
+ xPal.emplace(2);
+ (*xPal)[ 0 ] = COL_BLACK;
+ (*xPal)[ 1 ] = COL_WHITE;
+ }
+ else if( pImage->depth == 8 && mbGrey )
+ {
+ xPal.emplace(256);
+
+ for( sal_uInt16 i = 0; i < 256; i++ )
+ {
+ BitmapColor& rBmpCol = (*xPal)[ i ];
+
+ rBmpCol.SetRed( i );
+ rBmpCol.SetGreen( i );
+ rBmpCol.SetBlue( i );
+ }
+
+ }
+ else if( pImage->depth <= 8 )
+ {
+ const SalColormap& rColMap = pSalDisp->GetColormap( nScreen );
+ const sal_uInt16 nCols = std::min( static_cast<sal_uLong>(rColMap.GetUsed())
+ , static_cast<sal_uLong>(1 << pImage->depth)
+ );
+
+ xPal.emplace(nCols);
+
+ for( sal_uInt16 i = 0; i < nCols; i++ )
+ {
+ const Color nColor( rColMap.GetColor( i ) );
+ BitmapColor& rBmpCol = (*xPal)[ i ];
+
+ rBmpCol.SetRed( nColor.GetRed() );
+ rBmpCol.SetGreen( nColor.GetGreen() );
+ rBmpCol.SetBlue( nColor.GetBlue() );
+ }
+ }
+
+ pDstBuf = StretchAndConvert( *mpDIB, rTwoRect, nDstFormat, xPal, xMask.get() );
+ xPal.reset();
+ xMask.reset();
+
+ if( pDstBuf && pDstBuf->mpBits )
+ {
+#if defined HAVE_VALGRIND_HEADERS
+ if (RUNNING_ON_VALGRIND)
+ blankExtraSpace(pDstBuf.get());
+#endif
+ // set data in buffer as data member in pImage
+ pImage->data = reinterpret_cast<char*>(pDstBuf->mpBits);
+ }
+ else
+ {
+ XDestroyImage( pImage );
+ pImage = nullptr;
+ }
+
+ // note that pDstBuf it deleted here, but that doesn't destroy allocated data in buffer
+ }
+ }
+
+ return pImage;
+}
+
+bool X11SalBitmap::ImplCreateFromDrawable(
+ Drawable aDrawable,
+ SalX11Screen nScreen,
+ tools::Long nDrawableDepth,
+ tools::Long nX,
+ tools::Long nY,
+ tools::Long nWidth,
+ tools::Long nHeight
+) {
+ Destroy();
+
+ if( aDrawable && nWidth && nHeight && nDrawableDepth )
+ mpDDB.reset(new ImplSalDDB( aDrawable, nScreen, nDrawableDepth, nX, nY, nWidth, nHeight ));
+
+ return( mpDDB != nullptr );
+}
+
+ImplSalDDB* X11SalBitmap::ImplGetDDB(
+ Drawable aDrawable,
+ SalX11Screen nXScreen,
+ tools::Long nDrawableDepth,
+ const SalTwoRect& rTwoRect
+) const
+{
+ if( !mpDDB || !mpDDB->ImplMatches( nXScreen, nDrawableDepth, rTwoRect ) )
+ {
+ if( mpDDB )
+ {
+ // do we already have a DIB? if not, create aDIB from current DDB first
+ if( !mpDIB )
+ {
+ const_cast<X11SalBitmap*>(this)->mpDIB = ImplCreateDIB( mpDDB->ImplGetPixmap(),
+ mpDDB->ImplGetScreen(),
+ mpDDB->ImplGetDepth(),
+ 0, 0,
+ mpDDB->ImplGetWidth(),
+ mpDDB->ImplGetHeight(),
+ mbGrey );
+ }
+
+ mpDDB.reset();
+ }
+
+ if( mpCache )
+ mpCache->ImplRemove( this );
+
+ SalTwoRect aTwoRect( rTwoRect );
+ if( aTwoRect.mnSrcX < 0 )
+ {
+ aTwoRect.mnSrcWidth += aTwoRect.mnSrcX;
+ aTwoRect.mnSrcX = 0;
+ }
+ if( aTwoRect.mnSrcY < 0 )
+ {
+ aTwoRect.mnSrcHeight += aTwoRect.mnSrcY;
+ aTwoRect.mnSrcY = 0;
+ }
+
+ // create new DDB from DIB
+ const Size aSize( GetSize() );
+ if( aTwoRect.mnSrcWidth == aTwoRect.mnDestWidth &&
+ aTwoRect.mnSrcHeight == aTwoRect.mnDestHeight )
+ {
+ aTwoRect.mnSrcX = aTwoRect.mnSrcY = aTwoRect.mnDestX = aTwoRect.mnDestY = 0;
+ aTwoRect.mnSrcWidth = aTwoRect.mnDestWidth = aSize.Width();
+ aTwoRect.mnSrcHeight = aTwoRect.mnDestHeight = aSize.Height();
+ }
+ else if( aTwoRect.mnSrcWidth+aTwoRect.mnSrcX > aSize.Width() ||
+ aTwoRect.mnSrcHeight+aTwoRect.mnSrcY > aSize.Height() )
+ {
+ // #i47823# this should not happen at all, but does nonetheless
+ // because BitmapEx allows for mask bitmaps of different size
+ // than image bitmap (broken)
+ if( aTwoRect.mnSrcX >= aSize.Width() ||
+ aTwoRect.mnSrcY >= aSize.Height() )
+ return nullptr; // this would be a really mad case
+
+ if( aTwoRect.mnSrcWidth+aTwoRect.mnSrcX > aSize.Width() )
+ {
+ aTwoRect.mnSrcWidth = aSize.Width()-aTwoRect.mnSrcX;
+ if( aTwoRect.mnSrcWidth < 1 )
+ {
+ aTwoRect.mnSrcX = 0;
+ aTwoRect.mnSrcWidth = aSize.Width();
+ }
+ }
+ if( aTwoRect.mnSrcHeight+aTwoRect.mnSrcY > aSize.Height() )
+ {
+ aTwoRect.mnSrcHeight = aSize.Height() - aTwoRect.mnSrcY;
+ if( aTwoRect.mnSrcHeight < 1 )
+ {
+ aTwoRect.mnSrcY = 0;
+ aTwoRect.mnSrcHeight = aSize.Height();
+ }
+ }
+ }
+
+ XImage* pImage = ImplCreateXImage( vcl_sal::getSalDisplay(GetGenericUnixSalData()), nXScreen,
+ nDrawableDepth, aTwoRect );
+
+ if( pImage )
+ {
+ mpDDB.reset(new ImplSalDDB( pImage, aDrawable, nXScreen, aTwoRect ));
+ delete[] pImage->data;
+ pImage->data = nullptr;
+ XDestroyImage( pImage );
+
+ if( mpCache )
+ mpCache->ImplAdd( const_cast<X11SalBitmap*>(this) );
+ }
+ }
+
+ return mpDDB.get();
+}
+
+void X11SalBitmap::ImplDraw(
+ Drawable aDrawable,
+ SalX11Screen nXScreen,
+ tools::Long nDrawableDepth,
+ const SalTwoRect& rTwoRect,
+ const GC& rGC
+) const
+{
+ ImplGetDDB( aDrawable, nXScreen, nDrawableDepth, rTwoRect );
+ if( mpDDB )
+ mpDDB->ImplDraw( aDrawable, rTwoRect, rGC );
+}
+
+bool X11SalBitmap::Create( const Size& rSize, vcl::PixelFormat ePixelFormat, const BitmapPalette& rPal )
+{
+ Destroy();
+ mpDIB = ImplCreateDIB( rSize, ePixelFormat, rPal );
+
+ return( mpDIB != nullptr );
+}
+
+bool X11SalBitmap::Create( const SalBitmap& rSSalBmp )
+{
+ Destroy();
+
+ auto pX11Bmp = dynamic_cast<const X11SalBitmap*>( &rSSalBmp );
+ if (!pX11Bmp)
+ return false;
+
+ const X11SalBitmap& rSalBmp = *pX11Bmp;
+
+ if( rSalBmp.mpDIB )
+ {
+ // TODO: reference counting...
+ mpDIB.reset(new BitmapBuffer( *rSalBmp.mpDIB ));
+ // TODO: get rid of this when BitmapBuffer gets copy constructor
+ try
+ {
+ mpDIB->mpBits = new sal_uInt8[ mpDIB->mnScanlineSize * mpDIB->mnHeight ];
+#if defined HAVE_VALGRIND_HEADERS
+ if (RUNNING_ON_VALGRIND)
+ blankExtraSpace(mpDIB.get());
+#endif
+ }
+ catch (const std::bad_alloc&)
+ {
+ mpDIB.reset();
+ }
+
+ if( mpDIB )
+ memcpy( mpDIB->mpBits, rSalBmp.mpDIB->mpBits, mpDIB->mnScanlineSize * mpDIB->mnHeight );
+ }
+ else if( rSalBmp.mpDDB )
+ ImplCreateFromDrawable( rSalBmp.mpDDB->ImplGetPixmap(),
+ rSalBmp.mpDDB->ImplGetScreen(),
+ rSalBmp.mpDDB->ImplGetDepth(),
+ 0, 0, rSalBmp.mpDDB->ImplGetWidth(), rSalBmp.mpDDB->ImplGetHeight() );
+
+ return( ( !rSalBmp.mpDIB && !rSalBmp.mpDDB ) ||
+ ( rSalBmp.mpDIB && ( mpDIB != nullptr ) ) ||
+ ( rSalBmp.mpDDB && ( mpDDB != nullptr ) ) );
+}
+
+bool X11SalBitmap::Create( const SalBitmap&, SalGraphics* )
+{
+ return false;
+}
+
+bool X11SalBitmap::Create(const SalBitmap&, vcl::PixelFormat /*eNewPixelFormat*/)
+{
+ return false;
+}
+
+bool X11SalBitmap::Create(
+ const css::uno::Reference< css::rendering::XBitmapCanvas >& rBitmapCanvas,
+ Size& rSize,
+ bool bMask
+) {
+ css::uno::Reference< css::beans::XFastPropertySet > xFastPropertySet( rBitmapCanvas, css::uno::UNO_QUERY );
+
+ if( xFastPropertySet ) {
+ css::uno::Sequence< css::uno::Any > args;
+
+ if( xFastPropertySet->getFastPropertyValue(bMask ? 2 : 1) >>= args ) {
+ sal_Int64 pixmapHandle = {}; // spurious -Werror=maybe-uninitialized
+ sal_Int32 depth;
+ if( ( args[1] >>= pixmapHandle ) && ( args[2] >>= depth ) ) {
+
+ mbGrey = bMask;
+ bool bSuccess = ImplCreateFromDrawable(
+ pixmapHandle,
+ // FIXME: this seems multi-screen broken to me
+ SalX11Screen( 0 ),
+ depth,
+ 0,
+ 0,
+ rSize.Width(),
+ rSize.Height()
+ );
+ bool bFreePixmap = false;
+ if( bSuccess && (args[0] >>= bFreePixmap) && bFreePixmap )
+ XFreePixmap( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay(), pixmapHandle );
+
+ return bSuccess;
+ }
+ }
+ }
+
+ return false;
+}
+
+void X11SalBitmap::Destroy()
+{
+ if( mpDIB )
+ {
+ delete[] mpDIB->mpBits;
+ mpDIB.reset();
+ }
+
+ mpDDB.reset();
+
+ if( mpCache )
+ mpCache->ImplRemove( this );
+}
+
+Size X11SalBitmap::GetSize() const
+{
+ Size aSize;
+
+ if( mpDIB )
+ {
+ aSize.setWidth( mpDIB->mnWidth );
+ aSize.setHeight( mpDIB->mnHeight );
+ }
+ else if( mpDDB )
+ {
+ aSize.setWidth( mpDDB->ImplGetWidth() );
+ aSize.setHeight( mpDDB->ImplGetHeight() );
+ }
+
+ return aSize;
+}
+
+sal_uInt16 X11SalBitmap::GetBitCount() const
+{
+ sal_uInt16 nBitCount;
+
+ if( mpDIB )
+ nBitCount = mpDIB->mnBitCount;
+ else if( mpDDB )
+ nBitCount = mpDDB->ImplGetDepth();
+ else
+ nBitCount = 0;
+
+ return nBitCount;
+}
+
+BitmapBuffer* X11SalBitmap::AcquireBuffer( BitmapAccessMode /*nMode*/ )
+{
+ if( !mpDIB && mpDDB )
+ {
+ mpDIB = ImplCreateDIB(
+ mpDDB->ImplGetPixmap(),
+ mpDDB->ImplGetScreen(),
+ mpDDB->ImplGetDepth(),
+ 0, 0,
+ mpDDB->ImplGetWidth(),
+ mpDDB->ImplGetHeight(),
+ mbGrey
+ );
+ }
+
+ return mpDIB.get();
+}
+
+void X11SalBitmap::ReleaseBuffer( BitmapBuffer*, BitmapAccessMode nMode )
+{
+ if( nMode == BitmapAccessMode::Write )
+ {
+ mpDDB.reset();
+
+ if( mpCache )
+ mpCache->ImplRemove( this );
+ InvalidateChecksum();
+ }
+}
+
+bool X11SalBitmap::GetSystemData( BitmapSystemData& rData )
+{
+ if( mpDDB )
+ {
+ // Rename/retype pDummy to your likings (though X11 Pixmap is
+ // prolly not a good idea, since it's accessed from
+ // non-platform aware code in vcl/bitmap.hxx)
+ rData.aPixmap = reinterpret_cast<void*>(mpDDB->ImplGetPixmap());
+ rData.mnWidth = mpDDB->ImplGetWidth ();
+ rData.mnHeight = mpDDB->ImplGetHeight ();
+ return true;
+ }
+
+ return false;
+}
+
+bool X11SalBitmap::ScalingSupported() const
+{
+ return false;
+}
+
+bool X11SalBitmap::Scale( const double& /*rScaleX*/, const double& /*rScaleY*/, BmpScaleFlag /*nScaleFlag*/ )
+{
+ return false;
+}
+
+bool X11SalBitmap::Replace( const Color& /*rSearchColor*/, const Color& /*rReplaceColor*/, sal_uInt8 /*nTol*/ )
+{
+ return false;
+}
+
+
+ImplSalDDB::ImplSalDDB( XImage* pImage, Drawable aDrawable,
+ SalX11Screen nXScreen, const SalTwoRect& rTwoRect )
+ : maPixmap ( 0 )
+ , maTwoRect ( rTwoRect )
+ , mnDepth ( pImage->depth )
+ , mnXScreen ( nXScreen )
+{
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ Display* pXDisp = pSalDisp->GetDisplay();
+
+ maPixmap = limitXCreatePixmap( pXDisp, aDrawable, ImplGetWidth(), ImplGetHeight(), ImplGetDepth() );
+ if (!maPixmap)
+ return;
+
+ XGCValues aValues;
+ GC aGC;
+ int nValues = GCFunction;
+
+ aValues.function = GXcopy;
+
+ if( 1 == mnDepth )
+ {
+ nValues |= ( GCForeground | GCBackground );
+ aValues.foreground = 1;
+ aValues.background = 0;
+ }
+
+ aGC = XCreateGC( pXDisp, maPixmap, nValues, &aValues );
+ XPutImage( pXDisp, maPixmap, aGC, pImage, 0, 0, 0, 0, maTwoRect.mnDestWidth, maTwoRect.mnDestHeight );
+ XFreeGC( pXDisp, aGC );
+}
+
+ImplSalDDB::ImplSalDDB(
+ Drawable aDrawable,
+ SalX11Screen nXScreen,
+ tools::Long nDrawableDepth,
+ tools::Long nX,
+ tools::Long nY,
+ tools::Long nWidth,
+ tools::Long nHeight
+) : maTwoRect(0, 0, nWidth, nHeight, 0, 0, nWidth, nHeight)
+ , mnDepth( nDrawableDepth )
+ , mnXScreen( nXScreen )
+{
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ Display* pXDisp = pSalDisp->GetDisplay();
+
+ if( (maPixmap = limitXCreatePixmap( pXDisp, aDrawable, nWidth, nHeight, nDrawableDepth )) )
+ {
+ XGCValues aValues;
+ GC aGC;
+ int nValues = GCFunction;
+
+ aValues.function = GXcopy;
+
+ if( 1 == mnDepth )
+ {
+ nValues |= ( GCForeground | GCBackground );
+ aValues.foreground = 1;
+ aValues.background = 0;
+ }
+
+ aGC = XCreateGC( pXDisp, maPixmap, nValues, &aValues );
+ ImplDraw( aDrawable, nDrawableDepth, maPixmap,
+ nX, nY, nWidth, nHeight, 0, 0, aGC );
+ XFreeGC( pXDisp, aGC );
+ }
+ else
+ {
+ maTwoRect.mnSrcWidth = maTwoRect.mnDestWidth = 0;
+ maTwoRect.mnSrcHeight = maTwoRect.mnDestHeight = 0;
+ }
+}
+
+ImplSalDDB::~ImplSalDDB()
+{
+ if( maPixmap && ImplGetSVData() )
+ XFreePixmap( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay(), maPixmap );
+}
+
+bool ImplSalDDB::ImplMatches( SalX11Screen nXScreen, tools::Long nDepth, const SalTwoRect& rTwoRect ) const
+{
+ bool bRet = false;
+
+ if( ( maPixmap != 0 ) && ( ( mnDepth == nDepth ) || ( 1 == mnDepth ) ) && nXScreen == mnXScreen)
+ {
+ if ( rTwoRect.mnSrcX == maTwoRect.mnSrcX
+ && rTwoRect.mnSrcY == maTwoRect.mnSrcY
+ && rTwoRect.mnSrcWidth == maTwoRect.mnSrcWidth
+ && rTwoRect.mnSrcHeight == maTwoRect.mnSrcHeight
+ && rTwoRect.mnDestWidth == maTwoRect.mnDestWidth
+ && rTwoRect.mnDestHeight == maTwoRect.mnDestHeight
+ )
+ {
+ // absolutely identically
+ bRet = true;
+ }
+ else if( rTwoRect.mnSrcWidth == rTwoRect.mnDestWidth
+ && rTwoRect.mnSrcHeight == rTwoRect.mnDestHeight
+ && maTwoRect.mnSrcWidth == maTwoRect.mnDestWidth
+ && maTwoRect.mnSrcHeight == maTwoRect.mnDestHeight
+ && rTwoRect.mnSrcX >= maTwoRect.mnSrcX
+ && rTwoRect.mnSrcY >= maTwoRect.mnSrcY
+ && ( rTwoRect.mnSrcX + rTwoRect.mnSrcWidth ) <= ( maTwoRect.mnSrcX + maTwoRect.mnSrcWidth )
+ && ( rTwoRect.mnSrcY + rTwoRect.mnSrcHeight ) <= ( maTwoRect.mnSrcY + maTwoRect.mnSrcHeight )
+ )
+ {
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+void ImplSalDDB::ImplDraw(
+ Drawable aDrawable,
+ const SalTwoRect& rTwoRect,
+ const GC& rGC
+) const
+{
+ ImplDraw( maPixmap, mnDepth, aDrawable,
+ rTwoRect.mnSrcX - maTwoRect.mnSrcX, rTwoRect.mnSrcY - maTwoRect.mnSrcY,
+ rTwoRect.mnDestWidth, rTwoRect.mnDestHeight,
+ rTwoRect.mnDestX, rTwoRect.mnDestY, rGC );
+}
+
+void ImplSalDDB::ImplDraw(
+ Drawable aSrcDrawable,
+ tools::Long nSrcDrawableDepth,
+ Drawable aDstDrawable,
+ tools::Long nSrcX,
+ tools::Long nSrcY,
+ tools::Long nDestWidth,
+ tools::Long nDestHeight,
+ tools::Long nDestX,
+ tools::Long nDestY,
+ const GC& rGC
+) {
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ Display* pXDisp = pSalDisp->GetDisplay();
+
+ if( 1 == nSrcDrawableDepth )
+ {
+ XCopyPlane( pXDisp, aSrcDrawable, aDstDrawable, rGC,
+ nSrcX, nSrcY, nDestWidth, nDestHeight, nDestX, nDestY, 1 );
+ }
+ else
+ {
+ XCopyArea( pXDisp, aSrcDrawable, aDstDrawable, rGC,
+ nSrcX, nSrcY, nDestWidth, nDestHeight, nDestX, nDestY );
+ }
+}
+
+
+ImplSalBitmapCache::ImplSalBitmapCache()
+{
+}
+
+ImplSalBitmapCache::~ImplSalBitmapCache()
+{
+ ImplClear();
+}
+
+void ImplSalBitmapCache::ImplAdd( X11SalBitmap* pBmp )
+{
+ for(auto pObj : maBmpList)
+ {
+ if( pObj == pBmp )
+ return;
+ }
+ maBmpList.push_back( pBmp );
+}
+
+void ImplSalBitmapCache::ImplRemove( X11SalBitmap const * pBmp )
+{
+ auto it = std::find(maBmpList.begin(), maBmpList.end(), pBmp);
+ if( it != maBmpList.end() )
+ {
+ (*it)->ImplRemovedFromCache();
+ maBmpList.erase( it );
+ }
+}
+
+void ImplSalBitmapCache::ImplClear()
+{
+ for(auto pObj : maBmpList)
+ {
+ pObj->ImplRemovedFromCache();
+ }
+ maBmpList.clear();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/salgdi.cxx b/vcl/unx/generic/gdi/salgdi.cxx
new file mode 100644
index 000000000..d6eecfecb
--- /dev/null
+++ b/vcl/unx/generic/gdi/salgdi.cxx
@@ -0,0 +1,482 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_features.h>
+#include <vcl/skia/SkiaHelper.hxx>
+#if HAVE_FEATURE_SKIA
+#include <skia/x11/gdiimpl.hxx>
+#include <skia/x11/textrender.hxx>
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/Xrender.h>
+
+
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+
+#include <headless/svpgdi.hxx>
+
+#include <vcl/sysdata.hxx>
+#include <vcl/virdev.hxx>
+#include <sal/log.hxx>
+
+#include <unx/salunx.h>
+#include <unx/saldisp.hxx>
+#include <unx/salgdi.h>
+#include <unx/x11/xlimits.hxx>
+
+#include <salframe.hxx>
+#include <salgdiimpl.hxx>
+#include <textrender.hxx>
+#include <salvd.hxx>
+#include "gdiimpl.hxx"
+
+#include <unx/x11/x11cairotextrender.hxx>
+#include <unx/x11/xrender_peer.hxx>
+#include "cairo_xlib_cairo.hxx"
+#include <cairo-xlib.h>
+
+#if ENABLE_CAIRO_CANVAS
+#include "X11CairoSalGraphicsImpl.hxx"
+#endif
+
+
+// X11Common
+
+X11Common::X11Common()
+ : m_hDrawable(None)
+ , m_pColormap(nullptr)
+ , m_pExternalSurface(nullptr)
+{}
+
+cairo_t* X11Common::getCairoContext()
+{
+ if (m_pExternalSurface)
+ return cairo_create(m_pExternalSurface);
+
+ cairo_surface_t* surface = cairo_xlib_surface_create(GetXDisplay(), m_hDrawable, GetVisual().visual, SAL_MAX_INT16, SAL_MAX_INT16);
+
+ cairo_t *cr = cairo_create(surface);
+ cairo_surface_destroy(surface);
+
+ return cr;
+}
+
+void X11Common::releaseCairoContext(cairo_t* cr)
+{
+ cairo_destroy(cr);
+}
+
+bool X11Common::SupportsCairo() const
+{
+ static bool bSupportsCairo = [this] {
+ Display *pDisplay = GetXDisplay();
+ int nDummy;
+ return XQueryExtension(pDisplay, "RENDER", &nDummy, &nDummy, &nDummy);
+ }();
+ return bSupportsCairo;
+}
+
+// X11SalGraphics
+
+X11SalGraphics::X11SalGraphics():
+ m_pFrame(nullptr),
+ m_pVDev(nullptr),
+ m_nXScreen( 0 ),
+ m_pXRenderFormat(nullptr),
+ m_aXRenderPicture(0),
+ mpClipRegion(nullptr),
+ hBrush_(None),
+ bWindow_(false),
+ bVirDev_(false)
+{
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled())
+ {
+ mxImpl.reset(new X11SkiaSalGraphicsImpl(*this));
+ mxTextRenderImpl.reset(new SkiaTextRender);
+ }
+ else
+#endif
+ {
+ mxTextRenderImpl.reset(new X11CairoTextRender(*this));
+#if ENABLE_CAIRO_CANVAS
+ mxImpl.reset(new X11CairoSalGraphicsImpl(*this, maX11Common));
+#else
+ mxImpl.reset(new X11SalGraphicsImpl(*this));
+#endif
+ }
+}
+
+X11SalGraphics::~X11SalGraphics() COVERITY_NOEXCEPT_FALSE
+{
+ DeInit();
+ ReleaseFonts();
+ freeResources();
+}
+
+void X11SalGraphics::freeResources()
+{
+ Display *pDisplay = GetXDisplay();
+
+ if( mpClipRegion )
+ {
+ XDestroyRegion( mpClipRegion );
+ mpClipRegion = nullptr;
+ }
+
+ mxImpl->freeResources();
+
+ if( hBrush_ )
+ {
+ XFreePixmap( pDisplay, hBrush_ );
+ hBrush_ = None;
+ }
+ if( m_pDeleteColormap )
+ {
+ m_pDeleteColormap.reset();
+ maX11Common.m_pColormap = nullptr;
+ }
+ if( m_aXRenderPicture )
+ {
+ XRenderPeer::GetInstance().FreePicture( m_aXRenderPicture );
+ m_aXRenderPicture = 0;
+ }
+}
+
+SalGraphicsImpl* X11SalGraphics::GetImpl() const
+{
+ return mxImpl.get();
+}
+
+void X11SalGraphics::SetDrawable(Drawable aDrawable, cairo_surface_t* pExternalSurface, SalX11Screen nXScreen)
+{
+ maX11Common.m_pExternalSurface = pExternalSurface;
+
+ // shortcut if nothing changed
+ if( maX11Common.m_hDrawable == aDrawable )
+ return;
+
+ // free screen specific resources if needed
+ if( nXScreen != m_nXScreen )
+ {
+ freeResources();
+ maX11Common.m_pColormap = &vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetColormap( nXScreen );
+ m_nXScreen = nXScreen;
+ }
+
+ maX11Common.m_hDrawable = aDrawable;
+ SetXRenderFormat( nullptr );
+ if( m_aXRenderPicture )
+ {
+ XRenderPeer::GetInstance().FreePicture( m_aXRenderPicture );
+ m_aXRenderPicture = 0;
+ }
+}
+
+void X11SalGraphics::Init( SalFrame *pFrame, Drawable aTarget,
+ SalX11Screen nXScreen )
+{
+ maX11Common.m_pColormap = &vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetColormap(nXScreen);
+ m_nXScreen = nXScreen;
+
+ m_pFrame = pFrame;
+ m_pVDev = nullptr;
+
+ bWindow_ = true;
+ bVirDev_ = false;
+
+ SetDrawable(aTarget, nullptr, nXScreen);
+ mxImpl->Init();
+}
+
+void X11SalGraphics::DeInit()
+{
+ mxImpl->DeInit();
+ SetDrawable(None, nullptr, m_nXScreen);
+}
+
+void X11SalGraphics::SetClipRegion( GC pGC, Region pXReg ) const
+{
+ Display *pDisplay = GetXDisplay();
+
+ int n = 0;
+ Region Regions[3];
+
+ if( mpClipRegion )
+ Regions[n++] = mpClipRegion;
+
+ if( pXReg && !XEmptyRegion( pXReg ) )
+ Regions[n++] = pXReg;
+
+ if( 0 == n )
+ XSetClipMask( pDisplay, pGC, None );
+ else if( 1 == n )
+ XSetRegion( pDisplay, pGC, Regions[0] );
+ else
+ {
+ Region pTmpRegion = XCreateRegion();
+ XIntersectRegion( Regions[0], Regions[1], pTmpRegion );
+
+ XSetRegion( pDisplay, pGC, pTmpRegion );
+ XDestroyRegion( pTmpRegion );
+ }
+}
+
+// Calculate a dither-pixmap and make a brush of it
+#define P_DELTA 51
+#define DMAP( v, m ) ((v % P_DELTA) > m ? (v / P_DELTA) + 1 : (v / P_DELTA))
+
+bool X11SalGraphics::GetDitherPixmap( Color nColor )
+{
+ static const short nOrdDither8Bit[ 8 ][ 8 ] =
+ {
+ { 0, 38, 9, 48, 2, 40, 12, 50},
+ {25, 12, 35, 22, 28, 15, 37, 24},
+ { 6, 44, 3, 41, 8, 47, 5, 44},
+ {32, 19, 28, 16, 34, 21, 31, 18},
+ { 1, 40, 11, 49, 0, 39, 10, 48},
+ {27, 14, 36, 24, 26, 13, 36, 23},
+ { 8, 46, 4, 43, 7, 45, 4, 42},
+ {33, 20, 30, 17, 32, 20, 29, 16}
+ };
+
+ // test for correct depth (8bit)
+ if( GetColormap().GetVisual().GetDepth() != 8 )
+ return false;
+
+ char pBits[64];
+ char *pBitsPtr = pBits;
+
+ // Set the palette-entries for the dithering tile
+ sal_uInt8 nColorRed = nColor.GetRed();
+ sal_uInt8 nColorGreen = nColor.GetGreen();
+ sal_uInt8 nColorBlue = nColor.GetBlue();
+
+ for(auto & nY : nOrdDither8Bit)
+ {
+ for( int nX = 0; nX < 8; nX++ )
+ {
+ short nMagic = nY[nX];
+ sal_uInt8 nR = P_DELTA * DMAP( nColorRed, nMagic );
+ sal_uInt8 nG = P_DELTA * DMAP( nColorGreen, nMagic );
+ sal_uInt8 nB = P_DELTA * DMAP( nColorBlue, nMagic );
+
+ *pBitsPtr++ = GetColormap().GetPixel( Color( nR, nG, nB ) );
+ }
+ }
+
+ // create the tile as ximage and an according pixmap -> caching
+ XImage *pImage = XCreateImage( GetXDisplay(),
+ GetColormap().GetXVisual(),
+ 8,
+ ZPixmap,
+ 0, // offset
+ pBits, // data
+ 8, 8, // width & height
+ 8, // bitmap_pad
+ 0 ); // (default) bytes_per_line
+
+ if( !hBrush_ )
+ hBrush_ = limitXCreatePixmap( GetXDisplay(), GetDrawable(), 8, 8, 8 );
+
+ // put the ximage to the pixmap
+ XPutImage( GetXDisplay(),
+ hBrush_,
+ GetDisplay()->GetCopyGC( m_nXScreen ),
+ pImage,
+ 0, 0, // Source
+ 0, 0, // Destination
+ 8, 8 ); // width & height
+
+ // destroy image-frame but not palette-data
+ pImage->data = nullptr;
+ XDestroyImage( pImage );
+
+ return true;
+}
+
+void X11SalGraphics::GetResolution( sal_Int32 &rDPIX, sal_Int32 &rDPIY ) // const
+{
+ char* pForceDpi;
+ if ((pForceDpi = getenv("SAL_FORCEDPI")))
+ {
+ OString sForceDPI(pForceDpi);
+ rDPIX = rDPIY = sForceDPI.toInt32();
+ return;
+ }
+
+ const SalDisplay *pDisplay = GetDisplay();
+ if (!pDisplay)
+ {
+ SAL_WARN( "vcl", "Null display");
+ rDPIX = rDPIY = 96;
+ return;
+ }
+
+ Pair dpi = pDisplay->GetResolution();
+ rDPIX = dpi.A();
+ rDPIY = dpi.B();
+
+ if ( rDPIY > 200 )
+ {
+ rDPIX = Divide( rDPIX * 200, rDPIY );
+ rDPIY = 200;
+ }
+
+ // #i12705# equalize x- and y-resolution if they are close enough
+ if( rDPIX == rDPIY )
+ return;
+
+ // different x- and y- resolutions are usually artifacts of
+ // a wrongly calculated screen size.
+#ifdef DEBUG
+ SAL_INFO("vcl.gdi", "Forcing Resolution from "
+ << std::hex << rDPIX
+ << std::dec << rDPIX
+ << " to "
+ << std::hex << rDPIY
+ << std::dec << rDPIY);
+#endif
+ rDPIX = rDPIY; // y-resolution is more trustworthy
+}
+
+XRenderPictFormat* X11SalGraphics::GetXRenderFormat() const
+{
+ if( m_pXRenderFormat == nullptr )
+ m_pXRenderFormat = XRenderPeer::GetInstance().FindVisualFormat( GetVisual().visual );
+ return m_pXRenderFormat;
+}
+
+SystemGraphicsData X11SalGraphics::GetGraphicsData() const
+{
+ SystemGraphicsData aRes;
+
+ aRes.nSize = sizeof(aRes);
+ aRes.pDisplay = GetXDisplay();
+ aRes.hDrawable = maX11Common.m_hDrawable;
+ aRes.pVisual = GetVisual().visual;
+ aRes.nScreen = m_nXScreen.getXScreen();
+ aRes.pXRenderFormat = m_pXRenderFormat;
+ return aRes;
+}
+
+void X11SalGraphics::Flush()
+{
+ if( X11GraphicsImpl* x11Impl = dynamic_cast< X11GraphicsImpl* >( mxImpl.get()))
+ x11Impl->Flush();
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+bool X11SalGraphics::SupportsCairo() const
+{
+ return maX11Common.SupportsCairo();
+}
+
+cairo::SurfaceSharedPtr X11SalGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const
+{
+ return std::make_shared<cairo::X11Surface>(rSurface);
+}
+
+namespace
+{
+ cairo::X11SysData getSysData( const vcl::Window& rWindow )
+ {
+ const SystemEnvData* pSysData = rWindow.GetSystemData();
+
+ if( !pSysData )
+ return cairo::X11SysData();
+ else
+ return cairo::X11SysData(*pSysData, rWindow.ImplGetFrame());
+ }
+
+ cairo::X11SysData getSysData( const VirtualDevice& rVirDev )
+ {
+ return cairo::X11SysData( rVirDev.GetSystemGfxData() );
+ }
+}
+
+cairo::SurfaceSharedPtr X11SalGraphics::CreateSurface( const OutputDevice& rRefDevice,
+ int x, int y, int width, int height ) const
+{
+ if( rRefDevice.GetOutDevType() == OUTDEV_WINDOW )
+ return std::make_shared<cairo::X11Surface>(getSysData(*rRefDevice.GetOwnerWindow()),
+ x,y,width,height);
+ if( rRefDevice.IsVirtual() )
+ return std::make_shared<cairo::X11Surface>(getSysData(static_cast<const VirtualDevice&>(rRefDevice)),
+ x,y,width,height);
+ return cairo::SurfaceSharedPtr();
+}
+
+cairo::SurfaceSharedPtr X11SalGraphics::CreateBitmapSurface( const OutputDevice& rRefDevice,
+ const BitmapSystemData& rData,
+ const Size& rSize ) const
+{
+ SAL_INFO("vcl", "requested size: " << rSize.Width() << " x " << rSize.Height()
+ << " available size: " << rData.mnWidth << " x "
+ << rData.mnHeight);
+ if ( rData.mnWidth == rSize.Width() && rData.mnHeight == rSize.Height() )
+ {
+ if( rRefDevice.GetOutDevType() == OUTDEV_WINDOW )
+ return std::make_shared<cairo::X11Surface>(getSysData(*rRefDevice.GetOwnerWindow()), rData );
+ else if( rRefDevice.IsVirtual() )
+ return std::make_shared<cairo::X11Surface>(getSysData(static_cast<const VirtualDevice&>(rRefDevice)), rData );
+ }
+
+ return cairo::SurfaceSharedPtr();
+}
+
+css::uno::Any X11SalGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& rSurface, const basegfx::B2ISize& /*rSize*/) const
+{
+ cairo::X11Surface& rXlibSurface=dynamic_cast<cairo::X11Surface&>(*rSurface);
+ css::uno::Sequence< css::uno::Any > args{
+ css::uno::Any(false), // do not call XFreePixmap on it
+ css::uno::Any(sal_Int64(rXlibSurface.getPixmap()->mhDrawable)),
+ css::uno::Any(sal_Int32( rXlibSurface.getDepth() ))
+ };
+ return css::uno::Any(args);
+}
+
+#endif // ENABLE_CAIRO_CANVAS
+
+SalGeometryProvider *X11SalGraphics::GetGeometryProvider() const
+{
+ if (m_pFrame)
+ return static_cast< SalGeometryProvider * >(m_pFrame);
+ else
+ return static_cast< SalGeometryProvider * >(m_pVDev);
+}
+
+cairo_t* X11SalGraphics::getCairoContext()
+{
+ return maX11Common.getCairoContext();
+}
+
+void X11SalGraphics::releaseCairoContext(cairo_t* cr)
+{
+ X11Common::releaseCairoContext(cr);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/salgdi2.cxx b/vcl/unx/generic/gdi/salgdi2.cxx
new file mode 100644
index 000000000..f26048ae1
--- /dev/null
+++ b/vcl/unx/generic/gdi/salgdi2.cxx
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <salgdiimpl.hxx>
+
+#include <vcl/sysdata.hxx>
+
+#include <unx/saldisp.hxx>
+#include <unx/salgdi.h>
+#include <unx/x11/xrender_peer.hxx>
+#include <salframe.hxx>
+
+extern "C"
+{
+ static Bool GraphicsExposePredicate( Display*, XEvent* pEvent, const XPointer pFrameWindow )
+ {
+ Bool bRet = False;
+ if( (pEvent->type == GraphicsExpose || pEvent->type == NoExpose) &&
+ pEvent->xnoexpose.drawable == reinterpret_cast<Drawable>(pFrameWindow) )
+ {
+ bRet = True;
+ }
+ return bRet;
+ }
+}
+
+void X11SalGraphics::YieldGraphicsExpose()
+{
+ // get frame if necessary
+ SalFrame* pFrame = m_pFrame;
+ Display* pDisplay = GetXDisplay();
+ ::Window aWindow = GetDrawable();
+ if( ! pFrame )
+ {
+ for (auto pSalFrame : vcl_sal::getSalDisplay(GetGenericUnixSalData())->getFrames() )
+ {
+ const SystemEnvData* pEnvData = pSalFrame->GetSystemData();
+ if( Drawable(pEnvData->GetWindowHandle(pSalFrame)) == aWindow )
+ {
+ pFrame = pSalFrame;
+ break;
+ }
+ }
+ if( ! pFrame )
+ return;
+ }
+
+ XEvent aEvent;
+ while( XCheckTypedWindowEvent( pDisplay, aWindow, Expose, &aEvent ) )
+ {
+ SalPaintEvent aPEvt( aEvent.xexpose.x, aEvent.xexpose.y, aEvent.xexpose.width+1, aEvent.xexpose.height+1 );
+ pFrame->CallCallback( SalEvent::Paint, &aPEvt );
+ }
+
+ do
+ {
+ if( ! GetDisplay()->XIfEventWithTimeout( &aEvent, reinterpret_cast<XPointer>(aWindow), GraphicsExposePredicate ) )
+ // this should not happen at all; still sometimes it happens
+ break;
+
+ if( aEvent.type == NoExpose )
+ break;
+
+ if( pFrame )
+ {
+ SalPaintEvent aPEvt( aEvent.xgraphicsexpose.x, aEvent.xgraphicsexpose.y, aEvent.xgraphicsexpose.width+1, aEvent.xgraphicsexpose.height+1 );
+ pFrame->CallCallback( SalEvent::Paint, &aPEvt );
+ }
+ } while( aEvent.xgraphicsexpose.count != 0 );
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/salvd.cxx b/vcl/unx/generic/gdi/salvd.cxx
new file mode 100644
index 000000000..f5e4449c6
--- /dev/null
+++ b/vcl/unx/generic/gdi/salvd.cxx
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/sysdata.hxx>
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrender.h>
+
+#include <unx/saldisp.hxx>
+#include <unx/salinst.h>
+#include <unx/salgdi.h>
+#include <unx/salvd.h>
+#include <unx/x11/xlimits.hxx>
+
+#include <config_features.h>
+#include <vcl/skia/SkiaHelper.hxx>
+#if HAVE_FEATURE_SKIA
+#include <skia/x11/salvd.hxx>
+#endif
+
+std::unique_ptr<SalVirtualDevice> X11SalInstance::CreateX11VirtualDevice(const SalGraphics& rGraphics,
+ tools::Long &nDX, tools::Long &nDY, DeviceFormat eFormat, const SystemGraphicsData *pData,
+ std::unique_ptr<X11SalGraphics> pNewGraphics)
+{
+ assert(pNewGraphics);
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled())
+ return std::unique_ptr<SalVirtualDevice>(new X11SkiaSalVirtualDevice(rGraphics, nDX, nDY, pData, std::move(pNewGraphics)));
+ else
+#endif
+ return std::unique_ptr<SalVirtualDevice>(new X11SalVirtualDevice(rGraphics, nDX, nDY, eFormat, pData, std::move(pNewGraphics)));
+}
+
+std::unique_ptr<SalVirtualDevice> X11SalInstance::CreateVirtualDevice(SalGraphics& rGraphics,
+ tools::Long &nDX, tools::Long &nDY, DeviceFormat eFormat, const SystemGraphicsData *pData)
+{
+ return CreateX11VirtualDevice(rGraphics, nDX, nDY, eFormat, pData, std::make_unique<X11SalGraphics>());
+}
+
+void X11SalGraphics::Init( X11SalVirtualDevice *pDevice, cairo_surface_t* pPreExistingTarget, SalColormap* pColormap,
+ bool bDeleteColormap )
+{
+ SalDisplay *pDisplay = pDevice->GetDisplay();
+ m_nXScreen = pDevice->GetXScreenNumber();
+
+ int nVisualDepth = pDisplay->GetColormap( m_nXScreen ).GetVisual().GetDepth();
+ int nDeviceDepth = pDevice->GetDepth();
+
+ if( pColormap )
+ {
+ maX11Common.m_pColormap = pColormap;
+ if( bDeleteColormap )
+ m_pDeleteColormap.reset(pColormap);
+ }
+ else if( nDeviceDepth == nVisualDepth )
+ maX11Common.m_pColormap = &pDisplay->GetColormap( m_nXScreen );
+ else if( nDeviceDepth == 1 )
+ {
+ m_pDeleteColormap.reset(new SalColormap());
+ maX11Common.m_pColormap = m_pDeleteColormap.get();
+ }
+
+ m_pVDev = pDevice;
+ m_pFrame = nullptr;
+
+ bWindow_ = pDisplay->IsDisplay();
+ bVirDev_ = true;
+
+ SetDrawable(pDevice->GetDrawable(), pPreExistingTarget, m_nXScreen);
+ mxImpl->Init();
+}
+
+X11SalVirtualDevice::X11SalVirtualDevice(const SalGraphics& rGraphics, tools::Long &nDX, tools::Long &nDY,
+ DeviceFormat /*eFormat*/, const SystemGraphicsData *pData,
+ std::unique_ptr<X11SalGraphics> pNewGraphics) :
+ pGraphics_(std::move(pNewGraphics)),
+ m_nXScreen(0),
+ bGraphics_(false)
+{
+ SalColormap* pColormap = nullptr;
+ bool bDeleteColormap = false;
+
+ sal_uInt16 nBitCount = rGraphics.GetBitCount();
+ pDisplay_ = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ nDepth_ = nBitCount;
+
+ if( pData && pData->hDrawable != None )
+ {
+ ::Window aRoot;
+ int x, y;
+ unsigned int w = 0, h = 0, bw, d;
+ Display* pDisp = pDisplay_->GetDisplay();
+ XGetGeometry( pDisp, pData->hDrawable,
+ &aRoot, &x, &y, &w, &h, &bw, &d );
+ int nScreen = 0;
+ while( nScreen < ScreenCount( pDisp ) )
+ {
+ if( RootWindow( pDisp, nScreen ) == aRoot )
+ break;
+ nScreen++;
+ }
+ nDX_ = static_cast<tools::Long>(w);
+ nDY_ = static_cast<tools::Long>(h);
+ nDX = nDX_;
+ nDY = nDY_;
+ m_nXScreen = SalX11Screen( nScreen );
+ hDrawable_ = pData->hDrawable;
+ bExternPixmap_ = true;
+ }
+ else
+ {
+ nDX_ = nDX;
+ nDY_ = nDY;
+ m_nXScreen = static_cast<const X11SalGraphics&>(rGraphics).GetScreenNumber();
+ hDrawable_ = limitXCreatePixmap( GetXDisplay(),
+ pDisplay_->GetDrawable( m_nXScreen ),
+ nDX_, nDY_,
+ GetDepth() );
+ bExternPixmap_ = false;
+ }
+
+ XRenderPictFormat* pXRenderFormat = pData ? static_cast<XRenderPictFormat*>(pData->pXRenderFormat) : nullptr;
+ if( pXRenderFormat )
+ {
+ pGraphics_->SetXRenderFormat( pXRenderFormat );
+ if( pXRenderFormat->colormap )
+ pColormap = new SalColormap( pDisplay_, pXRenderFormat->colormap, m_nXScreen );
+ else
+ pColormap = new SalColormap( nBitCount );
+ bDeleteColormap = true;
+ }
+ else if( nBitCount != pDisplay_->GetVisual( m_nXScreen ).GetDepth() )
+ {
+ pColormap = new SalColormap( nBitCount );
+ bDeleteColormap = true;
+ }
+
+ pGraphics_->SetLayout( SalLayoutFlags::NONE ); // by default no! mirroring for VirtualDevices, can be enabled with EnableRTL()
+
+ // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget
+ cairo_surface_t* pPreExistingTarget = pData ? static_cast<cairo_surface_t*>(pData->pSurface) : nullptr;
+
+ pGraphics_->Init( this, pPreExistingTarget, pColormap, bDeleteColormap );
+}
+
+X11SalVirtualDevice::~X11SalVirtualDevice()
+{
+ pGraphics_.reset();
+
+ if( GetDrawable() && !bExternPixmap_ )
+ XFreePixmap( GetXDisplay(), GetDrawable() );
+}
+
+SalGraphics* X11SalVirtualDevice::AcquireGraphics()
+{
+ if( bGraphics_ )
+ return nullptr;
+
+ if( pGraphics_ )
+ bGraphics_ = true;
+
+ return pGraphics_.get();
+}
+
+void X11SalVirtualDevice::ReleaseGraphics( SalGraphics* )
+{ bGraphics_ = false; }
+
+bool X11SalVirtualDevice::SetSize( tools::Long nDX, tools::Long nDY )
+{
+ if( bExternPixmap_ )
+ return false;
+
+ if( !nDX ) nDX = 1;
+ if( !nDY ) nDY = 1;
+
+ Pixmap h = limitXCreatePixmap( GetXDisplay(),
+ pDisplay_->GetDrawable( m_nXScreen ),
+ nDX, nDY, nDepth_ );
+
+ if( !h )
+ {
+ if( !GetDrawable() )
+ {
+ hDrawable_ = limitXCreatePixmap( GetXDisplay(),
+ pDisplay_->GetDrawable( m_nXScreen ),
+ 1, 1, nDepth_ );
+ nDX_ = 1;
+ nDY_ = 1;
+ }
+ return false;
+ }
+
+ if( GetDrawable() )
+ XFreePixmap( GetXDisplay(), GetDrawable() );
+ hDrawable_ = h;
+
+ nDX_ = nDX;
+ nDY_ = nDY;
+
+ if( pGraphics_ )
+ pGraphics_->Init( this );
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/x11cairotextrender.cxx b/vcl/unx/generic/gdi/x11cairotextrender.cxx
new file mode 100644
index 000000000..6bbbbc1bf
--- /dev/null
+++ b/vcl/unx/generic/gdi/x11cairotextrender.cxx
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unx/x11/x11cairotextrender.hxx>
+
+#include <unx/glyphcache.hxx>
+#include <X11/Xregion.h>
+#include <cairo.h>
+#include <salframe.hxx>
+#include <salvd.hxx>
+
+X11CairoTextRender::X11CairoTextRender(X11SalGraphics& rParent)
+ : mrParent(rParent)
+{
+}
+
+cairo_t* X11CairoTextRender::getCairoContext()
+{
+ return mrParent.getCairoContext();
+}
+
+void X11CairoTextRender::getSurfaceOffset( double& nDX, double& nDY )
+{
+ nDX = 0;
+ nDY = 0;
+}
+
+void X11CairoTextRender::clipRegion(cairo_t* cr)
+{
+ Region pClipRegion = mrParent.mpClipRegion;
+ if( pClipRegion && !XEmptyRegion( pClipRegion ) )
+ {
+ for (tools::Long i = 0; i < pClipRegion->numRects; ++i)
+ {
+ cairo_rectangle(cr,
+ pClipRegion->rects[i].x1,
+ pClipRegion->rects[i].y1,
+ pClipRegion->rects[i].x2 - pClipRegion->rects[i].x1,
+ pClipRegion->rects[i].y2 - pClipRegion->rects[i].y1);
+ }
+ cairo_clip(cr);
+ }
+}
+
+void X11CairoTextRender::releaseCairoContext(cairo_t* cr)
+{
+ X11SalGraphics::releaseCairoContext(cr);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/gdi/xrender_peer.cxx b/vcl/unx/generic/gdi/xrender_peer.cxx
new file mode 100644
index 000000000..961f4cd3a
--- /dev/null
+++ b/vcl/unx/generic/gdi/xrender_peer.cxx
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unx/saldisp.hxx>
+
+#include <unx/x11/xrender_peer.hxx>
+
+XRenderPeer::XRenderPeer()
+ : mpDisplay( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay() )
+ , mpStandardFormatA8( nullptr )
+{
+ InitRenderLib();
+}
+
+XRenderPeer& XRenderPeer::GetInstance()
+{
+ static XRenderPeer aPeer;
+ return aPeer;
+}
+
+void XRenderPeer::InitRenderLib()
+{
+ int nDummy;
+ // needed to initialize libXrender internals
+ XRenderQueryExtension( mpDisplay, &nDummy, &nDummy );
+
+ // the 8bit alpha mask format must be there
+ XRenderPictFormat aPictFormat={0,0,8,{0,0,0,0,0,0,0,0xFF},0};
+ mpStandardFormatA8 = XRenderFindFormat( mpDisplay, PictFormatAlphaMask|PictFormatDepth, &aPictFormat, 0 );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/glyphs/freetype_glyphcache.cxx b/vcl/unx/generic/glyphs/freetype_glyphcache.cxx
new file mode 100644
index 000000000..bb7d3e10e
--- /dev/null
+++ b/vcl/unx/generic/glyphs/freetype_glyphcache.cxx
@@ -0,0 +1,975 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <o3tl/safeint.hxx>
+#include <vcl/fontcharmap.hxx>
+
+#include <unx/freetype_glyphcache.hxx>
+
+#include <fontinstance.hxx>
+#include <fontattributes.hxx>
+
+#include <unotools/fontdefs.hxx>
+
+#include <tools/poly.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+
+#include <sal/log.hxx>
+#include <osl/module.h>
+
+#include <langboost.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <sft.hxx>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+#include FT_MULTIPLE_MASTERS_H
+#include FT_OUTLINE_H
+#include FT_SIZES_H
+#include FT_SYNTHESIS_H
+#include FT_TRUETYPE_TABLES_H
+
+#include <vector>
+
+// TODO: move file mapping stuff to OSL
+#include <unistd.h>
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <unx/fontmanager.hxx>
+#include <impfontcharmap.hxx>
+
+static FT_Library aLibFT = nullptr;
+
+// TODO: remove when the priorities are selected by UI
+// if (AH==0) => disable autohinting
+// if (AA==0) => disable antialiasing
+// if (EB==0) => disable embedded bitmaps
+// if (AA prio <= AH prio) => antialias + autohint
+// if (AH<AA) => do not autohint when antialiasing
+// if (EB<AH) => do not autohint for monochrome
+static int nDefaultPrioEmbedded = 2;
+static int nDefaultPrioAntiAlias = 1;
+
+FreetypeFontFile::FreetypeFontFile( const OString& rNativeFileName )
+: maNativeFileName( rNativeFileName ),
+ mpFileMap( nullptr ),
+ mnFileSize( 0 ),
+ mnRefCount( 0 ),
+ mnLangBoost( 0 )
+{
+ // boost font preference if UI language is mentioned in filename
+ int nPos = maNativeFileName.lastIndexOf( '_' );
+ if( nPos == -1 || maNativeFileName[nPos+1] == '.' )
+ mnLangBoost += 0x1000; // no langinfo => good
+ else
+ {
+ static const char* pLangBoost = nullptr;
+ static bool bOnce = true;
+ if( bOnce )
+ {
+ bOnce = false;
+ pLangBoost = vcl::getLangBoost();
+ }
+
+ if( pLangBoost && !strncasecmp( pLangBoost, &maNativeFileName.getStr()[nPos+1], 3 ) )
+ mnLangBoost += 0x2000; // matching langinfo => better
+ }
+}
+
+bool FreetypeFontFile::Map()
+{
+ if (mnRefCount++ == 0)
+ {
+ const char* pFileName = maNativeFileName.getStr();
+ int nFile = open( pFileName, O_RDONLY );
+ if( nFile < 0 )
+ {
+ SAL_WARN("vcl.unx.freetype", "open('" << maNativeFileName << "') failed: " << strerror(errno));
+ return false;
+ }
+
+ struct stat aStat;
+ int nRet = fstat( nFile, &aStat );
+ if (nRet < 0)
+ {
+ SAL_WARN("vcl.unx.freetype", "fstat on '" << maNativeFileName << "' failed: " << strerror(errno));
+ close (nFile);
+ return false;
+ }
+ mnFileSize = aStat.st_size;
+ mpFileMap = static_cast<unsigned char*>(
+ mmap( nullptr, mnFileSize, PROT_READ, MAP_SHARED, nFile, 0 ));
+ if( mpFileMap == MAP_FAILED )
+ {
+ SAL_WARN("vcl.unx.freetype", "mmap of '" << maNativeFileName << "' failed: " << strerror(errno));
+ mpFileMap = nullptr;
+ }
+ close( nFile );
+ }
+
+ return (mpFileMap != nullptr);
+}
+
+void FreetypeFontFile::Unmap()
+{
+ if (--mnRefCount != 0)
+ return;
+ assert(mnRefCount >= 0 && "how did this go negative\n");
+ if (mpFileMap)
+ {
+ munmap(mpFileMap, mnFileSize);
+ mpFileMap = nullptr;
+ }
+}
+
+FreetypeFontInfo::FreetypeFontInfo( const FontAttributes& rDevFontAttributes,
+ FreetypeFontFile* const pFontFile, int nFaceNum, int nFaceVariation, sal_IntPtr nFontId)
+:
+ maFaceFT( nullptr ),
+ mpFontFile(pFontFile),
+ mnFaceNum( nFaceNum ),
+ mnFaceVariation( nFaceVariation ),
+ mnRefCount( 0 ),
+ mnFontId( nFontId ),
+ maDevFontAttributes( rDevFontAttributes )
+{
+ // prefer font with low ID
+ maDevFontAttributes.IncreaseQualityBy( 10000 - nFontId );
+ // prefer font with matching file names
+ maDevFontAttributes.IncreaseQualityBy( mpFontFile->GetLangBoost() );
+}
+
+FreetypeFontInfo::~FreetypeFontInfo()
+{
+}
+
+namespace
+{
+ void dlFT_Done_MM_Var(FT_Library library, FT_MM_Var *amaster)
+ {
+#if !HAVE_DLAPI
+ FT_Done_MM_Var(library, amaster);
+#else
+ static auto func = reinterpret_cast<void(*)(FT_Library, FT_MM_Var*)>(
+ osl_getAsciiFunctionSymbol(nullptr, "FT_Done_MM_Var"));
+ if (func)
+ func(library, amaster);
+ else
+ free(amaster);
+#endif
+ }
+}
+
+FT_FaceRec_* FreetypeFontInfo::GetFaceFT()
+{
+ if (!maFaceFT && mpFontFile->Map())
+ {
+ FT_Error rc = FT_New_Memory_Face( aLibFT,
+ mpFontFile->GetBuffer(),
+ mpFontFile->GetFileSize(), mnFaceNum, &maFaceFT );
+ if( (rc != FT_Err_Ok) || (maFaceFT->num_glyphs <= 0) )
+ maFaceFT = nullptr;
+
+ if (maFaceFT && mnFaceVariation)
+ {
+ FT_MM_Var *pFtMMVar;
+ if (FT_Get_MM_Var(maFaceFT, &pFtMMVar) == 0)
+ {
+ if (o3tl::make_unsigned(mnFaceVariation) <= pFtMMVar->num_namedstyles)
+ {
+ FT_Var_Named_Style *instance = &pFtMMVar->namedstyle[mnFaceVariation - 1];
+ FT_Set_Var_Design_Coordinates(maFaceFT, pFtMMVar->num_axis, instance->coords);
+ }
+ dlFT_Done_MM_Var(aLibFT, pFtMMVar);
+ }
+ }
+ }
+
+ ++mnRefCount;
+ return maFaceFT;
+}
+
+void FreetypeFont::SetFontVariationsOnHBFont(hb_font_t* pHbFace) const
+{
+ sal_uInt32 nFaceVariation = mxFontInfo->GetFontFaceVariation();
+ if (!(maFaceFT && nFaceVariation))
+ return;
+
+ FT_MM_Var *pFtMMVar;
+ if (FT_Get_MM_Var(maFaceFT, &pFtMMVar) != 0)
+ return;
+
+ if (nFaceVariation <= pFtMMVar->num_namedstyles)
+ {
+ FT_Var_Named_Style *instance = &pFtMMVar->namedstyle[nFaceVariation - 1];
+ std::vector<hb_variation_t> aVariations(pFtMMVar->num_axis);
+ for (FT_UInt i = 0; i < pFtMMVar->num_axis; ++i)
+ {
+ aVariations[i].tag = pFtMMVar->axis[i].tag;
+ aVariations[i].value = instance->coords[i] / 65536.0;
+ }
+ hb_font_set_variations(pHbFace, aVariations.data(), aVariations.size());
+ }
+ dlFT_Done_MM_Var(aLibFT, pFtMMVar);
+}
+
+void FreetypeFontInfo::ReleaseFaceFT()
+{
+ if (--mnRefCount == 0)
+ {
+ if (maFaceFT)
+ {
+ FT_Done_Face(maFaceFT);
+ maFaceFT = nullptr;
+ }
+ mpFontFile->Unmap();
+ }
+ assert(mnRefCount >= 0 && "how did this go negative\n");
+}
+
+static unsigned GetUInt( const unsigned char* p ) { return((p[0]<<24)+(p[1]<<16)+(p[2]<<8)+p[3]);}
+static unsigned GetUShort( const unsigned char* p ){ return((p[0]<<8)+p[1]);}
+
+const sal_uInt32 T_true = 0x74727565; /* 'true' */
+const sal_uInt32 T_ttcf = 0x74746366; /* 'ttcf' */
+const sal_uInt32 T_otto = 0x4f54544f; /* 'OTTO' */
+
+const unsigned char* FreetypeFontInfo::GetTable( const char* pTag, sal_uLong* pLength ) const
+{
+ const unsigned char* pBuffer = mpFontFile->GetBuffer();
+ int nFileSize = mpFontFile->GetFileSize();
+ if( !pBuffer || nFileSize<1024 )
+ return nullptr;
+
+ // we currently handle TTF, TTC and OTF headers
+ unsigned nFormat = GetUInt( pBuffer );
+
+ const unsigned char* p = pBuffer + 12;
+ if( nFormat == ::T_ttcf ) // TTC_MAGIC
+ p += GetUInt( p + 4 * mnFaceNum );
+ else if( nFormat != 0x00010000 && nFormat != ::T_true && nFormat != ::T_otto) // TTF_MAGIC and Apple TTF Magic and PS-OpenType font
+ return nullptr;
+
+ // walk table directory until match
+ int nTables = GetUShort( p - 8 );
+ if( nTables >= 64 ) // something fishy?
+ return nullptr;
+ for( int i = 0; i < nTables; ++i, p+=16 )
+ {
+ if( p[0]==pTag[0] && p[1]==pTag[1] && p[2]==pTag[2] && p[3]==pTag[3] )
+ {
+ sal_uLong nLength = GetUInt( p + 12 );
+ if( pLength != nullptr )
+ *pLength = nLength;
+ const unsigned char* pTable = pBuffer + GetUInt( p + 8 );
+ if( (pTable + nLength) <= (mpFontFile->GetBuffer() + nFileSize) )
+ return pTable;
+ }
+ }
+
+ return nullptr;
+}
+
+void FreetypeFontInfo::AnnounceFont( vcl::font::PhysicalFontCollection* pFontCollection )
+{
+ rtl::Reference<FreetypeFontFace> pFD(new FreetypeFontFace( this, maDevFontAttributes ));
+ pFontCollection->Add( pFD.get() );
+}
+
+void FreetypeManager::InitFreetype()
+{
+ /*FT_Error rcFT =*/ FT_Init_FreeType( &aLibFT );
+
+ // TODO: remove when the priorities are selected by UI
+ char* pEnv;
+ pEnv = ::getenv( "SAL_EMBEDDED_BITMAP_PRIORITY" );
+ if( pEnv )
+ nDefaultPrioEmbedded = pEnv[0] - '0';
+ pEnv = ::getenv( "SAL_ANTIALIASED_TEXT_PRIORITY" );
+ if( pEnv )
+ nDefaultPrioAntiAlias = pEnv[0] - '0';
+}
+
+namespace
+{
+ bool DoesAlmostHorizontalDrainRenderingPool()
+ {
+ FT_Int nMajor, nMinor, nPatch;
+ FT_Library_Version(aLibFT, &nMajor, &nMinor, &nPatch);
+ if (nMajor > 2)
+ return false;
+ if (nMajor == 2 && nMinor <= 8)
+ return true;
+ return false;
+ }
+}
+
+bool FreetypeFont::AlmostHorizontalDrainsRenderingPool(int nRatio, const vcl::font::FontSelectPattern& rFSD)
+{
+ static bool bAlmostHorizontalDrainsRenderingPool = DoesAlmostHorizontalDrainRenderingPool();
+ if (nRatio > 100 && rFSD.maTargetName == "OpenSymbol" && bAlmostHorizontalDrainsRenderingPool)
+ {
+ // tdf#127189 FreeType <= 2.8 will fail to render stretched horizontal
+ // brace glyphs in starmath at a fairly low stretch ratio. The failure
+ // will set CAIRO_STATUS_FREETYPE_ERROR on the surface which cannot be
+ // cleared, so all further painting to the surface fails.
+
+ // This appears fixed in >= freetype 2.9
+
+ // Restrict this bodge to a stretch ratio > ~10 of the OpenSymbol font
+ // where it has been seen in practice.
+ SAL_WARN("vcl", "rendering text would fail with stretch ratio of: " << nRatio << ", with FreeType <= 2.8");
+ return true;
+ }
+ return false;
+}
+
+FT_Face FreetypeFont::GetFtFace() const
+{
+ FT_Activate_Size( maSizeFT );
+
+ return maFaceFT;
+}
+
+void FreetypeManager::AddFontFile(const OString& rNormalizedName,
+ int nFaceNum, int nVariantNum, sal_IntPtr nFontId, const FontAttributes& rDevFontAttr)
+{
+ if( rNormalizedName.isEmpty() )
+ return;
+
+ if( m_aFontInfoList.find( nFontId ) != m_aFontInfoList.end() )
+ return;
+
+ FreetypeFontInfo* pFontInfo = new FreetypeFontInfo( rDevFontAttr,
+ FindFontFile(rNormalizedName), nFaceNum, nVariantNum, nFontId);
+ m_aFontInfoList[ nFontId ].reset(pFontInfo);
+ if( m_nMaxFontId < nFontId )
+ m_nMaxFontId = nFontId;
+}
+
+void FreetypeManager::AnnounceFonts( vcl::font::PhysicalFontCollection* pToAdd ) const
+{
+ for (auto const& font : m_aFontInfoList)
+ {
+ FreetypeFontInfo* pFreetypeFontInfo = font.second.get();
+ pFreetypeFontInfo->AnnounceFont( pToAdd );
+ }
+}
+
+FreetypeFont* FreetypeManager::CreateFont(FreetypeFontInstance* pFontInstance)
+{
+ // find a FontInfo matching to the font id
+ if (!pFontInstance)
+ return nullptr;
+
+ const vcl::font::PhysicalFontFace* pFontFace = pFontInstance->GetFontFace();
+ if (!pFontFace)
+ return nullptr;
+
+ sal_IntPtr nFontId = pFontFace->GetFontId();
+ FontInfoList::iterator it = m_aFontInfoList.find(nFontId);
+
+ if (it == m_aFontInfoList.end())
+ return nullptr;
+
+ return new FreetypeFont(*pFontInstance, it->second);
+}
+
+FreetypeFontFace::FreetypeFontFace( FreetypeFontInfo* pFI, const FontAttributes& rDFA )
+: vcl::font::PhysicalFontFace( rDFA ),
+ mpFreetypeFontInfo( pFI )
+{
+}
+
+rtl::Reference<LogicalFontInstance> FreetypeFontFace::CreateFontInstance(const vcl::font::FontSelectPattern& rFSD) const
+{
+ return new FreetypeFontInstance(*this, rFSD);
+}
+
+// FreetypeFont
+
+FreetypeFont::FreetypeFont(FreetypeFontInstance& rFontInstance, const std::shared_ptr<FreetypeFontInfo>& rFI)
+: mrFontInstance(rFontInstance),
+ mnCos( 0x10000),
+ mnSin( 0 ),
+ mnPrioAntiAlias(nDefaultPrioAntiAlias),
+ mxFontInfo(rFI),
+ mnLoadFlags( 0 ),
+ maFaceFT( nullptr ),
+ maSizeFT( nullptr ),
+ mbFaceOk( false ),
+ mbArtItalic( false ),
+ mbArtBold(false)
+{
+ int nPrioEmbedded = nDefaultPrioEmbedded;
+
+ maFaceFT = mxFontInfo->GetFaceFT();
+
+ const vcl::font::FontSelectPattern& rFSD = rFontInstance.GetFontSelectPattern();
+
+ if( rFSD.mnOrientation )
+ {
+ const double dRad = toRadians(rFSD.mnOrientation);
+ mnCos = static_cast<tools::Long>( 0x10000 * cos( dRad ) + 0.5 );
+ mnSin = static_cast<tools::Long>( 0x10000 * sin( dRad ) + 0.5 );
+ }
+
+ // set the pixel size of the font instance
+ mnWidth = rFSD.mnWidth;
+ if( !mnWidth )
+ mnWidth = rFSD.mnHeight;
+ if (rFSD.mnHeight == 0)
+ {
+ SAL_WARN("vcl", "FreetypeFont divide by zero");
+ mfStretch = 1.0;
+ return;
+ }
+ mfStretch = static_cast<double>(mnWidth) / rFSD.mnHeight;
+ // sanity checks (e.g. #i66394#, #i66244#, #i66537#)
+ if (mnWidth < 0)
+ {
+ SAL_WARN("vcl", "FreetypeFont negative font width of: " << mnWidth);
+ return;
+ }
+ if (mfStretch > +148.0 || mfStretch < -148.0)
+ {
+ SAL_WARN("vcl", "FreetypeFont excessive stretch of: " << mfStretch);
+ return;
+ }
+
+ if( !maFaceFT )
+ return;
+
+ FT_New_Size( maFaceFT, &maSizeFT );
+ FT_Activate_Size( maSizeFT );
+ /* This might fail for color bitmap fonts, but that is fine since we will
+ * not need any glyph data from FreeType in this case */
+ /*FT_Error rc = */ FT_Set_Pixel_Sizes( maFaceFT, mnWidth, rFSD.mnHeight );
+
+ FT_Select_Charmap(maFaceFT, FT_ENCODING_UNICODE);
+
+ if( mxFontInfo->IsSymbolFont() )
+ {
+ FT_Encoding eEncoding = FT_ENCODING_MS_SYMBOL;
+ FT_Select_Charmap(maFaceFT, eEncoding);
+ }
+
+ mbFaceOk = true;
+
+ // TODO: query GASP table for load flags
+ mnLoadFlags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_TRANSFORM;
+
+ mbArtItalic = (rFSD.GetItalic() != ITALIC_NONE && mxFontInfo->GetFontAttributes().GetItalic() == ITALIC_NONE);
+ mbArtBold = (rFSD.GetWeight() > WEIGHT_MEDIUM && mxFontInfo->GetFontAttributes().GetWeight() <= WEIGHT_MEDIUM);
+
+ if( ((mnCos != 0) && (mnSin != 0)) || (nPrioEmbedded <= 0) )
+ mnLoadFlags |= FT_LOAD_NO_BITMAP;
+}
+
+namespace
+{
+ std::unique_ptr<FontConfigFontOptions> GetFCFontOptions(const FontAttributes& rFontAttributes, int nSize)
+ {
+ return psp::PrintFontManager::getFontOptions(rFontAttributes, nSize);
+ }
+}
+
+const FontConfigFontOptions* FreetypeFont::GetFontOptions() const
+{
+ if (!mxFontOptions)
+ {
+ mxFontOptions = GetFCFontOptions(mxFontInfo->GetFontAttributes(), mrFontInstance.GetFontSelectPattern().mnHeight);
+ mxFontOptions->SyncPattern(GetFontFileName(), GetFontFaceIndex(), GetFontFaceVariation(), NeedsArtificialBold());
+ }
+ return mxFontOptions.get();
+}
+
+const OString& FreetypeFont::GetFontFileName() const
+{
+ return mxFontInfo->GetFontFileName();
+}
+
+int FreetypeFont::GetFontFaceIndex() const
+{
+ return mxFontInfo->GetFontFaceIndex();
+}
+
+int FreetypeFont::GetFontFaceVariation() const
+{
+ return mxFontInfo->GetFontFaceVariation();
+}
+
+FreetypeFont::~FreetypeFont()
+{
+ if( maSizeFT )
+ FT_Done_Size( maSizeFT );
+
+ mxFontInfo->ReleaseFaceFT();
+}
+
+void FreetypeFont::GetFontMetric(ImplFontMetricDataRef const & rxTo) const
+{
+ rxTo->FontAttributes::operator =(mxFontInfo->GetFontAttributes());
+
+ rxTo->SetOrientation(mrFontInstance.GetFontSelectPattern().mnOrientation);
+
+ //Always consider [star]symbol as symbol fonts
+ if ( IsStarSymbol( rxTo->GetFamilyName() ) )
+ rxTo->SetSymbolFlag( true );
+
+ FT_Activate_Size( maSizeFT );
+
+ rxTo->ImplCalcLineSpacing(&mrFontInstance);
+ rxTo->ImplInitBaselines(&mrFontInstance);
+
+ rxTo->SetSlant( 0 );
+ rxTo->SetWidth( mnWidth );
+
+ const TT_OS2* pOS2 = static_cast<const TT_OS2*>(FT_Get_Sfnt_Table( maFaceFT, ft_sfnt_os2 ));
+ if( pOS2 && (pOS2->version != 0xFFFF) )
+ {
+ // map the panose info from the OS2 table to their VCL counterparts
+ switch( pOS2->panose[0] )
+ {
+ case 1: rxTo->SetFamilyType( FAMILY_ROMAN ); break;
+ case 2: rxTo->SetFamilyType( FAMILY_SWISS ); break;
+ case 3: rxTo->SetFamilyType( FAMILY_MODERN ); break;
+ case 4: rxTo->SetFamilyType( FAMILY_SCRIPT ); break;
+ case 5: rxTo->SetFamilyType( FAMILY_DECORATIVE ); break;
+ // TODO: is it reasonable to override the attribute with DONTKNOW?
+ case 0: // fall through
+ default: rxTo->SetFamilyType( FAMILY_DONTKNOW ); break;
+ }
+
+ switch( pOS2->panose[3] )
+ {
+ case 2: // fall through
+ case 3: // fall through
+ case 4: // fall through
+ case 5: // fall through
+ case 6: // fall through
+ case 7: // fall through
+ case 8: rxTo->SetPitch( PITCH_VARIABLE ); break;
+ case 9: rxTo->SetPitch( PITCH_FIXED ); break;
+ // TODO: is it reasonable to override the attribute with DONTKNOW?
+ case 0: // fall through
+ case 1: // fall through
+ default: rxTo->SetPitch( PITCH_DONTKNOW ); break;
+ }
+ }
+
+ // initialize kashida width
+ rxTo->SetMinKashida(mrFontInstance.GetKashidaWidth());
+}
+
+void FreetypeFont::ApplyGlyphTransform(bool bVertical, FT_Glyph pGlyphFT ) const
+{
+ // shortcut most common case
+ if (!mrFontInstance.GetFontSelectPattern().mnOrientation && !bVertical)
+ return;
+
+ const FT_Size_Metrics& rMetrics = maFaceFT->size->metrics;
+ FT_Vector aVector;
+ FT_Matrix aMatrix;
+
+ bool bStretched = false;
+
+ if (!bVertical)
+ {
+ // straight
+ aVector.x = 0;
+ aVector.y = 0;
+ aMatrix.xx = +mnCos;
+ aMatrix.yy = +mnCos;
+ aMatrix.xy = -mnSin;
+ aMatrix.yx = +mnSin;
+ }
+ else
+ {
+ // left
+ bStretched = (mfStretch != 1.0);
+ aVector.x = static_cast<FT_Pos>(+rMetrics.descender * mfStretch);
+ aVector.y = -rMetrics.ascender;
+ aMatrix.xx = static_cast<FT_Pos>(-mnSin / mfStretch);
+ aMatrix.yy = static_cast<FT_Pos>(-mnSin * mfStretch);
+ aMatrix.xy = static_cast<FT_Pos>(-mnCos * mfStretch);
+ aMatrix.yx = static_cast<FT_Pos>(+mnCos / mfStretch);
+ }
+
+ if( pGlyphFT->format != FT_GLYPH_FORMAT_BITMAP )
+ {
+ FT_Glyph_Transform( pGlyphFT, nullptr, &aVector );
+
+ // orthogonal transforms are better handled by bitmap operations
+ if( bStretched )
+ {
+ // apply non-orthogonal or stretch transformations
+ FT_Glyph_Transform( pGlyphFT, &aMatrix, nullptr );
+ }
+ }
+ else
+ {
+ // FT<=2005 ignores transforms for bitmaps, so do it manually
+ FT_BitmapGlyph pBmpGlyphFT = reinterpret_cast<FT_BitmapGlyph>(pGlyphFT);
+ pBmpGlyphFT->left += (aVector.x + 32) >> 6;
+ pBmpGlyphFT->top += (aVector.y + 32) >> 6;
+ }
+}
+
+bool FreetypeFont::GetGlyphBoundRect(sal_GlyphId nID, tools::Rectangle& rRect, bool bVertical) const
+{
+ FT_Activate_Size( maSizeFT );
+
+ FT_Error rc = FT_Load_Glyph(maFaceFT, nID, mnLoadFlags);
+
+ if (rc != FT_Err_Ok)
+ return false;
+
+ if (mbArtBold)
+ FT_GlyphSlot_Embolden(maFaceFT->glyph);
+
+ FT_Glyph pGlyphFT;
+ rc = FT_Get_Glyph(maFaceFT->glyph, &pGlyphFT);
+ if (rc != FT_Err_Ok)
+ return false;
+
+ ApplyGlyphTransform(bVertical, pGlyphFT);
+
+ FT_BBox aBbox;
+ FT_Glyph_Get_CBox( pGlyphFT, FT_GLYPH_BBOX_PIXELS, &aBbox );
+ FT_Done_Glyph( pGlyphFT );
+
+ tools::Rectangle aRect(aBbox.xMin, -aBbox.yMax, aBbox.xMax, -aBbox.yMin);
+ if (mnCos != 0x10000 && mnSin != 0)
+ {
+ const double nCos = mnCos / 65536.0;
+ const double nSin = mnSin / 65536.0;
+ rRect.SetLeft( nCos*aRect.Left() + nSin*aRect.Top() );
+ rRect.SetTop( -nSin*aRect.Left() - nCos*aRect.Top() );
+ rRect.SetRight( nCos*aRect.Right() + nSin*aRect.Bottom() );
+ rRect.SetBottom( -nSin*aRect.Right() - nCos*aRect.Bottom() );
+ }
+ else
+ rRect = aRect;
+ return true;
+}
+
+bool FreetypeFont::GetAntialiasAdvice() const
+{
+ // TODO: also use GASP info
+ return !mrFontInstance.GetFontSelectPattern().mbNonAntialiased && (mnPrioAntiAlias > 0);
+}
+
+// determine unicode ranges in font
+
+const FontCharMapRef & FreetypeFont::GetFontCharMap() const
+{
+ return mxFontInfo->GetFontCharMap();
+}
+
+bool FreetypeFont::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ return mxFontInfo->GetFontCapabilities(rFontCapabilities);
+}
+
+const FontCharMapRef & FreetypeFontInfo::GetFontCharMap() const
+{
+ // check if the charmap is already cached
+ if( mxFontCharMap.is() )
+ return mxFontCharMap;
+
+ // get the charmap and cache it
+ CmapResult aCmapResult;
+ aCmapResult.mbSymbolic = IsSymbolFont();
+
+ sal_uLong nLength = 0;
+ const unsigned char* pCmap = GetTable("cmap", &nLength);
+ if (pCmap && (nLength > 0) && ParseCMAP(pCmap, nLength, aCmapResult))
+ {
+ FontCharMapRef xFontCharMap( new FontCharMap ( aCmapResult ) );
+ mxFontCharMap = xFontCharMap;
+ }
+ else
+ {
+ FontCharMapRef xFontCharMap( new FontCharMap() );
+ mxFontCharMap = xFontCharMap;
+ }
+ // mxFontCharMap on either branch now has a refcount of 1
+ return mxFontCharMap;
+}
+
+bool FreetypeFontInfo::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ bool bRet = false;
+
+ sal_uLong nLength = 0;
+
+ // load OS/2 table
+ const FT_Byte* pOS2 = GetTable("OS/2", &nLength);
+ if (pOS2)
+ {
+ bRet = vcl::getTTCoverage(
+ rFontCapabilities.oUnicodeRange,
+ rFontCapabilities.oCodePageRange,
+ pOS2, nLength);
+ }
+
+ return bRet;
+}
+
+// outline stuff
+
+namespace {
+
+class PolyArgs
+{
+public:
+ PolyArgs( tools::PolyPolygon& rPolyPoly, sal_uInt16 nMaxPoints );
+
+ void AddPoint( tools::Long nX, tools::Long nY, PolyFlags);
+ void ClosePolygon();
+
+ tools::Long GetPosX() const { return maPosition.x;}
+ tools::Long GetPosY() const { return maPosition.y;}
+
+private:
+ tools::PolyPolygon& mrPolyPoly;
+
+ std::unique_ptr<Point[]>
+ mpPointAry;
+ std::unique_ptr<PolyFlags[]>
+ mpFlagAry;
+
+ FT_Vector maPosition;
+ sal_uInt16 mnMaxPoints;
+ sal_uInt16 mnPoints;
+ sal_uInt16 mnPoly;
+ bool bHasOffline;
+
+ PolyArgs(const PolyArgs&) = delete;
+ PolyArgs& operator=(const PolyArgs&) = delete;
+};
+
+}
+
+PolyArgs::PolyArgs( tools::PolyPolygon& rPolyPoly, sal_uInt16 nMaxPoints )
+: mrPolyPoly(rPolyPoly),
+ mnMaxPoints(nMaxPoints),
+ mnPoints(0),
+ mnPoly(0),
+ bHasOffline(false)
+{
+ mpPointAry.reset( new Point[ mnMaxPoints ] );
+ mpFlagAry.reset( new PolyFlags [ mnMaxPoints ] );
+ maPosition.x = maPosition.y = 0;
+}
+
+void PolyArgs::AddPoint( tools::Long nX, tools::Long nY, PolyFlags aFlag )
+{
+ SAL_WARN_IF( (mnPoints >= mnMaxPoints), "vcl", "FTGlyphOutline: AddPoint overflow!" );
+ if( mnPoints >= mnMaxPoints )
+ return;
+
+ maPosition.x = nX;
+ maPosition.y = nY;
+ mpPointAry[ mnPoints ] = Point( nX, nY );
+ mpFlagAry[ mnPoints++ ]= aFlag;
+ bHasOffline |= (aFlag != PolyFlags::Normal);
+}
+
+void PolyArgs::ClosePolygon()
+{
+ if( !mnPoly++ )
+ return;
+
+ // freetype seems to always close the polygon with an ON_CURVE point
+ // PolyPoly wants to close the polygon itself => remove last point
+ SAL_WARN_IF( (mnPoints < 2), "vcl", "FTGlyphOutline: PolyFinishNum failed!" );
+ --mnPoints;
+ SAL_WARN_IF( (mpPointAry[0]!=mpPointAry[mnPoints]), "vcl", "FTGlyphOutline: PolyFinishEq failed!" );
+ SAL_WARN_IF( (mpFlagAry[0]!=PolyFlags::Normal), "vcl", "FTGlyphOutline: PolyFinishFE failed!" );
+ SAL_WARN_IF( (mpFlagAry[mnPoints]!=PolyFlags::Normal), "vcl", "FTGlyphOutline: PolyFinishFS failed!" );
+
+ tools::Polygon aPoly( mnPoints, mpPointAry.get(), (bHasOffline ? mpFlagAry.get() : nullptr) );
+
+ // #i35928#
+ // This may be an invalid polygon, e.g. the last point is a control point.
+ // So close the polygon (and add the first point again) if the last point
+ // is a control point or different from first.
+ // #i48298#
+ // Now really duplicating the first point, to close or correct the
+ // polygon. Also no longer duplicating the flags, but enforcing
+ // PolyFlags::Normal for the newly added last point.
+ const sal_uInt16 nPolySize(aPoly.GetSize());
+ if(nPolySize)
+ {
+ if((aPoly.HasFlags() && PolyFlags::Control == aPoly.GetFlags(nPolySize - 1))
+ || (aPoly.GetPoint(nPolySize - 1) != aPoly.GetPoint(0)))
+ {
+ aPoly.SetSize(nPolySize + 1);
+ aPoly.SetPoint(aPoly.GetPoint(0), nPolySize);
+
+ if(aPoly.HasFlags())
+ {
+ aPoly.SetFlags(nPolySize, PolyFlags::Normal);
+ }
+ }
+ }
+
+ mrPolyPoly.Insert( aPoly );
+ mnPoints = 0;
+ bHasOffline = false;
+}
+
+extern "C" {
+
+// TODO: wait till all compilers accept that calling conventions
+// for functions are the same independent of implementation constness,
+// then uncomment the const-tokens in the function interfaces below
+static int FT_move_to( const FT_Vector* p0, void* vpPolyArgs )
+{
+ PolyArgs& rA = *static_cast<PolyArgs*>(vpPolyArgs);
+
+ // move_to implies a new polygon => finish old polygon first
+ rA.ClosePolygon();
+
+ rA.AddPoint( p0->x, p0->y, PolyFlags::Normal );
+ return 0;
+}
+
+static int FT_line_to( const FT_Vector* p1, void* vpPolyArgs )
+{
+ PolyArgs& rA = *static_cast<PolyArgs*>(vpPolyArgs);
+ rA.AddPoint( p1->x, p1->y, PolyFlags::Normal );
+ return 0;
+}
+
+static int FT_conic_to( const FT_Vector* p1, const FT_Vector* p2, void* vpPolyArgs )
+{
+ PolyArgs& rA = *static_cast<PolyArgs*>(vpPolyArgs);
+
+ // VCL's Polygon only knows cubic beziers
+ const tools::Long nX1 = (2 * rA.GetPosX() + 4 * p1->x + 3) / 6;
+ const tools::Long nY1 = (2 * rA.GetPosY() + 4 * p1->y + 3) / 6;
+ rA.AddPoint( nX1, nY1, PolyFlags::Control );
+
+ const tools::Long nX2 = (2 * p2->x + 4 * p1->x + 3) / 6;
+ const tools::Long nY2 = (2 * p2->y + 4 * p1->y + 3) / 6;
+ rA.AddPoint( nX2, nY2, PolyFlags::Control );
+
+ rA.AddPoint( p2->x, p2->y, PolyFlags::Normal );
+ return 0;
+}
+
+static int FT_cubic_to( const FT_Vector* p1, const FT_Vector* p2, const FT_Vector* p3, void* vpPolyArgs )
+{
+ PolyArgs& rA = *static_cast<PolyArgs*>(vpPolyArgs);
+ rA.AddPoint( p1->x, p1->y, PolyFlags::Control );
+ rA.AddPoint( p2->x, p2->y, PolyFlags::Control );
+ rA.AddPoint( p3->x, p3->y, PolyFlags::Normal );
+ return 0;
+}
+
+} // extern "C"
+
+bool FreetypeFont::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rB2DPolyPoly, bool bIsVertical) const
+{
+ if( maSizeFT )
+ FT_Activate_Size( maSizeFT );
+
+ rB2DPolyPoly.clear();
+
+ FT_Int nLoadFlags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_TRANSFORM;
+
+#ifdef FT_LOAD_TARGET_LIGHT
+ // enable "light hinting" if available
+ nLoadFlags |= FT_LOAD_TARGET_LIGHT;
+#endif
+
+ FT_Error rc = FT_Load_Glyph(maFaceFT, nId, nLoadFlags);
+ if( rc != FT_Err_Ok )
+ return false;
+
+ if (mbArtBold)
+ FT_GlyphSlot_Embolden(maFaceFT->glyph);
+
+ FT_Glyph pGlyphFT;
+ rc = FT_Get_Glyph( maFaceFT->glyph, &pGlyphFT );
+ if( rc != FT_Err_Ok )
+ return false;
+
+ if( pGlyphFT->format != FT_GLYPH_FORMAT_OUTLINE )
+ {
+ FT_Done_Glyph( pGlyphFT );
+ return false;
+ }
+
+ if( mbArtItalic )
+ {
+ FT_Matrix aMatrix;
+ aMatrix.xx = aMatrix.yy = 0x10000L;
+ aMatrix.xy = 0x6000L;
+ aMatrix.yx = 0;
+ FT_Glyph_Transform( pGlyphFT, &aMatrix, nullptr );
+ }
+
+ FT_Outline& rOutline = reinterpret_cast<FT_OutlineGlyphRec*>(pGlyphFT)->outline;
+ if( !rOutline.n_points ) // blank glyphs are ok
+ {
+ FT_Done_Glyph( pGlyphFT );
+ return true;
+ }
+
+ tools::Long nMaxPoints = 1 + rOutline.n_points * 3;
+ tools::PolyPolygon aToolPolyPolygon;
+ PolyArgs aPolyArg( aToolPolyPolygon, nMaxPoints );
+
+ ApplyGlyphTransform(bIsVertical, pGlyphFT);
+
+ FT_Outline_Funcs aFuncs;
+ aFuncs.move_to = &FT_move_to;
+ aFuncs.line_to = &FT_line_to;
+ aFuncs.conic_to = &FT_conic_to;
+ aFuncs.cubic_to = &FT_cubic_to;
+ aFuncs.shift = 0;
+ aFuncs.delta = 0;
+ FT_Outline_Decompose( &rOutline, &aFuncs, static_cast<void*>(&aPolyArg) );
+ aPolyArg.ClosePolygon(); // close last polygon
+ FT_Done_Glyph( pGlyphFT );
+
+ // convert to basegfx polypolygon
+ // TODO: get rid of the intermediate tools polypolygon
+ rB2DPolyPoly = aToolPolyPolygon.getB2DPolyPolygon();
+ rB2DPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix( +0x1p-6, -0x1p-6 ));
+
+ return true;
+}
+
+const unsigned char* FreetypeFont::GetTable(const char* pName, sal_uLong* pLength) const
+{
+ return mxFontInfo->GetTable( pName, pLength );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/glyphs/glyphcache.cxx b/vcl/unx/generic/glyphs/glyphcache.cxx
new file mode 100644
index 000000000..79db2d87b
--- /dev/null
+++ b/vcl/unx/generic/glyphs/glyphcache.cxx
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <stdlib.h>
+#include <unx/freetype_glyphcache.hxx>
+#include <unx/gendata.hxx>
+
+#include <fontinstance.hxx>
+
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+
+FreetypeManager::FreetypeManager()
+ : m_nMaxFontId(0)
+{
+ InitFreetype();
+}
+
+FreetypeManager::~FreetypeManager()
+{
+ ClearFontCache();
+}
+
+void FreetypeManager::ClearFontCache()
+{
+ m_aFontInfoList.clear();
+}
+
+FreetypeManager& FreetypeManager::get()
+{
+ GenericUnixSalData* const pSalData(GetGenericUnixSalData());
+ assert(pSalData);
+ return *pSalData->GetFreetypeManager();
+}
+
+FreetypeFontFile* FreetypeManager::FindFontFile(const OString& rNativeFileName)
+{
+ // font file already known? (e.g. for ttc, synthetic, aliased fonts)
+ const char* pFileName = rNativeFileName.getStr();
+ FontFileList::const_iterator it = m_aFontFileList.find(pFileName);
+ if (it != m_aFontFileList.end())
+ return it->second.get();
+
+ // no => create new one
+ FreetypeFontFile* pFontFile = new FreetypeFontFile(rNativeFileName);
+ pFileName = pFontFile->maNativeFileName.getStr();
+ m_aFontFileList[pFileName].reset(pFontFile);
+ return pFontFile;
+}
+
+FreetypeFontInstance::FreetypeFontInstance(const vcl::font::PhysicalFontFace& rPFF, const vcl::font::FontSelectPattern& rFSP)
+ : LogicalFontInstance(rPFF, rFSP)
+ , mxFreetypeFont(FreetypeManager::get().CreateFont(this))
+{
+}
+
+FreetypeFontInstance::~FreetypeFontInstance()
+{
+}
+
+static hb_blob_t* getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData)
+{
+ char pTagName[5];
+ LogicalFontInstance::DecodeOpenTypeTag( nTableTag, pTagName );
+
+ sal_uLong nLength = 0;
+ FreetypeFontInstance* pFontInstance = static_cast<FreetypeFontInstance*>( pUserData );
+ FreetypeFont& rFont = pFontInstance->GetFreetypeFont();
+ const char* pBuffer = reinterpret_cast<const char*>(
+ rFont.GetTable(pTagName, &nLength) );
+
+ hb_blob_t* pBlob = nullptr;
+ if (pBuffer != nullptr)
+ pBlob = hb_blob_create(pBuffer, nLength, HB_MEMORY_MODE_READONLY, nullptr, nullptr);
+
+ return pBlob;
+}
+
+hb_font_t* FreetypeFontInstance::ImplInitHbFont()
+{
+ hb_font_t* pRet = InitHbFont(hb_face_create_for_tables(getFontTable, this, nullptr));
+ assert(mxFreetypeFont);
+ mxFreetypeFont->SetFontVariationsOnHBFont(pRet);
+ return pRet;
+}
+
+bool FreetypeFontInstance::ImplGetGlyphBoundRect(sal_GlyphId nId, tools::Rectangle& rRect, bool bVertical) const
+{
+ assert(mxFreetypeFont);
+ if (!mxFreetypeFont)
+ return false;
+ return mxFreetypeFont->GetGlyphBoundRect(nId, rRect, bVertical);
+}
+
+bool FreetypeFontInstance::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rPoly, bool bVertical) const
+{
+ assert(mxFreetypeFont);
+ if (!mxFreetypeFont)
+ return false;
+ return mxFreetypeFont->GetGlyphOutline(nId, rPoly, bVertical);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/GenPspGfxBackend.cxx b/vcl/unx/generic/print/GenPspGfxBackend.cxx
new file mode 100644
index 000000000..7b461ff4f
--- /dev/null
+++ b/vcl/unx/generic/print/GenPspGfxBackend.cxx
@@ -0,0 +1,412 @@
+/* -*- 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 <unx/GenPspGfxBackend.hxx>
+#include <unx/printergfx.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <salbmp.hxx>
+
+// ----- Implementation of PrinterBmp by means of SalBitmap/BitmapBuffer ---------------
+
+namespace
+{
+class SalPrinterBmp : public psp::PrinterBmp
+{
+private:
+ BitmapBuffer* mpBmpBuffer;
+
+ FncGetPixel mpFncGetPixel;
+ Scanline mpScanAccess;
+ sal_PtrDiff mnScanOffset;
+
+public:
+ explicit SalPrinterBmp(BitmapBuffer* pBitmap);
+
+ virtual sal_uInt32 GetPaletteColor(sal_uInt32 nIdx) const override;
+ virtual sal_uInt32 GetPaletteEntryCount() const override;
+ virtual sal_uInt32 GetPixelRGB(sal_uInt32 nRow, sal_uInt32 nColumn) const override;
+ virtual sal_uInt8 GetPixelGray(sal_uInt32 nRow, sal_uInt32 nColumn) const override;
+ virtual sal_uInt8 GetPixelIdx(sal_uInt32 nRow, sal_uInt32 nColumn) const override;
+ virtual sal_uInt32 GetDepth() const override;
+};
+}
+
+SalPrinterBmp::SalPrinterBmp(BitmapBuffer* pBuffer)
+ : mpBmpBuffer(pBuffer)
+{
+ assert(mpBmpBuffer && "SalPrinterBmp::SalPrinterBmp () can't acquire Bitmap");
+
+ // calibrate scanline buffer
+ if (mpBmpBuffer->mnFormat & ScanlineFormat::TopDown)
+ {
+ mpScanAccess = mpBmpBuffer->mpBits;
+ mnScanOffset = mpBmpBuffer->mnScanlineSize;
+ }
+ else
+ {
+ mpScanAccess
+ = mpBmpBuffer->mpBits + (mpBmpBuffer->mnHeight - 1) * mpBmpBuffer->mnScanlineSize;
+ mnScanOffset = -mpBmpBuffer->mnScanlineSize;
+ }
+
+ // request read access to the pixels
+ mpFncGetPixel = BitmapReadAccess::GetPixelFunction(mpBmpBuffer->mnFormat);
+}
+
+sal_uInt32 SalPrinterBmp::GetDepth() const
+{
+ sal_uInt32 nDepth;
+
+ switch (mpBmpBuffer->mnBitCount)
+ {
+ case 1:
+ nDepth = 1;
+ break;
+
+ case 4:
+ case 8:
+ nDepth = 8;
+ break;
+
+ case 24:
+ case 32:
+ nDepth = 24;
+ break;
+
+ default:
+ nDepth = 1;
+ assert(false && "Error: unsupported bitmap depth in SalPrinterBmp::GetDepth()");
+ break;
+ }
+
+ return nDepth;
+}
+
+sal_uInt32 SalPrinterBmp::GetPaletteEntryCount() const
+{
+ return mpBmpBuffer->maPalette.GetEntryCount();
+}
+
+sal_uInt32 SalPrinterBmp::GetPaletteColor(sal_uInt32 nIdx) const
+{
+ BitmapColor aColor(mpBmpBuffer->maPalette[nIdx]);
+
+ return ((aColor.GetBlue()) & 0x000000ff) | ((aColor.GetGreen() << 8) & 0x0000ff00)
+ | ((aColor.GetRed() << 16) & 0x00ff0000);
+}
+
+sal_uInt32 SalPrinterBmp::GetPixelRGB(sal_uInt32 nRow, sal_uInt32 nColumn) const
+{
+ Scanline pScan = mpScanAccess + nRow * mnScanOffset;
+ BitmapColor aColor = mpFncGetPixel(pScan, nColumn, mpBmpBuffer->maColorMask);
+
+ if (!!mpBmpBuffer->maPalette)
+ GetPaletteColor(aColor.GetIndex());
+
+ return ((aColor.GetBlue()) & 0x000000ff) | ((aColor.GetGreen() << 8) & 0x0000ff00)
+ | ((aColor.GetRed() << 16) & 0x00ff0000);
+}
+
+sal_uInt8 SalPrinterBmp::GetPixelGray(sal_uInt32 nRow, sal_uInt32 nColumn) const
+{
+ Scanline pScan = mpScanAccess + nRow * mnScanOffset;
+ BitmapColor aColor = mpFncGetPixel(pScan, nColumn, mpBmpBuffer->maColorMask);
+
+ if (!!mpBmpBuffer->maPalette)
+ aColor = mpBmpBuffer->maPalette[aColor.GetIndex()];
+
+ return (aColor.GetBlue() * 28UL + aColor.GetGreen() * 151UL + aColor.GetRed() * 77UL) >> 8;
+}
+
+sal_uInt8 SalPrinterBmp::GetPixelIdx(sal_uInt32 nRow, sal_uInt32 nColumn) const
+{
+ Scanline pScan = mpScanAccess + nRow * mnScanOffset;
+ BitmapColor aColor = mpFncGetPixel(pScan, nColumn, mpBmpBuffer->maColorMask);
+
+ if (!!mpBmpBuffer->maPalette)
+ return aColor.GetIndex();
+ else
+ return 0;
+}
+
+GenPspGfxBackend::GenPspGfxBackend(psp::PrinterGfx* pPrinterGfx)
+ : m_pPrinterGfx(pPrinterGfx)
+{
+}
+
+GenPspGfxBackend::~GenPspGfxBackend() {}
+
+void GenPspGfxBackend::Init() {}
+void GenPspGfxBackend::freeResources() {}
+
+bool GenPspGfxBackend::setClipRegion(vcl::Region const& rRegion)
+{
+ // TODO: support polygonal clipregions here
+ RectangleVector aRectangles;
+ rRegion.GetRegionRectangles(aRectangles);
+ m_pPrinterGfx->BeginSetClipRegion();
+
+ for (auto const& rectangle : aRectangles)
+ {
+ const tools::Long nWidth(rectangle.GetWidth());
+ const tools::Long nHeight(rectangle.GetHeight());
+
+ if (nWidth && nHeight)
+ {
+ m_pPrinterGfx->UnionClipRegion(rectangle.Left(), rectangle.Top(), nWidth, nHeight);
+ }
+ }
+
+ m_pPrinterGfx->EndSetClipRegion();
+
+ return true;
+}
+
+void GenPspGfxBackend::ResetClipRegion() { m_pPrinterGfx->ResetClipRegion(); }
+
+sal_uInt16 GenPspGfxBackend::GetBitCount() const { return m_pPrinterGfx->GetBitCount(); }
+
+tools::Long GenPspGfxBackend::GetGraphicsWidth() const { return 0; }
+
+void GenPspGfxBackend::SetLineColor() { m_pPrinterGfx->SetLineColor(); }
+
+void GenPspGfxBackend::SetLineColor(Color nColor)
+{
+ psp::PrinterColor aColor(nColor.GetRed(), nColor.GetGreen(), nColor.GetBlue());
+ m_pPrinterGfx->SetLineColor(aColor);
+}
+
+void GenPspGfxBackend::SetFillColor() { m_pPrinterGfx->SetFillColor(); }
+
+void GenPspGfxBackend::SetFillColor(Color nColor)
+{
+ psp::PrinterColor aColor(nColor.GetRed(), nColor.GetGreen(), nColor.GetBlue());
+ m_pPrinterGfx->SetFillColor(aColor);
+}
+
+void GenPspGfxBackend::SetXORMode(bool bSet, bool /*bInvertOnly*/)
+{
+ SAL_WARN_IF(bSet, "vcl", "Error: PrinterGfx::SetXORMode() not implemented");
+}
+
+void GenPspGfxBackend::SetROPLineColor(SalROPColor /*nROPColor*/)
+{
+ SAL_WARN("vcl", "Error: PrinterGfx::SetROPLineColor() not implemented");
+}
+
+void GenPspGfxBackend::SetROPFillColor(SalROPColor /*nROPColor*/)
+{
+ SAL_WARN("vcl", "Error: PrinterGfx::SetROPFillColor() not implemented");
+}
+
+void GenPspGfxBackend::drawPixel(tools::Long nX, tools::Long nY)
+{
+ m_pPrinterGfx->DrawPixel(Point(nX, nY));
+}
+void GenPspGfxBackend::drawPixel(tools::Long nX, tools::Long nY, Color nColor)
+{
+ psp::PrinterColor aColor(nColor.GetRed(), nColor.GetGreen(), nColor.GetBlue());
+ m_pPrinterGfx->DrawPixel(Point(nX, nY), aColor);
+}
+
+void GenPspGfxBackend::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2)
+{
+ m_pPrinterGfx->DrawLine(Point(nX1, nY1), Point(nX2, nY2));
+}
+void GenPspGfxBackend::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight)
+{
+ m_pPrinterGfx->DrawRect(tools::Rectangle(Point(nX, nY), Size(nWidth, nHeight)));
+}
+
+void GenPspGfxBackend::drawPolyLine(sal_uInt32 nPoints, const Point* pPointArray)
+{
+ m_pPrinterGfx->DrawPolyLine(nPoints, pPointArray);
+}
+
+void GenPspGfxBackend::drawPolygon(sal_uInt32 nPoints, const Point* pPointArray)
+{
+ // Point must be equal to Point! see include/vcl/salgtype.hxx
+ m_pPrinterGfx->DrawPolygon(nPoints, pPointArray);
+}
+
+void GenPspGfxBackend::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point** pPointArray)
+{
+ m_pPrinterGfx->DrawPolyPolygon(nPoly, pPoints, pPointArray);
+}
+
+bool GenPspGfxBackend::drawPolyPolygon(const basegfx::B2DHomMatrix& /*rObjectToDevice*/,
+ const basegfx::B2DPolyPolygon&, double /*fTransparency*/)
+{
+ // TODO: implement and advertise OutDevSupportType::B2DDraw support
+ return false;
+}
+
+bool GenPspGfxBackend::drawPolyLine(const basegfx::B2DHomMatrix& /*rObjectToDevice*/,
+ const basegfx::B2DPolygon& /*rPolygon*/,
+ double /*fTransparency*/, double /*fLineWidth*/,
+ const std::vector<double>* /*pStroke*/, basegfx::B2DLineJoin,
+ css::drawing::LineCap, double /*fMiterMinimumAngle*/,
+ bool /*bPixelSnapHairline*/)
+{
+ // TODO: a PS printer can draw B2DPolyLines almost directly
+ return false;
+}
+
+bool GenPspGfxBackend::drawPolyLineBezier(sal_uInt32 nPoints, const Point* pPointArray,
+ const PolyFlags* pFlagArray)
+{
+ m_pPrinterGfx->DrawPolyLineBezier(nPoints, pPointArray, pFlagArray);
+ return true;
+}
+
+bool GenPspGfxBackend::drawPolygonBezier(sal_uInt32 nPoints, const Point* pPointArray,
+ const PolyFlags* pFlagArray)
+{
+ m_pPrinterGfx->DrawPolygonBezier(nPoints, pPointArray, pFlagArray);
+ return true;
+}
+
+bool GenPspGfxBackend::drawPolyPolygonBezier(sal_uInt32 nPoly, const sal_uInt32* pPoints,
+ const Point* const* pPointArray,
+ const PolyFlags* const* pFlagArray)
+{
+ // Point must be equal to Point! see include/vcl/salgtype.hxx
+ m_pPrinterGfx->DrawPolyPolygonBezier(nPoly, pPoints, pPointArray, pFlagArray);
+ return true;
+}
+
+void GenPspGfxBackend::copyArea(tools::Long /*nDestX*/, tools::Long /*nDestY*/,
+ tools::Long /*nSrcX*/, tools::Long /*nSrcY*/,
+ tools::Long /*nSrcWidth*/, tools::Long /*nSrcHeight*/,
+ bool /*bWindowInvalidate*/)
+{
+ OSL_FAIL("Error: PrinterGfx::CopyArea() not implemented");
+}
+
+void GenPspGfxBackend::copyBits(const SalTwoRect& /*rPosAry*/, SalGraphics* /*pSrcGraphics*/)
+{
+ OSL_FAIL("Error: PrinterGfx::CopyBits() not implemented");
+}
+
+void GenPspGfxBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap)
+{
+ tools::Rectangle aSrc(Point(rPosAry.mnSrcX, rPosAry.mnSrcY),
+ Size(rPosAry.mnSrcWidth, rPosAry.mnSrcHeight));
+
+ tools::Rectangle aDst(Point(rPosAry.mnDestX, rPosAry.mnDestY),
+ Size(rPosAry.mnDestWidth, rPosAry.mnDestHeight));
+
+ BitmapBuffer* pBuffer
+ = const_cast<SalBitmap&>(rSalBitmap).AcquireBuffer(BitmapAccessMode::Read);
+
+ SalPrinterBmp aBmp(pBuffer);
+ m_pPrinterGfx->DrawBitmap(aDst, aSrc, aBmp);
+
+ const_cast<SalBitmap&>(rSalBitmap).ReleaseBuffer(pBuffer, BitmapAccessMode::Read);
+}
+
+void GenPspGfxBackend::drawBitmap(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rSalBitmap*/,
+ const SalBitmap& /*rMaskBitmap*/)
+{
+ OSL_FAIL("Error: no PrinterGfx::DrawBitmap() for transparent bitmap");
+}
+
+void GenPspGfxBackend::drawMask(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rSalBitmap*/,
+ Color /*nMaskColor*/)
+{
+ OSL_FAIL("Error: PrinterGfx::DrawMask() not implemented");
+}
+
+std::shared_ptr<SalBitmap> GenPspGfxBackend::getBitmap(tools::Long /*nX*/, tools::Long /*nY*/,
+ tools::Long /*nWidth*/,
+ tools::Long /*nHeight*/)
+{
+ SAL_INFO("vcl", "Warning: PrinterGfx::GetBitmap() not implemented");
+ return nullptr;
+}
+
+Color GenPspGfxBackend::getPixel(tools::Long /*nX*/, tools::Long /*nY*/)
+{
+ OSL_FAIL("Warning: PrinterGfx::GetPixel() not implemented");
+ return Color();
+}
+
+void GenPspGfxBackend::invert(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/,
+ tools::Long /*nHeight*/, SalInvert /*nFlags*/)
+{
+ OSL_FAIL("Warning: PrinterGfx::Invert() not implemented");
+}
+
+void GenPspGfxBackend::invert(sal_uInt32 /*nPoints*/, const Point* /*pPtAry*/, SalInvert /*nFlags*/)
+{
+ SAL_WARN("vcl", "Error: PrinterGfx::Invert() not implemented");
+}
+
+bool GenPspGfxBackend::drawEPS(tools::Long nX, tools::Long nY, tools::Long nWidth,
+ tools::Long nHeight, void* pPtr, sal_uInt32 nSize)
+{
+ return m_pPrinterGfx->DrawEPS(tools::Rectangle(Point(nX, nY), Size(nWidth, nHeight)), pPtr,
+ nSize);
+}
+
+bool GenPspGfxBackend::blendBitmap(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rBitmap*/)
+{
+ return false;
+}
+
+bool GenPspGfxBackend::blendAlphaBitmap(const SalTwoRect& /*rPosAry*/,
+ const SalBitmap& /*rSrcBitmap*/,
+ const SalBitmap& /*rMaskBitmap*/,
+ const SalBitmap& /*rAlphaBitmap*/)
+{
+ return false;
+}
+
+bool GenPspGfxBackend::drawAlphaBitmap(const SalTwoRect& /*rPosAry*/,
+ const SalBitmap& /*rSourceBitmap*/,
+ const SalBitmap& /*rAlphaBitmap*/)
+{
+ return false;
+}
+
+bool GenPspGfxBackend::drawTransformedBitmap(const basegfx::B2DPoint& /*rNull*/,
+ const basegfx::B2DPoint& /*rX*/,
+ const basegfx::B2DPoint& /*rY*/,
+ const SalBitmap& /*rSourceBitmap*/,
+ const SalBitmap* /*pAlphaBitmap*/, double /*fAlpha*/)
+{
+ return false;
+}
+
+bool GenPspGfxBackend::hasFastDrawTransformedBitmap() const { return false; }
+
+bool GenPspGfxBackend::drawAlphaRect(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/,
+ tools::Long /*nHeight*/, sal_uInt8 /*nTransparency*/)
+{
+ return false;
+}
+
+bool GenPspGfxBackend::drawGradient(const tools::PolyPolygon& /*rPolygon*/,
+ const Gradient& /*rGradient*/)
+{
+ return false;
+}
+
+bool GenPspGfxBackend::implDrawGradient(basegfx::B2DPolyPolygon const& /*rPolyPolygon*/,
+ SalGradient const& /*rGradient*/)
+{
+ return false;
+}
+
+bool GenPspGfxBackend::supportsOperation(OutDevSupportType /*eType*/) const { return false; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/bitmap_gfx.cxx b/vcl/unx/generic/print/bitmap_gfx.cxx
new file mode 100644
index 000000000..2d8649706
--- /dev/null
+++ b/vcl/unx/generic/print/bitmap_gfx.cxx
@@ -0,0 +1,674 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <array>
+#include <memory>
+#include "psputil.hxx"
+
+#include <unx/printergfx.hxx>
+
+namespace psp {
+
+const sal_uInt32 nLineLength = 80;
+const sal_uInt32 nBufferSize = 16384;
+
+/*
+ *
+ * Bitmap compression / Hex encoding / Ascii85 Encoding
+ *
+ */
+
+PrinterBmp::~PrinterBmp()
+{
+}
+
+/* HexEncoder */
+
+namespace {
+
+class HexEncoder
+{
+private:
+
+ osl::File* mpFile;
+ sal_uInt32 mnColumn;
+ sal_uInt32 mnOffset;
+ OStringBuffer mpFileBuffer;
+
+public:
+
+ explicit HexEncoder (osl::File* pFile);
+ ~HexEncoder ();
+ void WriteAscii (sal_uInt8 nByte);
+ void EncodeByte (sal_uInt8 nByte);
+ void FlushLine ();
+};
+
+}
+
+HexEncoder::HexEncoder (osl::File* pFile) :
+ mpFile (pFile),
+ mnColumn (0),
+ mnOffset (0)
+{}
+
+HexEncoder::~HexEncoder ()
+{
+ FlushLine ();
+ if (mnColumn > 0)
+ WritePS (mpFile, "\n");
+}
+
+void
+HexEncoder::WriteAscii (sal_uInt8 nByte)
+{
+ sal_uInt32 nOff = psp::getHexValueOf (nByte, mpFileBuffer);
+ mnColumn += nOff;
+ mnOffset += nOff;
+
+ if (mnColumn >= nLineLength)
+ {
+ mnOffset += psp::appendStr ("\n", mpFileBuffer);
+ mnColumn = 0;
+ }
+ if (mnOffset >= nBufferSize)
+ FlushLine ();
+}
+
+void
+HexEncoder::EncodeByte (sal_uInt8 nByte)
+{
+ WriteAscii (nByte);
+}
+
+void
+HexEncoder::FlushLine ()
+{
+ if (mnOffset > 0)
+ {
+ WritePS (mpFile, mpFileBuffer.makeStringAndClear());
+ mnOffset = 0;
+ }
+}
+
+/* Ascii85 encoder, is abi compatible with HexEncoder but writes a ~> to
+ indicate end of data EOD */
+
+namespace {
+
+class Ascii85Encoder
+{
+private:
+
+ osl::File* mpFile;
+ sal_uInt32 mnByte;
+ sal_uInt8 mpByteBuffer[4];
+
+ sal_uInt32 mnColumn;
+ sal_uInt32 mnOffset;
+ OStringBuffer mpFileBuffer;
+
+ inline void PutByte (sal_uInt8 nByte);
+ inline void PutEOD ();
+ void ConvertToAscii85 ();
+ void FlushLine ();
+
+public:
+
+ explicit Ascii85Encoder (osl::File* pFile);
+ virtual ~Ascii85Encoder ();
+ virtual void EncodeByte (sal_uInt8 nByte);
+ void WriteAscii (sal_uInt8 nByte);
+};
+
+}
+
+Ascii85Encoder::Ascii85Encoder (osl::File* pFile) :
+ mpFile (pFile),
+ mnByte (0),
+ mnColumn (0),
+ mnOffset (0)
+{}
+
+inline void
+Ascii85Encoder::PutByte (sal_uInt8 nByte)
+{
+ mpByteBuffer [mnByte++] = nByte;
+}
+
+inline void
+Ascii85Encoder::PutEOD ()
+{
+ WritePS (mpFile, "~>\n");
+}
+
+void
+Ascii85Encoder::ConvertToAscii85 ()
+{
+ // Add (4 - mnByte) zero padding bytes:
+ if (mnByte < 4)
+ std::memset (mpByteBuffer + mnByte, 0, (4 - mnByte) * sizeof(sal_uInt8));
+
+ sal_uInt32 nByteValue = mpByteBuffer[0] * 256 * 256 * 256
+ + mpByteBuffer[1] * 256 * 256
+ + mpByteBuffer[2] * 256
+ + mpByteBuffer[3];
+
+ if (nByteValue == 0 && mnByte == 4)
+ {
+ /* special case of 4 Bytes in row */
+ mpFileBuffer.append('z');
+
+ mnOffset += 1;
+ mnColumn += 1;
+ }
+ else
+ {
+ /* real ascii85 encoding */
+
+ // Of the up to 5 characters to be generated, do not generate the last (4 - mnByte) ones
+ // that correspond to the (4 - mnByte) zero padding bytes added to the input:
+
+ auto const pos = mpFileBuffer.getLength();
+ if (mnByte == 4) {
+ mpFileBuffer.insert(pos, char((nByteValue % 85) + 33));
+ }
+ nByteValue /= 85;
+ if (mnByte >= 3) {
+ mpFileBuffer.insert(pos, char((nByteValue % 85) + 33));
+ }
+ nByteValue /= 85;
+ if (mnByte >= 2) {
+ mpFileBuffer.insert(pos, char((nByteValue % 85) + 33));
+ }
+ nByteValue /= 85;
+ mpFileBuffer.insert(pos, char((nByteValue % 85) + 33));
+ nByteValue /= 85;
+ mpFileBuffer.insert(pos, char((nByteValue % 85) + 33));
+
+ mnColumn += (mnByte + 1);
+ mnOffset += (mnByte + 1);
+
+ /* insert a newline if necessary */
+ if (mnColumn > nLineLength)
+ {
+ sal_uInt32 nEolOff = mnColumn - nLineLength;
+ auto const posNl = pos + (mnByte + 1) - nEolOff;
+
+ mpFileBuffer.insert(posNl, '\n');
+
+ mnOffset++;
+ mnColumn = nEolOff;
+ }
+ }
+
+ mnByte = 0;
+}
+
+void
+Ascii85Encoder::WriteAscii (sal_uInt8 nByte)
+{
+ PutByte (nByte);
+ if (mnByte == 4)
+ ConvertToAscii85 ();
+
+ if (mnColumn >= nLineLength)
+ {
+ mnOffset += psp::appendStr ("\n", mpFileBuffer);
+ mnColumn = 0;
+ }
+ if (mnOffset >= nBufferSize)
+ FlushLine ();
+}
+
+void
+Ascii85Encoder::EncodeByte (sal_uInt8 nByte)
+{
+ WriteAscii (nByte);
+}
+
+void
+Ascii85Encoder::FlushLine ()
+{
+ if (mnOffset > 0)
+ {
+ WritePS (mpFile, mpFileBuffer.makeStringAndClear());
+ mnOffset = 0;
+ }
+}
+
+Ascii85Encoder::~Ascii85Encoder ()
+{
+ if (mnByte > 0)
+ ConvertToAscii85 ();
+ if (mnOffset > 0)
+ FlushLine ();
+ PutEOD ();
+}
+
+/* LZW encoder */
+
+namespace {
+
+class LZWEncoder : public Ascii85Encoder
+{
+private:
+
+ struct LZWCTreeNode
+ {
+ LZWCTreeNode* mpBrother; // next node with same parent
+ LZWCTreeNode* mpFirstChild; // first son
+ sal_uInt16 mnCode; // code for the string
+ sal_uInt16 mnValue; // pixelvalue
+ };
+
+ std::array<LZWCTreeNode, 4096>
+ maTable; // LZW compression data
+ LZWCTreeNode* mpPrefix; // the compression is as same as the TIFF compression
+ static constexpr sal_uInt16 gnDataSize = 8;
+ static constexpr sal_uInt16 gnClearCode = 1 << gnDataSize;
+ static constexpr sal_uInt16 gnEOICode = gnClearCode + 1;
+ sal_uInt16 mnTableSize;
+ sal_uInt16 mnCodeSize;
+ sal_uInt32 mnOffset;
+ sal_uInt32 mdwShift;
+
+ void WriteBits (sal_uInt16 nCode, sal_uInt16 nCodeLen);
+
+public:
+
+ explicit LZWEncoder (osl::File* pOutputFile);
+ virtual ~LZWEncoder () override;
+
+ virtual void EncodeByte (sal_uInt8 nByte) override;
+};
+
+}
+
+LZWEncoder::LZWEncoder(osl::File* pOutputFile) :
+ Ascii85Encoder (pOutputFile),
+ maTable{{}},
+ mpPrefix(nullptr),
+ mnTableSize(gnEOICode + 1),
+ mnCodeSize(gnDataSize + 1),
+ mnOffset(32), // free bits in dwShift
+ mdwShift(0)
+{
+ for (sal_uInt32 i = 0; i < 4096; i++)
+ {
+ maTable[i].mpBrother = nullptr;
+ maTable[i].mpFirstChild = nullptr;
+ maTable[i].mnCode = i;
+ maTable[i].mnValue = static_cast<sal_uInt8>(maTable[i].mnCode);
+ }
+
+ WriteBits( gnClearCode, mnCodeSize );
+}
+
+LZWEncoder::~LZWEncoder()
+{
+ if (mpPrefix)
+ WriteBits (mpPrefix->mnCode, mnCodeSize);
+
+ WriteBits (gnEOICode, mnCodeSize);
+}
+
+void
+LZWEncoder::WriteBits (sal_uInt16 nCode, sal_uInt16 nCodeLen)
+{
+ mdwShift |= (nCode << (mnOffset - nCodeLen));
+ mnOffset -= nCodeLen;
+ while (mnOffset < 24)
+ {
+ WriteAscii (static_cast<sal_uInt8>(mdwShift >> 24));
+ mdwShift <<= 8;
+ mnOffset += 8;
+ }
+ if (nCode == 257 && mnOffset != 32)
+ WriteAscii (static_cast<sal_uInt8>(mdwShift >> 24));
+}
+
+void
+LZWEncoder::EncodeByte (sal_uInt8 nByte )
+{
+ LZWCTreeNode* p;
+ sal_uInt16 i;
+ sal_uInt8 nV;
+
+ if (!mpPrefix)
+ {
+ mpPrefix = maTable.data() + nByte;
+ }
+ else
+ {
+ nV = nByte;
+ for (p = mpPrefix->mpFirstChild; p != nullptr; p = p->mpBrother)
+ {
+ if (p->mnValue == nV)
+ break;
+ }
+
+ if (p != nullptr)
+ {
+ mpPrefix = p;
+ }
+ else
+ {
+ WriteBits (mpPrefix->mnCode, mnCodeSize);
+
+ if (mnTableSize == 409)
+ {
+ WriteBits (gnClearCode, mnCodeSize);
+
+ for (i = 0; i < gnClearCode; i++)
+ maTable[i].mpFirstChild = nullptr;
+
+ mnCodeSize = gnDataSize + 1;
+ mnTableSize = gnEOICode + 1;
+ }
+ else
+ {
+ if(mnTableSize == static_cast<sal_uInt16>((1 << mnCodeSize) - 1))
+ mnCodeSize++;
+
+ p = maTable.data() + (mnTableSize++);
+ p->mpBrother = mpPrefix->mpFirstChild;
+ mpPrefix->mpFirstChild = p;
+ p->mnValue = nV;
+ p->mpFirstChild = nullptr;
+ }
+
+ mpPrefix = maTable.data() + nV;
+ }
+ }
+}
+
+/*
+ *
+ * bitmap handling routines
+ *
+ */
+
+void
+PrinterGfx::DrawBitmap (const tools::Rectangle& rDest, const tools::Rectangle& rSrc,
+ const PrinterBmp& rBitmap)
+{
+ double fScaleX = static_cast<double>(rDest.GetWidth());
+ double fScaleY = static_cast<double>(rDest.GetHeight());
+ if(rSrc.GetWidth() > 0)
+ {
+ fScaleX = static_cast<double>(rDest.GetWidth()) / static_cast<double>(rSrc.GetWidth());
+ }
+ if(rSrc.GetHeight() > 0)
+ {
+ fScaleY = static_cast<double>(rDest.GetHeight()) / static_cast<double>(rSrc.GetHeight());
+ }
+ PSGSave ();
+ PSTranslate (rDest.BottomLeft());
+ PSScale (fScaleX, fScaleY);
+
+ if (mnPSLevel >= 2)
+ {
+ if (rBitmap.GetDepth() == 1)
+ {
+ DrawPS2MonoImage (rBitmap, rSrc);
+ }
+ else
+ if (rBitmap.GetDepth() == 8 && mbColor)
+ {
+ // if the palette is larger than the image itself print it as a truecolor
+ // image to save diskspace. This is important for printing transparent
+ // bitmaps that are disassembled into small pieces
+ sal_Int32 nImageSz = rSrc.GetWidth() * rSrc.GetHeight();
+ sal_Int32 nPaletteSz = rBitmap.GetPaletteEntryCount();
+ if ((nImageSz < nPaletteSz) || (nImageSz < 24) )
+ DrawPS2TrueColorImage (rBitmap, rSrc);
+ else
+ DrawPS2PaletteImage (rBitmap, rSrc);
+ }
+ else
+ if (rBitmap.GetDepth() == 24 && mbColor)
+ {
+ DrawPS2TrueColorImage (rBitmap, rSrc);
+ }
+ else
+ {
+ DrawPS2GrayImage (rBitmap, rSrc);
+ }
+ }
+ else
+ {
+ DrawPS1GrayImage (rBitmap, rSrc);
+ }
+
+ PSGRestore ();
+}
+
+/*
+ *
+ * Implementation: PS Level 1
+ *
+ */
+
+void
+PrinterGfx::DrawPS1GrayImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea)
+{
+ sal_uInt32 nWidth = rArea.GetWidth();
+ sal_uInt32 nHeight = rArea.GetHeight();
+
+ OStringBuffer pGrayImage;
+
+ // image header
+ psp::getValueOf (nWidth, pGrayImage);
+ psp::appendStr (" ", pGrayImage);
+ psp::getValueOf (nHeight, pGrayImage);
+ psp::appendStr (" 8 ", pGrayImage);
+ psp::appendStr ("[ 1 0 0 1 0 ", pGrayImage);
+ psp::getValueOf (nHeight, pGrayImage);
+ psp::appendStr ("]", pGrayImage);
+ psp::appendStr (" {currentfile ", pGrayImage);
+ psp::getValueOf (nWidth, pGrayImage);
+ psp::appendStr (" string readhexstring pop}\n", pGrayImage);
+ psp::appendStr ("image\n", pGrayImage);
+
+ WritePS (mpPageBody, pGrayImage.makeStringAndClear());
+
+ // image body
+ HexEncoder aEncoder(mpPageBody);
+
+ for (tools::Long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
+ {
+ for (tools::Long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
+ {
+ unsigned char nByte = rBitmap.GetPixelGray (nRow, nColumn);
+ aEncoder.EncodeByte (nByte);
+ }
+ }
+
+ WritePS (mpPageBody, "\n");
+}
+
+/*
+ *
+ * Implementation: PS Level 2
+ *
+ */
+
+void
+PrinterGfx::writePS2ImageHeader (const tools::Rectangle& rArea, psp::ImageType nType)
+{
+ OStringBuffer pImage;
+
+ sal_Int32 nDictType = 0;
+ switch (nType)
+ {
+ case psp::ImageType::TrueColorImage: nDictType = 0; break;
+ case psp::ImageType::PaletteImage: nDictType = 1; break;
+ case psp::ImageType::GrayScaleImage: nDictType = 2; break;
+ case psp::ImageType::MonochromeImage: nDictType = 3; break;
+ default: break;
+ }
+
+ psp::getValueOf (rArea.GetWidth(), pImage);
+ psp::appendStr (" ", pImage);
+ psp::getValueOf (rArea.GetHeight(), pImage);
+ psp::appendStr (" ", pImage);
+ psp::getValueOf (nDictType, pImage);
+ psp::appendStr (" ", pImage);
+ psp::getValueOf (sal_Int32(1), pImage); // nCompressType
+ psp::appendStr (" psp_imagedict image\n", pImage);
+
+ WritePS (mpPageBody, pImage.makeStringAndClear());
+}
+
+void
+PrinterGfx::writePS2Colorspace(const PrinterBmp& rBitmap, psp::ImageType nType)
+{
+ switch (nType)
+ {
+ case psp::ImageType::GrayScaleImage:
+
+ WritePS (mpPageBody, "/DeviceGray setcolorspace\n");
+ break;
+
+ case psp::ImageType::TrueColorImage:
+
+ WritePS (mpPageBody, "/DeviceRGB setcolorspace\n");
+ break;
+
+ case psp::ImageType::MonochromeImage:
+ case psp::ImageType::PaletteImage:
+ {
+
+ OStringBuffer pImage;
+
+ const sal_uInt32 nSize = rBitmap.GetPaletteEntryCount();
+
+ psp::appendStr ("[/Indexed /DeviceRGB ", pImage);
+ psp::getValueOf (nSize - 1, pImage);
+ psp::appendStr ("\npsp_lzwstring\n", pImage);
+ WritePS (mpPageBody, pImage.makeStringAndClear());
+
+ LZWEncoder aEncoder(mpPageBody);
+ for (sal_uInt32 i = 0; i < nSize; i++)
+ {
+ PrinterColor aColor = rBitmap.GetPaletteColor(i);
+
+ aEncoder.EncodeByte (aColor.GetRed());
+ aEncoder.EncodeByte (aColor.GetGreen());
+ aEncoder.EncodeByte (aColor.GetBlue());
+ }
+
+ WritePS (mpPageBody, "pop ] setcolorspace\n");
+ }
+ break;
+ default: break;
+ }
+}
+
+void
+PrinterGfx::DrawPS2GrayImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea)
+{
+ writePS2Colorspace(rBitmap, psp::ImageType::GrayScaleImage);
+ writePS2ImageHeader(rArea, psp::ImageType::GrayScaleImage);
+
+ LZWEncoder aEncoder(mpPageBody);
+
+ for (tools::Long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
+ {
+ for (tools::Long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
+ {
+ unsigned char nByte = rBitmap.GetPixelGray (nRow, nColumn);
+ aEncoder.EncodeByte (nByte);
+ }
+ }
+}
+
+void
+PrinterGfx::DrawPS2MonoImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea)
+{
+ writePS2Colorspace(rBitmap, psp::ImageType::MonochromeImage);
+ writePS2ImageHeader(rArea, psp::ImageType::MonochromeImage);
+
+ LZWEncoder aEncoder(mpPageBody);
+
+ for (tools::Long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
+ {
+ tools::Long nBitPos = 0;
+ unsigned char nByte = 0;
+
+ for (tools::Long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
+ {
+ unsigned char nBit = rBitmap.GetPixelIdx (nRow, nColumn);
+ nByte |= nBit << (7 - nBitPos);
+
+ if (++nBitPos == 8)
+ {
+ aEncoder.EncodeByte (nByte);
+ nBitPos = 0;
+ nByte = 0;
+ }
+ }
+ // keep the row byte aligned
+ if (nBitPos != 0)
+ aEncoder.EncodeByte (nByte);
+ }
+}
+
+void
+PrinterGfx::DrawPS2PaletteImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea)
+{
+ writePS2Colorspace(rBitmap, psp::ImageType::PaletteImage);
+ writePS2ImageHeader(rArea, psp::ImageType::PaletteImage);
+
+ LZWEncoder aEncoder(mpPageBody);
+
+ for (tools::Long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
+ {
+ for (tools::Long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
+ {
+ unsigned char nByte = rBitmap.GetPixelIdx (nRow, nColumn);
+ aEncoder.EncodeByte (nByte);
+ }
+ }
+}
+
+void
+PrinterGfx::DrawPS2TrueColorImage (const PrinterBmp& rBitmap, const tools::Rectangle& rArea)
+{
+ writePS2Colorspace(rBitmap, psp::ImageType::TrueColorImage);
+ writePS2ImageHeader(rArea, psp::ImageType::TrueColorImage);
+
+ LZWEncoder aEncoder(mpPageBody);
+
+ for (tools::Long nRow = rArea.Top(); nRow <= rArea.Bottom(); nRow++)
+ {
+ for (tools::Long nColumn = rArea.Left(); nColumn <= rArea.Right(); nColumn++)
+ {
+ PrinterColor aColor = rBitmap.GetPixelRGB (nRow, nColumn);
+ aEncoder.EncodeByte (aColor.GetRed());
+ aEncoder.EncodeByte (aColor.GetGreen());
+ aEncoder.EncodeByte (aColor.GetBlue());
+ }
+ }
+}
+
+} /* namespace psp */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/common_gfx.cxx b/vcl/unx/generic/print/common_gfx.cxx
new file mode 100644
index 000000000..aba50ece2
--- /dev/null
+++ b/vcl/unx/generic/print/common_gfx.cxx
@@ -0,0 +1,1152 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include "psputil.hxx"
+#include "glyphset.hxx"
+
+#include <unx/printergfx.hxx>
+#include <unx/printerjob.hxx>
+#include <unx/fontmanager.hxx>
+#include <strhelper.hxx>
+#include <printerinfomanager.hxx>
+
+#include <tools/color.hxx>
+#include <tools/poly.hxx>
+#include <tools/stream.hxx>
+#include <o3tl/string_view.hxx>
+
+using namespace psp ;
+
+const sal_Int32 nMaxTextColumn = 80;
+
+GraphicsStatus::GraphicsStatus() :
+ maEncoding(RTL_TEXTENCODING_DONTKNOW),
+ mbArtItalic( false ),
+ mbArtBold( false ),
+ mnTextHeight( 0 ),
+ mnTextWidth( 0 ),
+ mfLineWidth( -1 )
+{
+}
+
+/*
+ * non graphics routines
+ */
+
+void
+PrinterGfx::Init (PrinterJob &rPrinterJob)
+{
+ mpPageBody = rPrinterJob.GetCurrentPageBody ();
+ mnDepth = rPrinterJob.GetDepth ();
+ mnPSLevel = rPrinterJob.GetPostscriptLevel ();
+ mbColor = rPrinterJob.IsColorPrinter ();
+
+ mnDpi = rPrinterJob.GetResolution();
+ rPrinterJob.GetScale (mfScaleX, mfScaleY);
+ const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( rPrinterJob.GetPrinterName() ) );
+ mbUploadPS42Fonts = rInfo.m_pParser && rInfo.m_pParser->isType42Capable();
+}
+
+void
+PrinterGfx::Init (const JobData& rData)
+{
+ mpPageBody = nullptr;
+ mnDepth = rData.m_nColorDepth;
+ mnPSLevel = rData.m_nPSLevel ? rData.m_nPSLevel : (rData.m_pParser ? rData.m_pParser->getLanguageLevel() : 2 );
+ mbColor = rData.m_nColorDevice ? ( rData.m_nColorDevice != -1 ) : ( rData.m_pParser == nullptr || rData.m_pParser->isColorDevice() );
+ int nRes = rData.m_aContext.getRenderResolution();
+ mnDpi = nRes;
+ mfScaleX = 72.0 / static_cast<double>(mnDpi);
+ mfScaleY = 72.0 / static_cast<double>(mnDpi);
+ const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( rData.m_aPrinterName ) );
+ mbUploadPS42Fonts = rInfo.m_pParser && rInfo.m_pParser->isType42Capable();
+}
+
+
+PrinterGfx::PrinterGfx()
+ : mfScaleX(0.0)
+ , mfScaleY(0.0)
+ , mnDpi(0)
+ , mnDepth(0)
+ , mnPSLevel(0)
+ , mbColor(false)
+ , mbUploadPS42Fonts(false)
+ , mpPageBody(nullptr)
+ , mnFontID(0)
+ , mnTextAngle(0)
+ , mbTextVertical(false)
+ , mrFontMgr(PrintFontManager::get())
+ , maFillColor(0xff,0,0)
+ , maTextColor(0,0,0)
+ , maLineColor(0, 0xff, 0)
+{
+ maVirtualStatus.mfLineWidth = 1.0;
+ maVirtualStatus.mnTextHeight = 12;
+ maVirtualStatus.mnTextWidth = 0;
+
+ maGraphicsStack.emplace_back( );
+}
+
+PrinterGfx::~PrinterGfx()
+{
+}
+
+void
+PrinterGfx::Clear()
+{
+ mpPageBody = nullptr;
+ mnFontID = 0;
+ maVirtualStatus = GraphicsStatus();
+ maVirtualStatus.mnTextHeight = 12;
+ maVirtualStatus.mnTextWidth = 0;
+ maVirtualStatus.mfLineWidth = 1.0;
+ mbTextVertical = false;
+ maLineColor = PrinterColor();
+ maFillColor = PrinterColor();
+ maTextColor = PrinterColor();
+ mnDpi = 300;
+ mnDepth = 24;
+ mnPSLevel = 2;
+ mbColor = true;
+ mnTextAngle = 0_deg10;
+
+ maClipRegion.clear();
+ maGraphicsStack.clear();
+ maGraphicsStack.emplace_back( );
+}
+
+/*
+ * clip region handling
+ */
+
+void
+PrinterGfx::ResetClipRegion()
+{
+ maClipRegion.clear();
+ PSGRestore ();
+ PSGSave (); // get "clean" clippath
+}
+
+void
+PrinterGfx::BeginSetClipRegion()
+{
+ maClipRegion.clear();
+}
+
+void
+PrinterGfx::UnionClipRegion (sal_Int32 nX,sal_Int32 nY,sal_Int32 nDX,sal_Int32 nDY)
+{
+ if( nDX && nDY )
+ maClipRegion.emplace_back(Point(nX,nY ), Size(nDX,nDY));
+}
+
+bool
+PrinterGfx::JoinVerticalClipRectangles( std::list< tools::Rectangle >::iterator& it,
+ Point& rOldPoint, sal_Int32& rColumn )
+{
+ bool bSuccess = false;
+
+ std::list< tools::Rectangle >::iterator tempit, nextit;
+ nextit = it;
+ ++nextit;
+ std::list< Point > leftside, rightside;
+
+ tools::Rectangle aLastRect( *it );
+ leftside.emplace_back( it->Left(), it->Top() );
+ rightside.emplace_back( it->Right()+1, it->Top() );
+ while( nextit != maClipRegion.end() )
+ {
+ tempit = nextit;
+ ++tempit;
+ if( nextit->Top() == aLastRect.Bottom()+1 )
+ {
+ if(
+ ( nextit->Left() >= aLastRect.Left() && nextit->Left() <= aLastRect.Right() ) // left endpoint touches last rectangle
+ ||
+ ( nextit->Right() >= aLastRect.Left() && nextit->Right() <= aLastRect.Right() ) // right endpoint touches last rectangle
+ ||
+ ( nextit->Left() <= aLastRect.Left() && nextit->Right() >= aLastRect.Right() ) // whole line touches last rectangle
+ )
+ {
+ if( aLastRect.GetHeight() > 1 ||
+ std::abs( aLastRect.Left() - nextit->Left() ) > 2 ||
+ std::abs( aLastRect.Right() - nextit->Right() ) > 2
+ )
+ {
+ leftside.emplace_back( aLastRect.Left(), aLastRect.Bottom()+1 );
+ rightside.emplace_back( aLastRect.Right()+1, aLastRect.Bottom()+1 );
+ }
+ aLastRect = *nextit;
+ leftside.push_back( aLastRect.TopLeft() );
+ rightside.push_back( aLastRect.TopRight() );
+ maClipRegion.erase( nextit );
+ }
+ }
+ nextit = tempit;
+ }
+ if( leftside.size() > 1 )
+ {
+ // push the last coordinates
+ leftside.emplace_back( aLastRect.Left(), aLastRect.Bottom()+1 );
+ rightside.emplace_back( aLastRect.Right()+1, aLastRect.Bottom()+1 );
+
+ // cool, we can concatenate rectangles
+ const int nDX = -65536, nDY = 65536;
+ int nNewDX = 0, nNewDY = 0;
+
+ Point aLastPoint = leftside.front();
+ PSBinMoveTo (aLastPoint, rOldPoint, rColumn);
+ leftside.pop_front();
+ while( !leftside.empty() )
+ {
+ Point aPoint (leftside.front());
+ leftside.pop_front();
+ // may have been the last one
+ if( !leftside.empty() )
+ {
+ nNewDX = aPoint.X() - aLastPoint.X();
+ nNewDY = aPoint.Y() - aLastPoint.Y();
+ if( nNewDX != 0 &&
+ static_cast<double>(nNewDY)/static_cast<double>(nNewDX) == double(nDY)/double(nDX) )
+ continue;
+ }
+ PSBinLineTo (aPoint, rOldPoint, rColumn);
+ aLastPoint = aPoint;
+ }
+
+ aLastPoint = rightside.back();
+ PSBinLineTo (aLastPoint, rOldPoint, rColumn);
+ rightside.pop_back();
+ while( !rightside.empty() )
+ {
+ Point aPoint (rightside.back());
+ rightside.pop_back();
+ if( !rightside.empty() )
+ {
+ nNewDX = aPoint.X() - aLastPoint.X();
+ nNewDY = aPoint.Y() - aLastPoint.Y();
+ if( nNewDX != 0 &&
+ static_cast<double>(nNewDY)/static_cast<double>(nNewDX) == double(nDY)/double(nDX) )
+ continue;
+ }
+ PSBinLineTo (aPoint, rOldPoint, rColumn);
+ }
+
+ tempit = it;
+ ++tempit;
+ maClipRegion.erase( it );
+ it = tempit;
+ bSuccess = true;
+ }
+ return bSuccess;
+}
+
+void
+PrinterGfx::EndSetClipRegion()
+{
+ PSGRestore ();
+ PSGSave (); // get "clean" clippath
+
+ PSBinStartPath ();
+ Point aOldPoint (0, 0);
+ sal_Int32 nColumn = 0;
+
+ std::list< tools::Rectangle >::iterator it = maClipRegion.begin();
+ while( it != maClipRegion.end() )
+ {
+ // try to concatenate adjacent rectangles
+ // first try in y direction, then in x direction
+ if( ! JoinVerticalClipRectangles( it, aOldPoint, nColumn ) )
+ {
+ // failed, so it is a single rectangle
+ PSBinMoveTo (Point( it->Left()-1, it->Top()-1), aOldPoint, nColumn );
+ PSBinLineTo (Point( it->Left()-1, it->Bottom()+1 ), aOldPoint, nColumn );
+ PSBinLineTo (Point( it->Right()+1, it->Bottom()+1 ), aOldPoint, nColumn );
+ PSBinLineTo (Point( it->Right()+1, it->Top()-1 ), aOldPoint, nColumn );
+ ++it;
+ }
+ }
+
+ PSBinEndPath ();
+
+ WritePS (mpPageBody, "closepath clip newpath\n");
+ maClipRegion.clear();
+}
+
+/*
+ * draw graphic primitives
+ */
+
+void
+PrinterGfx::DrawRect (const tools::Rectangle& rRectangle )
+{
+ OStringBuffer pRect;
+
+ psp::getValueOf (rRectangle.Left(), pRect);
+ psp::appendStr (" ", pRect);
+ psp::getValueOf (rRectangle.Top(), pRect);
+ psp::appendStr (" ", pRect);
+ psp::getValueOf (rRectangle.GetWidth(), pRect);
+ psp::appendStr (" ", pRect);
+ psp::getValueOf (rRectangle.GetHeight(), pRect);
+ psp::appendStr (" ", pRect);
+ auto const rect = pRect.makeStringAndClear();
+
+ if( maFillColor.Is() )
+ {
+ PSSetColor (maFillColor);
+ PSSetColor ();
+ WritePS (mpPageBody, rect);
+ WritePS (mpPageBody, "rectfill\n");
+ }
+ if( maLineColor.Is() )
+ {
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+ WritePS (mpPageBody, rect);
+ WritePS (mpPageBody, "rectstroke\n");
+ }
+}
+
+void
+PrinterGfx::DrawLine (const Point& rFrom, const Point& rTo)
+{
+ if( maLineColor.Is() )
+ {
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+
+ PSMoveTo (rFrom);
+ PSLineTo (rTo);
+ WritePS (mpPageBody, "stroke\n" );
+ }
+}
+
+void
+PrinterGfx::DrawPixel (const Point& rPoint, const PrinterColor& rPixelColor)
+{
+ if( rPixelColor.Is() )
+ {
+ PSSetColor (rPixelColor);
+ PSSetColor ();
+
+ PSMoveTo (rPoint);
+ PSLineTo (Point (rPoint.X ()+1, rPoint.Y ()));
+ PSLineTo (Point (rPoint.X ()+1, rPoint.Y ()+1));
+ PSLineTo (Point (rPoint.X (), rPoint.Y ()+1));
+ WritePS (mpPageBody, "fill\n" );
+ }
+}
+
+void
+PrinterGfx::DrawPolyLine (sal_uInt32 nPoints, const Point* pPath)
+{
+ if( maLineColor.Is() && nPoints && pPath )
+ {
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+
+ PSBinCurrentPath (nPoints, pPath);
+
+ WritePS (mpPageBody, "stroke\n" );
+ }
+}
+
+void
+PrinterGfx::DrawPolygon (sal_uInt32 nPoints, const Point* pPath)
+{
+ // premature end of operation
+ if (nPoints <= 0 || (pPath == nullptr) || !(maFillColor.Is() || maLineColor.Is()))
+ return;
+
+ // setup closed path
+ Point aPoint( 0, 0 );
+ sal_Int32 nColumn( 0 );
+
+ PSBinStartPath();
+ PSBinMoveTo( pPath[0], aPoint, nColumn );
+ for( unsigned int n = 1; n < nPoints; n++ )
+ PSBinLineTo( pPath[n], aPoint, nColumn );
+ if( pPath[0] != pPath[nPoints-1] )
+ PSBinLineTo( pPath[0], aPoint, nColumn );
+ PSBinEndPath();
+
+ // fill the polygon first, then draw the border, note that fill and
+ // stroke reset the currentpath
+
+ // if fill and stroke, save the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGSave();
+
+ if (maFillColor.Is ())
+ {
+ PSSetColor (maFillColor);
+ PSSetColor ();
+ WritePS (mpPageBody, "eofill\n");
+ }
+
+ // restore the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGRestore();
+
+ if (maLineColor.Is ())
+ {
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+ WritePS (mpPageBody, "stroke\n");
+ }
+}
+
+void
+PrinterGfx::DrawPolyPolygon (sal_uInt32 nPoly, const sal_uInt32* pSizes, const Point** pPaths )
+{
+ // sanity check
+ if ( !nPoly || !pPaths || !(maFillColor.Is() || maLineColor.Is()))
+ return;
+
+ // setup closed path
+ for( unsigned int i = 0; i < nPoly; i++ )
+ {
+ Point aPoint( 0, 0 );
+ sal_Int32 nColumn( 0 );
+
+ PSBinStartPath();
+ PSBinMoveTo( pPaths[i][0], aPoint, nColumn );
+ for( unsigned int n = 1; n < pSizes[i]; n++ )
+ PSBinLineTo( pPaths[i][n], aPoint, nColumn );
+ if( pPaths[i][0] != pPaths[i][pSizes[i]-1] )
+ PSBinLineTo( pPaths[i][0], aPoint, nColumn );
+ PSBinEndPath();
+ }
+
+ // if eofill and stroke, save the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGSave();
+
+ // first draw area
+ if( maFillColor.Is() )
+ {
+ PSSetColor (maFillColor);
+ PSSetColor ();
+ WritePS (mpPageBody, "eofill\n");
+ }
+
+ // restore the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGRestore();
+
+ // now draw outlines
+ if( maLineColor.Is() )
+ {
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+ WritePS (mpPageBody, "stroke\n");
+ }
+}
+
+/*
+ * Bezier Polygon Drawing methods.
+ */
+
+void
+PrinterGfx::DrawPolyLineBezier (sal_uInt32 nPoints, const Point* pPath, const PolyFlags* pFlgAry)
+{
+ const sal_uInt32 nBezString= 1024;
+ char pString[nBezString];
+
+ if ( nPoints <= 1 || !maLineColor.Is() || !pPath )
+ return;
+
+ PSSetColor (maLineColor);
+ PSSetColor ();
+ PSSetLineWidth ();
+
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " moveto\n", sal_Int64(pPath[0].X()), sal_Int64(pPath[0].Y()));
+ WritePS(mpPageBody, pString);
+
+ // Handle the drawing of mixed lines mixed with curves
+ // - a normal point followed by a normal point is a line
+ // - a normal point followed by 2 control points and a normal point is a curve
+ for (unsigned int i=1; i<nPoints;)
+ {
+ if (pFlgAry[i] != PolyFlags::Control) //If the next point is a PolyFlags::Normal, we're drawing a line
+ {
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " lineto\n", sal_Int64(pPath[i].X()), sal_Int64(pPath[i].Y()));
+ i++;
+ }
+ else //Otherwise we're drawing a spline
+ {
+ if (i+2 >= nPoints)
+ return; //Error: wrong sequence of control/normal points somehow
+ if ((pFlgAry[i] == PolyFlags::Control) && (pFlgAry[i+1] == PolyFlags::Control) &&
+ (pFlgAry[i+2] != PolyFlags::Control))
+ {
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " curveto\n",
+ sal_Int64(pPath[i].X()), sal_Int64(pPath[i].Y()),
+ sal_Int64(pPath[i+1].X()), sal_Int64(pPath[i+1].Y()),
+ sal_Int64(pPath[i+2].X()), sal_Int64(pPath[i+2].Y()));
+ }
+ else
+ {
+ OSL_FAIL( "PrinterGfx::DrawPolyLineBezier: Strange output" );
+ }
+ i+=3;
+ }
+ WritePS(mpPageBody, pString);
+ }
+
+ // now draw outlines
+ WritePS (mpPageBody, "stroke\n");
+}
+
+void
+PrinterGfx::DrawPolygonBezier (sal_uInt32 nPoints, const Point* pPath, const PolyFlags* pFlgAry)
+{
+ const sal_uInt32 nBezString = 1024;
+ char pString[nBezString];
+ // premature end of operation
+ if (nPoints <= 0 || (pPath == nullptr) || !(maFillColor.Is() || maLineColor.Is()))
+ return;
+
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " moveto\n", sal_Int64(pPath[0].X()), sal_Int64(pPath[0].Y()));
+ WritePS(mpPageBody, pString); //Move to the starting point for the PolyPolygon
+ for (unsigned int i=1; i < nPoints;)
+ {
+ if (pFlgAry[i] != PolyFlags::Control)
+ {
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " lineto\n",
+ sal_Int64(pPath[i].X()), sal_Int64(pPath[i].Y()));
+ WritePS(mpPageBody, pString);
+ i++;
+ }
+ else
+ {
+ if (i+2 >= nPoints)
+ return; //Error: wrong sequence of control/normal points somehow
+ if ((pFlgAry[i] == PolyFlags::Control) && (pFlgAry[i+1] == PolyFlags::Control) &&
+ (pFlgAry[i+2] != PolyFlags::Control))
+ {
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " curveto\n",
+ sal_Int64(pPath[i].X()), sal_Int64(pPath[i].Y()),
+ sal_Int64(pPath[i+1].X()), sal_Int64(pPath[i+1].Y()),
+ sal_Int64(pPath[i+2].X()), sal_Int64(pPath[i+2].Y()));
+ WritePS(mpPageBody, pString);
+ }
+ else
+ {
+ OSL_FAIL( "PrinterGfx::DrawPolygonBezier: Strange output" );
+ }
+ i+=3;
+ }
+ }
+
+ // if fill and stroke, save the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGSave();
+
+ if (maFillColor.Is ())
+ {
+ PSSetColor (maFillColor);
+ PSSetColor ();
+ WritePS (mpPageBody, "eofill\n");
+ }
+
+ // restore the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGRestore();
+}
+
+void
+PrinterGfx::DrawPolyPolygonBezier (sal_uInt32 nPoly, const sal_uInt32 * pPoints, const Point* const * pPtAry, const PolyFlags* const* pFlgAry)
+{
+ const sal_uInt32 nBezString = 1024;
+ char pString[nBezString];
+ if ( !nPoly || !pPtAry || !pPoints || !(maFillColor.Is() || maLineColor.Is()))
+ return;
+
+ for (unsigned int i=0; i<nPoly;i++)
+ {
+ sal_uInt32 nPoints = pPoints[i];
+ // sanity check
+ if( nPoints == 0 || pPtAry[i] == nullptr )
+ continue;
+
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " moveto\n",
+ sal_Int64(pPtAry[i][0].X()), sal_Int64(pPtAry[i][0].Y())); //Move to the starting point
+ WritePS(mpPageBody, pString);
+ for (unsigned int j=1; j < nPoints;)
+ {
+ // if no flag array exists for this polygon, then it must be a regular
+ // polygon without beziers
+ if ( ! pFlgAry[i] || pFlgAry[i][j] != PolyFlags::Control)
+ {
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " lineto\n",
+ sal_Int64(pPtAry[i][j].X()), sal_Int64(pPtAry[i][j].Y()));
+ WritePS(mpPageBody, pString);
+ j++;
+ }
+ else
+ {
+ if (j+2 >= nPoints)
+ break; //Error: wrong sequence of control/normal points somehow
+ if ((pFlgAry[i][j] == PolyFlags::Control) && (pFlgAry[i][j+1] == PolyFlags::Control) && (pFlgAry[i][j+2] != PolyFlags::Control))
+ {
+ snprintf(pString, nBezString, "%" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " %" SAL_PRIdINT64 " curveto\n",
+ sal_Int64(pPtAry[i][j].X()), sal_Int64(pPtAry[i][j].Y()),
+ sal_Int64(pPtAry[i][j+1].X()), sal_Int64(pPtAry[i][j+1].Y()),
+ sal_Int64(pPtAry[i][j+2].X()), sal_Int64(pPtAry[i][j+2].Y()));
+ WritePS(mpPageBody, pString);
+ }
+ else
+ {
+ OSL_FAIL( "PrinterGfx::DrawPolyPolygonBezier: Strange output" );
+ }
+ j+=3;
+ }
+ }
+ }
+
+ // if fill and stroke, save the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGSave();
+
+ if (maFillColor.Is ())
+ {
+ PSSetColor (maFillColor);
+ PSSetColor ();
+ WritePS (mpPageBody, "eofill\n");
+ }
+
+ // restore the current path
+ if( maFillColor.Is() && maLineColor.Is())
+ PSGRestore();
+}
+
+/*
+ * postscript generating routines
+ */
+void
+PrinterGfx::PSGSave ()
+{
+ WritePS (mpPageBody, "gsave\n" );
+ GraphicsStatus aNewState;
+ if( !maGraphicsStack.empty() )
+ aNewState = maGraphicsStack.front();
+ maGraphicsStack.push_front( aNewState );
+}
+
+void
+PrinterGfx::PSGRestore ()
+{
+ WritePS (mpPageBody, "grestore\n" );
+ if( maGraphicsStack.empty() )
+ WritePS (mpPageBody, "Error: too many grestores\n" );
+ else
+ maGraphicsStack.pop_front();
+}
+
+void
+PrinterGfx::PSSetLineWidth ()
+{
+ if( currentState().mfLineWidth != maVirtualStatus.mfLineWidth )
+ {
+ OStringBuffer pBuffer;
+
+ currentState().mfLineWidth = maVirtualStatus.mfLineWidth;
+ psp::getValueOfDouble (pBuffer, maVirtualStatus.mfLineWidth, 5);
+ psp::appendStr (" setlinewidth\n", pBuffer);
+ WritePS (mpPageBody, pBuffer.makeStringAndClear());
+ }
+}
+
+void
+PrinterGfx::PSSetColor ()
+{
+ PrinterColor& rColor( maVirtualStatus.maColor );
+
+ if( currentState().maColor == rColor )
+ return;
+
+ currentState().maColor = rColor;
+
+ OStringBuffer pBuffer;
+
+ if( mbColor )
+ {
+ psp::getValueOfDouble (pBuffer,
+ static_cast<double>(rColor.GetRed()) / 255.0, 5);
+ psp::appendStr (" ", pBuffer);
+ psp::getValueOfDouble (pBuffer,
+ static_cast<double>(rColor.GetGreen()) / 255.0, 5);
+ psp::appendStr (" ", pBuffer);
+ psp::getValueOfDouble (pBuffer,
+ static_cast<double>(rColor.GetBlue()) / 255.0, 5);
+ psp::appendStr (" setrgbcolor\n", pBuffer );
+ }
+ else
+ {
+ Color aColor( rColor.GetRed(), rColor.GetGreen(), rColor.GetBlue() );
+ sal_uInt8 nCol = aColor.GetLuminance();
+ psp::getValueOfDouble( pBuffer, static_cast<double>(nCol) / 255.0, 5 );
+ psp::appendStr( " setgray\n", pBuffer );
+ }
+
+ WritePS (mpPageBody, pBuffer.makeStringAndClear());
+}
+
+void
+PrinterGfx::PSSetFont ()
+{
+ GraphicsStatus& rCurrent( currentState() );
+ if( !(maVirtualStatus.maFont != rCurrent.maFont ||
+ maVirtualStatus.mnTextHeight != rCurrent.mnTextHeight ||
+ maVirtualStatus.maEncoding != rCurrent.maEncoding ||
+ maVirtualStatus.mnTextWidth != rCurrent.mnTextWidth ||
+ maVirtualStatus.mbArtBold != rCurrent.mbArtBold ||
+ maVirtualStatus.mbArtItalic != rCurrent.mbArtItalic)
+ )
+ return;
+
+ rCurrent.maFont = maVirtualStatus.maFont;
+ rCurrent.maEncoding = maVirtualStatus.maEncoding;
+ rCurrent.mnTextWidth = maVirtualStatus.mnTextWidth;
+ rCurrent.mnTextHeight = maVirtualStatus.mnTextHeight;
+ rCurrent.mbArtItalic = maVirtualStatus.mbArtItalic;
+ rCurrent.mbArtBold = maVirtualStatus.mbArtBold;
+
+ sal_Int32 nTextHeight = rCurrent.mnTextHeight;
+ sal_Int32 nTextWidth = rCurrent.mnTextWidth ? rCurrent.mnTextWidth
+ : rCurrent.mnTextHeight;
+
+ OStringBuffer pSetFont;
+
+ // postscript based fonts need reencoding
+ if ( ( rCurrent.maEncoding == RTL_TEXTENCODING_MS_1252)
+ || ( rCurrent.maEncoding == RTL_TEXTENCODING_ISO_8859_1)
+ || ( rCurrent.maEncoding >= RTL_TEXTENCODING_USER_START
+ && rCurrent.maEncoding <= RTL_TEXTENCODING_USER_END)
+ )
+ {
+ OString aReencodedFont =
+ psp::GlyphSet::GetReencodedFontName (rCurrent.maEncoding,
+ rCurrent.maFont);
+
+ psp::appendStr ("(", pSetFont);
+ psp::appendStr (aReencodedFont.getStr(),
+ pSetFont);
+ psp::appendStr (") cvn findfont ",
+ pSetFont);
+ }
+ else
+ // tt based fonts mustn't reencode, the encoding is implied by the fontname
+ // same for symbol type1 fonts, don't try to touch them
+ {
+ psp::appendStr ("(", pSetFont);
+ psp::appendStr (rCurrent.maFont.getStr(),
+ pSetFont);
+ psp::appendStr (") cvn findfont ",
+ pSetFont);
+ }
+
+ if( ! rCurrent.mbArtItalic )
+ {
+ psp::getValueOf (nTextWidth, pSetFont);
+ psp::appendStr (" ", pSetFont);
+ psp::getValueOf (-nTextHeight, pSetFont);
+ psp::appendStr (" matrix scale makefont setfont\n", pSetFont);
+ }
+ else // skew 15 degrees to right
+ {
+ psp::appendStr ( " [", pSetFont);
+ psp::getValueOf (nTextWidth, pSetFont);
+ psp::appendStr (" 0 ", pSetFont);
+ psp::getValueOfDouble (pSetFont, 0.27*static_cast<double>(nTextWidth), 3 );
+ psp::appendStr ( " ", pSetFont);
+ psp::getValueOf (-nTextHeight, pSetFont);
+
+ psp::appendStr (" 0 0] makefont setfont\n", pSetFont);
+ }
+
+ WritePS (mpPageBody, pSetFont.makeStringAndClear());
+
+}
+
+void
+PrinterGfx::PSRotate (Degree10 nAngle)
+{
+ sal_Int32 nPostScriptAngle = -nAngle.get();
+ while( nPostScriptAngle < 0 )
+ nPostScriptAngle += 3600;
+
+ if (nPostScriptAngle == 0)
+ return;
+
+ sal_Int32 nFullAngle = nPostScriptAngle / 10;
+ sal_Int32 nTenthAngle = nPostScriptAngle % 10;
+
+ OStringBuffer pRotate;
+
+ psp::getValueOf (nFullAngle, pRotate);
+ psp::appendStr (".", pRotate);
+ psp::getValueOf (nTenthAngle, pRotate);
+ psp::appendStr (" rotate\n", pRotate);
+
+ WritePS (mpPageBody, pRotate.makeStringAndClear());
+}
+
+void
+PrinterGfx::PSPointOp (const Point& rPoint, const char* pOperator)
+{
+ OStringBuffer pPSCommand;
+
+ psp::getValueOf (rPoint.X(), pPSCommand);
+ psp::appendStr (" ", pPSCommand);
+ psp::getValueOf (rPoint.Y(), pPSCommand);
+ psp::appendStr (" ", pPSCommand);
+ psp::appendStr (pOperator, pPSCommand);
+ psp::appendStr ("\n", pPSCommand);
+
+ WritePS (mpPageBody, pPSCommand.makeStringAndClear());
+}
+
+void
+PrinterGfx::PSTranslate (const Point& rPoint)
+{
+ PSPointOp (rPoint, "translate");
+}
+
+void
+PrinterGfx::PSMoveTo (const Point& rPoint)
+{
+ PSPointOp (rPoint, "moveto");
+}
+
+void
+PrinterGfx::PSLineTo (const Point& rPoint)
+{
+ PSPointOp (rPoint, "lineto");
+}
+
+/* get a compressed representation of the path information */
+
+#define DEBUG_BINPATH 0
+
+void
+PrinterGfx::PSBinLineTo (const Point& rCurrent, Point& rOld, sal_Int32& nColumn)
+{
+#if (DEBUG_BINPATH == 1)
+ PSLineTo (rCurrent);
+#else
+ PSBinPath (rCurrent, rOld, lineto, nColumn);
+#endif
+}
+
+void
+PrinterGfx::PSBinMoveTo (const Point& rCurrent, Point& rOld, sal_Int32& nColumn)
+{
+#if (DEBUG_BINPATH == 1)
+ PSMoveTo (rCurrent);
+#else
+ PSBinPath (rCurrent, rOld, moveto, nColumn);
+#endif
+}
+
+void
+PrinterGfx::PSBinStartPath ()
+{
+#if (DEBUG_BINPATH == 1)
+ WritePS (mpPageBody, "% PSBinStartPath\n");
+#else
+ WritePS (mpPageBody, "readpath\n" );
+#endif
+}
+
+void
+PrinterGfx::PSBinEndPath ()
+{
+#if (DEBUG_BINPATH == 1)
+ WritePS (mpPageBody, "% PSBinEndPath\n");
+#else
+ WritePS (mpPageBody, "~\n");
+#endif
+}
+
+void
+PrinterGfx::PSBinCurrentPath (sal_uInt32 nPoints, const Point* pPath)
+{
+ // create the path
+ Point aPoint (0, 0);
+ sal_Int32 nColumn = 0;
+
+ PSBinStartPath ();
+ PSBinMoveTo (*pPath, aPoint, nColumn);
+ for (unsigned int i = 1; i < nPoints; i++)
+ PSBinLineTo (pPath[i], aPoint, nColumn);
+ PSBinEndPath ();
+}
+
+void
+PrinterGfx::PSBinPath (const Point& rCurrent, Point& rOld,
+ pspath_t eType, sal_Int32& nColumn)
+{
+ OStringBuffer pPath;
+ sal_Int32 nChar;
+
+ // create the hex representation of the dx and dy path shift, store the field
+ // width as it is needed for the building the command
+ sal_Int32 nXPrec = getAlignedHexValueOf (rCurrent.X() - rOld.X(), pPath);
+ sal_Int32 nYPrec = getAlignedHexValueOf (rCurrent.Y() - rOld.Y(), pPath);
+
+ // build the command, it is a char with bit representation 000cxxyy
+ // c represents the char, xx and yy repr. the field width of the dx and dy shift,
+ // dx and dy represent the number of bytes to read after the opcode
+ char cCmd = (eType == lineto ? char(0x00) : char(0x10));
+ switch (nYPrec)
+ {
+ case 2: break;
+ case 4: cCmd |= 0x01; break;
+ case 6: cCmd |= 0x02; break;
+ case 8: cCmd |= 0x03; break;
+ default: OSL_FAIL("invalid x precision in binary path");
+ }
+ switch (nXPrec)
+ {
+ case 2: break;
+ case 4: cCmd |= 0x04; break;
+ case 6: cCmd |= 0x08; break;
+ case 8: cCmd |= 0x0c; break;
+ default: OSL_FAIL("invalid y precision in binary path");
+ }
+ cCmd += 'A';
+ pPath.insert(0, cCmd);
+ auto const path = pPath.makeStringAndClear();
+
+ // write the command to file,
+ // line breaking at column nMaxTextColumn (80)
+ nChar = 1 + nXPrec + nYPrec;
+ if ((nColumn + nChar) > nMaxTextColumn)
+ {
+ sal_Int32 nSegment = nMaxTextColumn - nColumn;
+
+ WritePS (mpPageBody, path.copy(0, nSegment));
+ WritePS (mpPageBody, "\n", 1);
+ WritePS (mpPageBody, path.copy(nSegment));
+
+ nColumn = nChar - nSegment;
+ }
+ else
+ {
+ WritePS (mpPageBody, path);
+
+ nColumn += nChar;
+ }
+
+ rOld = rCurrent;
+}
+
+void
+PrinterGfx::PSScale (double fScaleX, double fScaleY)
+{
+ OStringBuffer pScale;
+
+ psp::getValueOfDouble (pScale, fScaleX, 5);
+ psp::appendStr (" ", pScale);
+ psp::getValueOfDouble (pScale, fScaleY, 5);
+ psp::appendStr (" scale\n", pScale);
+
+ WritePS (mpPageBody, pScale.makeStringAndClear());
+}
+
+/* psshowtext helper routines: draw an hex string for show/xshow */
+void
+PrinterGfx::PSHexString (const unsigned char* pString, sal_Int16 nLen)
+{
+ OStringBuffer pHexString;
+ sal_Int32 nChar = psp::appendStr ("<", pHexString);
+ for (int i = 0; i < nLen; i++)
+ {
+ if (nChar >= (nMaxTextColumn - 1))
+ {
+ psp::appendStr ("\n", pHexString);
+ WritePS (mpPageBody, pHexString.makeStringAndClear());
+ nChar = 0;
+ }
+ nChar += psp::getHexValueOf (static_cast<sal_Int32>(pString[i]), pHexString);
+ }
+
+ psp::appendStr (">\n", pHexString);
+ WritePS (mpPageBody, pHexString.makeStringAndClear());
+}
+
+void
+PrinterGfx::PSShowGlyph (const unsigned char nGlyphId)
+{
+ PSSetColor (maTextColor);
+ PSSetColor ();
+ PSSetFont ();
+ // rotate the user coordinate system
+ if (mnTextAngle)
+ {
+ PSGSave ();
+ PSRotate (mnTextAngle);
+ }
+
+ char pBuffer[256];
+ if( maVirtualStatus.mbArtBold )
+ {
+ sal_Int32 nLW = maVirtualStatus.mnTextWidth;
+ if( nLW == 0 )
+ nLW = maVirtualStatus.mnTextHeight;
+ else
+ nLW = std::min(nLW, maVirtualStatus.mnTextHeight);
+ psp::getValueOfDouble( pBuffer, static_cast<double>(nLW) / 30.0 );
+ }
+
+ // dispatch to the drawing method
+ PSHexString (&nGlyphId, 1);
+
+ if( maVirtualStatus.mbArtBold )
+ {
+ WritePS( mpPageBody, pBuffer );
+ WritePS( mpPageBody, " bshow\n" );
+ }
+ else
+ WritePS (mpPageBody, "show\n");
+
+ // restore the user coordinate system
+ if (mnTextAngle)
+ PSGRestore ();
+}
+
+bool
+PrinterGfx::DrawEPS( const tools::Rectangle& rBoundingBox, void* pPtr, sal_uInt32 nSize )
+{
+ if( nSize == 0 )
+ return true;
+ if( ! mpPageBody )
+ return false;
+
+ bool bSuccess = false;
+
+ // first search the BoundingBox of the EPS data
+ SvMemoryStream aStream( pPtr, nSize, StreamMode::READ );
+ aStream.Seek( STREAM_SEEK_TO_BEGIN );
+ OString aLine;
+
+ OString aDocTitle;
+ double fLeft = 0, fRight = 0, fTop = 0, fBottom = 0;
+ bool bEndComments = false;
+ while( ! aStream.eof()
+ && ( ( fLeft == 0 && fRight == 0 && fTop == 0 && fBottom == 0 ) ||
+ ( aDocTitle.isEmpty() && !bEndComments ) )
+ )
+ {
+ aStream.ReadLine( aLine );
+ if( aLine.getLength() > 1 && aLine[0] == '%' )
+ {
+ char cChar = aLine[1];
+ if( cChar == '%' )
+ {
+ if( aLine.matchIgnoreAsciiCase( "%%BoundingBox:" ) )
+ {
+ aLine = WhitespaceToSpace( o3tl::getToken(aLine, 1, ':') );
+ if( !aLine.isEmpty() && aLine.indexOf( "atend" ) == -1 )
+ {
+ fLeft = StringToDouble( GetCommandLineToken( 0, aLine ) );
+ fBottom = StringToDouble( GetCommandLineToken( 1, aLine ) );
+ fRight = StringToDouble( GetCommandLineToken( 2, aLine ) );
+ fTop = StringToDouble( GetCommandLineToken( 3, aLine ) );
+ }
+ }
+ else if( aLine.matchIgnoreAsciiCase( "%%Title:" ) )
+ aDocTitle = WhitespaceToSpace( aLine.subView( 8 ) );
+ else if( aLine.matchIgnoreAsciiCase( "%%EndComments" ) )
+ bEndComments = true;
+ }
+ else if( cChar == ' ' || cChar == '\t' || cChar == '\r' || cChar == '\n' )
+ bEndComments = true;
+ }
+ else
+ bEndComments = true;
+ }
+
+ static sal_uInt16 nEps = 0;
+ if( aDocTitle.isEmpty() )
+ aDocTitle = OString::number(nEps++);
+
+ if( fLeft != fRight && fTop != fBottom )
+ {
+ double fScaleX = static_cast<double>(rBoundingBox.GetWidth())/(fRight-fLeft);
+ double fScaleY = -static_cast<double>(rBoundingBox.GetHeight())/(fTop-fBottom);
+ Point aTranslatePoint( static_cast<int>(rBoundingBox.Left()-fLeft*fScaleX),
+ static_cast<int>(rBoundingBox.Bottom()+1-fBottom*fScaleY) );
+ // prepare EPS
+ WritePS( mpPageBody,
+ "/b4_Inc_state save def\n"
+ "/dict_count countdictstack def\n"
+ "/op_count count 1 sub def\n"
+ "userdict begin\n"
+ "/showpage {} def\n"
+ "0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin\n"
+ "10 setmiterlimit [] 0 setdash newpath\n"
+ "/languagelevel where\n"
+ "{pop languagelevel\n"
+ "1 ne\n"
+ " {false setstrokeadjust false setoverprint\n"
+ " } if\n"
+ "}if\n" );
+ // set up clip path and scale
+ BeginSetClipRegion();
+ UnionClipRegion( rBoundingBox.Left(), rBoundingBox.Top(), rBoundingBox.GetWidth(), rBoundingBox.GetHeight() );
+ EndSetClipRegion();
+ PSTranslate( aTranslatePoint );
+ PSScale( fScaleX, fScaleY );
+
+ // DSC requires BeginDocument
+ WritePS( mpPageBody, "%%BeginDocument: " );
+ WritePS( mpPageBody, aDocTitle );
+ WritePS( mpPageBody, "\n" );
+
+ // write the EPS data
+ sal_uInt64 nOutLength;
+ mpPageBody->write( pPtr, nSize, nOutLength );
+ bSuccess = nOutLength == nSize;
+
+ // corresponding EndDocument
+ if( static_cast<char*>(pPtr)[ nSize-1 ] != '\n' )
+ WritePS( mpPageBody, "\n" );
+ WritePS( mpPageBody, "%%EndDocument\n" );
+
+ // clean up EPS
+ WritePS( mpPageBody,
+ "count op_count sub {pop} repeat\n"
+ "countdictstack dict_count sub {end} repeat\n"
+ "b4_Inc_state restore\n" );
+ }
+ return bSuccess;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/genprnpsp.cxx b/vcl/unx/generic/print/genprnpsp.cxx
new file mode 100644
index 000000000..b84ba0bef
--- /dev/null
+++ b/vcl/unx/generic/print/genprnpsp.cxx
@@ -0,0 +1,1298 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/**
+ this file implements the sal printer interface (SalPrinter, SalInfoPrinter
+ and some printer relevant methods of SalInstance and SalGraphicsData)
+
+ as underlying library the printer features of psprint are used.
+
+ The query methods of a SalInfoPrinter are implemented by querying psprint
+
+ The job methods of a SalPrinter are implemented by calling psprint
+ printer job functions.
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+// For spawning PDF and FAX generation
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <comphelper/fileurl.hxx>
+#include <o3tl/safeint.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/printer/Options.hxx>
+#include <vcl/print.hxx>
+#include <vcl/QueueInfo.hxx>
+#include <vcl/pdfwriter.hxx>
+#include <printerinfomanager.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/weld.hxx>
+#include <strings.hrc>
+#include <unx/genprn.h>
+#include <unx/geninst.h>
+#include <unx/genpspgraphics.h>
+
+#include <jobset.h>
+#include <print.h>
+#include "prtsetup.hxx"
+#include <salptype.hxx>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+
+using namespace psp;
+using namespace com::sun::star;
+
+static bool getPdfDir( const PrinterInfo& rInfo, OUString &rDir )
+{
+ sal_Int32 nIndex = 0;
+ while( nIndex != -1 )
+ {
+ OUString aToken( rInfo.m_aFeatures.getToken( 0, ',', nIndex ) );
+ if( aToken.startsWith( "pdf=" ) )
+ {
+ sal_Int32 nPos = 0;
+ rDir = aToken.getToken( 1, '=', nPos );
+ if( rDir.isEmpty() && getenv( "HOME" ) )
+ rDir = OUString( getenv( "HOME" ), strlen( getenv( "HOME" ) ), osl_getThreadTextEncoding() );
+ return true;
+ }
+ }
+ return false;
+}
+
+namespace
+{
+ class QueryString : public weld::GenericDialogController
+ {
+ private:
+ OUString& m_rReturnValue;
+
+ std::unique_ptr<weld::Button> m_xOKButton;
+ std::unique_ptr<weld::Label> m_xFixedText;
+ std::unique_ptr<weld::Entry> m_xEdit;
+
+ DECL_LINK( ClickBtnHdl, weld::Button&, void );
+
+ public:
+ // parent window, Query text, initial value
+ QueryString(weld::Window*, OUString const &, OUString &);
+ };
+
+ /*
+ * QueryString
+ */
+ QueryString::QueryString(weld::Window* pParent, OUString const & rQuery, OUString& rRet)
+ : GenericDialogController(pParent, "vcl/ui/querydialog.ui", "QueryDialog")
+ , m_rReturnValue( rRet )
+ , m_xOKButton(m_xBuilder->weld_button("ok"))
+ , m_xFixedText(m_xBuilder->weld_label("label"))
+ , m_xEdit(m_xBuilder->weld_entry("entry"))
+ {
+ m_xOKButton->connect_clicked(LINK(this, QueryString, ClickBtnHdl));
+ m_xFixedText->set_label(rQuery);
+ m_xEdit->set_text(m_rReturnValue);
+ m_xDialog->set_title(rQuery);
+ }
+
+ IMPL_LINK(QueryString, ClickBtnHdl, weld::Button&, rButton, void)
+ {
+ if (&rButton == m_xOKButton.get())
+ {
+ m_rReturnValue = m_xEdit->get_text();
+ m_xDialog->response(RET_OK);
+ }
+ else
+ m_xDialog->response(RET_CANCEL);
+ }
+
+ int QueryFaxNumber(OUString& rNumber)
+ {
+ QueryString aQuery(Application::GetDefDialogParent(), VclResId(SV_PRINT_QUERYFAXNUMBER_TXT), rNumber);
+ return aQuery.run();
+ }
+}
+
+static int PtTo10Mu( int nPoints ) { return static_cast<int>((static_cast<double>(nPoints)*35.27777778)+0.5); }
+
+static int TenMuToPt( int nUnits ) { return static_cast<int>((static_cast<double>(nUnits)/35.27777778)+0.5); }
+
+static void copyJobDataToJobSetup( ImplJobSetup* pJobSetup, JobData& rData )
+{
+ pJobSetup->SetOrientation( rData.m_eOrientation == orientation::Landscape ?
+ Orientation::Landscape : Orientation::Portrait );
+
+ // copy page size
+ OUString aPaper;
+ int width, height;
+
+ rData.m_aContext.getPageSize( aPaper, width, height );
+ pJobSetup->SetPaperFormat( PaperInfo::fromPSName(
+ OUStringToOString( aPaper, RTL_TEXTENCODING_ISO_8859_1 )));
+
+ pJobSetup->SetPaperWidth( 0 );
+ pJobSetup->SetPaperHeight( 0 );
+ if( pJobSetup->GetPaperFormat() == PAPER_USER )
+ {
+ // transform to 100dth mm
+ width = PtTo10Mu( width );
+ height = PtTo10Mu( height );
+
+ if( rData.m_eOrientation == psp::orientation::Portrait )
+ {
+ pJobSetup->SetPaperWidth( width );
+ pJobSetup->SetPaperHeight( height );
+ }
+ else
+ {
+ pJobSetup->SetPaperWidth( height );
+ pJobSetup->SetPaperHeight( width );
+ }
+ }
+
+ // copy input slot
+ const PPDKey* pKey = nullptr;
+ const PPDValue* pValue = nullptr;
+
+ pJobSetup->SetPaperBin( 0 );
+ if( rData.m_pParser )
+ pKey = rData.m_pParser->getKey( "InputSlot" );
+ if( pKey )
+ pValue = rData.m_aContext.getValue( pKey );
+ if( pKey && pValue )
+ {
+ int nPaperBin;
+ for( nPaperBin = 0;
+ pValue != pKey->getValue( nPaperBin ) &&
+ nPaperBin < pKey->countValues();
+ nPaperBin++);
+ pJobSetup->SetPaperBin(
+ nPaperBin == pKey->countValues() ? 0 : nPaperBin);
+ }
+
+ // copy duplex
+ pKey = nullptr;
+ pValue = nullptr;
+
+ pJobSetup->SetDuplexMode( DuplexMode::Unknown );
+ if( rData.m_pParser )
+ pKey = rData.m_pParser->getKey( "Duplex" );
+ if( pKey )
+ pValue = rData.m_aContext.getValue( pKey );
+ if( pKey && pValue )
+ {
+ if( pValue->m_aOption.equalsIgnoreAsciiCase( "None" ) ||
+ pValue->m_aOption.startsWithIgnoreAsciiCase( "Simplex" )
+ )
+ {
+ pJobSetup->SetDuplexMode( DuplexMode::Off);
+ }
+ else if( pValue->m_aOption.equalsIgnoreAsciiCase( "DuplexNoTumble" ) )
+ {
+ pJobSetup->SetDuplexMode( DuplexMode::LongEdge );
+ }
+ else if( pValue->m_aOption.equalsIgnoreAsciiCase( "DuplexTumble" ) )
+ {
+ pJobSetup->SetDuplexMode( DuplexMode::ShortEdge );
+ }
+ }
+
+ // copy the whole context
+ if( pJobSetup->GetDriverData() )
+ std::free( const_cast<sal_uInt8*>(pJobSetup->GetDriverData()) );
+
+ sal_uInt32 nBytes;
+ void* pBuffer = nullptr;
+ if( rData.getStreamBuffer( pBuffer, nBytes ) )
+ {
+ pJobSetup->SetDriverDataLen( nBytes );
+ pJobSetup->SetDriverData( static_cast<sal_uInt8*>(pBuffer) );
+ }
+ else
+ {
+ pJobSetup->SetDriverDataLen( 0 );
+ pJobSetup->SetDriverData( nullptr );
+ }
+ pJobSetup->SetPapersizeFromSetup( rData.m_bPapersizeFromSetup );
+}
+
+// Needs a cleaner abstraction ...
+static bool passFileToCommandLine( const OUString& rFilename, const OUString& rCommandLine )
+{
+ bool bSuccess = false;
+
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OString aCmdLine(OUStringToOString(rCommandLine, aEncoding));
+ OString aFilename(OUStringToOString(rFilename, aEncoding));
+
+ bool bPipe = aCmdLine.indexOf( "(TMP)" ) == -1;
+
+ // setup command line for exec
+ if( ! bPipe )
+ aCmdLine = aCmdLine.replaceAll("(TMP)", aFilename);
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", (bPipe ? "piping to" : "executing")
+ << " commandline: \"" << aCmdLine << "\".");
+ struct stat aStat;
+ SAL_WARN_IF(stat( aFilename.getStr(), &aStat ),
+ "vcl.unx.print", "stat( " << aFilename << " ) failed.");
+ SAL_INFO("vcl.unx.print", "Tmp file " << aFilename
+ << " has modes: "
+ << std::showbase << std::oct
+ << (long)aStat.st_mode);
+#endif
+ const char* argv[4];
+ if( ! ( argv[ 0 ] = getenv( "SHELL" ) ) )
+ argv[ 0 ] = "/bin/sh";
+ argv[ 1 ] = "-c";
+ argv[ 2 ] = aCmdLine.getStr();
+ argv[ 3 ] = nullptr;
+
+ bool bHavePipes = false;
+ int pid, fd[2];
+
+ if( bPipe )
+ bHavePipes = pipe( fd ) == 0;
+ if( ( pid = fork() ) > 0 )
+ {
+ if( bPipe && bHavePipes )
+ {
+ close( fd[0] );
+ char aBuffer[ 2048 ];
+ FILE* fp = fopen( aFilename.getStr(), "r" );
+ while (fp && !feof(fp))
+ {
+ size_t nBytesRead = fread(aBuffer, 1, sizeof( aBuffer ), fp);
+ if (nBytesRead )
+ {
+ size_t nBytesWritten = write(fd[1], aBuffer, nBytesRead);
+ OSL_ENSURE(nBytesWritten == nBytesRead, "short write");
+ if (nBytesWritten != nBytesRead)
+ break;
+ }
+ }
+ fclose( fp );
+ close( fd[ 1 ] );
+ }
+ int status = 0;
+ if(waitpid( pid, &status, 0 ) != -1)
+ {
+ if( ! status )
+ bSuccess = true;
+ }
+ }
+ else if( ! pid )
+ {
+ if( bPipe && bHavePipes )
+ {
+ close( fd[1] );
+ if( fd[0] != STDIN_FILENO ) // not probable, but who knows :)
+ dup2( fd[0], STDIN_FILENO );
+ }
+ execv( argv[0], const_cast<char**>(argv) );
+ fprintf( stderr, "failed to execute \"%s\"\n", aCmdLine.getStr() );
+ _exit( 1 );
+ }
+ else
+ fprintf( stderr, "failed to fork\n" );
+
+ // clean up the mess
+ unlink( aFilename.getStr() );
+
+ return bSuccess;
+}
+
+static std::vector<OUString> getFaxNumbers()
+{
+ std::vector<OUString> aFaxNumbers;
+
+ OUString aNewNr;
+ if (QueryFaxNumber(aNewNr))
+ {
+ for (sal_Int32 nIndex {0}; nIndex >= 0; )
+ aFaxNumbers.push_back(aNewNr.getToken( 0, ';', nIndex ));
+ }
+
+ return aFaxNumbers;
+}
+
+static bool createPdf( std::u16string_view rToFile, const OUString& rFromFile, const OUString& rCommandLine )
+{
+ return passFileToCommandLine( rFromFile, rCommandLine.replaceAll("(OUTFILE)", rToFile) );
+}
+
+/*
+ * SalInstance
+ */
+
+void SalGenericInstance::configurePspInfoPrinter(PspSalInfoPrinter *pPrinter,
+ SalPrinterQueueInfo const * pQueueInfo, ImplJobSetup* pJobSetup)
+{
+ if( !pJobSetup )
+ return;
+
+ PrinterInfoManager& rManager( PrinterInfoManager::get() );
+ PrinterInfo aInfo( rManager.getPrinterInfo( pQueueInfo->maPrinterName ) );
+ pPrinter->m_aJobData = aInfo;
+ pPrinter->m_aPrinterGfx.Init( pPrinter->m_aJobData );
+
+ if( pJobSetup->GetDriverData() )
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(),
+ pJobSetup->GetDriverDataLen(), aInfo );
+
+ pJobSetup->SetSystem( JOBSETUP_SYSTEM_UNIX );
+ pJobSetup->SetPrinterName( pQueueInfo->maPrinterName );
+ pJobSetup->SetDriver( aInfo.m_aDriverName );
+ copyJobDataToJobSetup( pJobSetup, aInfo );
+}
+
+SalInfoPrinter* SalGenericInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pJobSetup )
+{
+ mbPrinterInit = true;
+ // create and initialize SalInfoPrinter
+ PspSalInfoPrinter* pPrinter = new PspSalInfoPrinter();
+ configurePspInfoPrinter(pPrinter, pQueueInfo, pJobSetup);
+ return pPrinter;
+}
+
+void SalGenericInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter )
+{
+ delete pPrinter;
+}
+
+std::unique_ptr<SalPrinter> SalGenericInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
+{
+ mbPrinterInit = true;
+ // create and initialize SalPrinter
+ PspSalPrinter* pPrinter = new PspSalPrinter( pInfoPrinter );
+ pPrinter->m_aJobData = static_cast<PspSalInfoPrinter*>(pInfoPrinter)->m_aJobData;
+
+ return std::unique_ptr<SalPrinter>(pPrinter);
+}
+
+void SalGenericInstance::GetPrinterQueueInfo( ImplPrnQueueList* pList )
+{
+ mbPrinterInit = true;
+ PrinterInfoManager& rManager( PrinterInfoManager::get() );
+ static const char* pNoSyncDetection = getenv( "SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION" );
+ if( ! pNoSyncDetection || ! *pNoSyncDetection )
+ {
+ // #i62663# synchronize possible asynchronouse printer detection now
+ rManager.checkPrintersChanged( true );
+ }
+ ::std::vector< OUString > aPrinters;
+ rManager.listPrinters( aPrinters );
+
+ for (auto const& printer : aPrinters)
+ {
+ const PrinterInfo& rInfo( rManager.getPrinterInfo(printer) );
+ // create new entry
+ std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo);
+ pInfo->maPrinterName = printer;
+ pInfo->maDriver = rInfo.m_aDriverName;
+ pInfo->maLocation = rInfo.m_aLocation;
+ pInfo->maComment = rInfo.m_aComment;
+
+ OUString sPdfDir;
+ if (getPdfDir(rInfo, sPdfDir))
+ pInfo->maLocation = sPdfDir;
+
+ pList->Add( std::move(pInfo) );
+ }
+}
+
+void SalGenericInstance::GetPrinterQueueState( SalPrinterQueueInfo* )
+{
+ mbPrinterInit = true;
+}
+
+OUString SalGenericInstance::GetDefaultPrinter()
+{
+ mbPrinterInit = true;
+ PrinterInfoManager& rManager( PrinterInfoManager::get() );
+ return rManager.getDefaultPrinter();
+}
+
+PspSalInfoPrinter::PspSalInfoPrinter()
+{
+}
+
+PspSalInfoPrinter::~PspSalInfoPrinter()
+{
+}
+
+void PspSalInfoPrinter::InitPaperFormats( const ImplJobSetup* )
+{
+ m_aPaperFormats.clear();
+ m_bPapersInit = true;
+
+ if( !m_aJobData.m_pParser )
+ return;
+
+ const PPDKey* pKey = m_aJobData.m_pParser->getKey( "PageSize" );
+ if( pKey )
+ {
+ int nValues = pKey->countValues();
+ for( int i = 0; i < nValues; i++ )
+ {
+ const PPDValue* pValue = pKey->getValue( i );
+ int nWidth = 0, nHeight = 0;
+ m_aJobData.m_pParser->getPaperDimension( pValue->m_aOption, nWidth, nHeight );
+ PaperInfo aInfo(PtTo10Mu( nWidth ), PtTo10Mu( nHeight ));
+ m_aPaperFormats.push_back( aInfo );
+ }
+ }
+}
+
+int PspSalInfoPrinter::GetLandscapeAngle( const ImplJobSetup* )
+{
+ return 900;
+}
+
+SalGraphics* PspSalInfoPrinter::AcquireGraphics()
+{
+ // return a valid pointer only once
+ // the reasoning behind this is that we could have different
+ // SalGraphics that can run in multiple threads
+ // (future plans)
+ SalGraphics* pRet = nullptr;
+ if( ! m_pGraphics )
+ {
+ m_pGraphics = GetGenericInstance()->CreatePrintGraphics();
+ m_pGraphics->Init(&m_aJobData, &m_aPrinterGfx);
+ pRet = m_pGraphics.get();
+ }
+ return pRet;
+}
+
+void PspSalInfoPrinter::ReleaseGraphics( SalGraphics* pGraphics )
+{
+ if( m_pGraphics.get() == pGraphics )
+ {
+ m_pGraphics.reset();
+ }
+}
+
+bool PspSalInfoPrinter::Setup( weld::Window* pFrame, ImplJobSetup* pJobSetup )
+{
+ if( ! pFrame || ! pJobSetup )
+ return false;
+
+ PrinterInfoManager& rManager = PrinterInfoManager::get();
+
+ PrinterInfo aInfo( rManager.getPrinterInfo( pJobSetup->GetPrinterName() ) );
+ if ( pJobSetup->GetDriverData() )
+ {
+ SetData( JobSetFlags::ALL, pJobSetup );
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aInfo );
+ }
+ aInfo.m_bPapersizeFromSetup = pJobSetup->GetPapersizeFromSetup();
+ aInfo.meSetupMode = pJobSetup->GetPrinterSetupMode();
+
+ if (SetupPrinterDriver(pFrame, aInfo))
+ {
+ aInfo.resolveDefaultBackend();
+ std::free( const_cast<sal_uInt8*>(pJobSetup->GetDriverData()) );
+ pJobSetup->SetDriverData( nullptr );
+
+ sal_uInt32 nBytes;
+ void* pBuffer = nullptr;
+ aInfo.getStreamBuffer( pBuffer, nBytes );
+ pJobSetup->SetDriverDataLen( nBytes );
+ pJobSetup->SetDriverData( static_cast<sal_uInt8*>(pBuffer) );
+
+ // copy everything to job setup
+ copyJobDataToJobSetup( pJobSetup, aInfo );
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), m_aJobData );
+ return true;
+ }
+ return false;
+}
+
+// This function gets the driver data and puts it into pJobSetup
+// If pJobSetup->GetDriverData() is NOT NULL, then the independent
+// data should be merged into the driver data
+// If pJobSetup->GetDriverData() IS NULL, then the driver defaults
+// should be merged into the independent data
+bool PspSalInfoPrinter::SetPrinterData( ImplJobSetup* pJobSetup )
+{
+ if( pJobSetup->GetDriverData() )
+ return SetData( JobSetFlags::ALL, pJobSetup );
+
+ copyJobDataToJobSetup( pJobSetup, m_aJobData );
+
+ return true;
+}
+
+// This function merges the independent driver data
+// and sets the new independent data in pJobSetup
+// Only the data must be changed, where the bit
+// in nGetDataFlags is set
+bool PspSalInfoPrinter::SetData(
+ JobSetFlags nSetDataFlags,
+ ImplJobSetup* pJobSetup )
+{
+ JobData aData;
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+
+ if( aData.m_pParser )
+ {
+ const PPDKey* pKey;
+ const PPDValue* pValue;
+
+ // merge papersize if necessary
+ if( nSetDataFlags & JobSetFlags::PAPERSIZE )
+ {
+ OUString aPaper;
+
+ if( pJobSetup->GetPaperFormat() == PAPER_USER )
+ aPaper = aData.m_pParser->matchPaper(
+ TenMuToPt( pJobSetup->GetPaperWidth() ),
+ TenMuToPt( pJobSetup->GetPaperHeight() ) );
+ else
+ aPaper = OStringToOUString(PaperInfo::toPSName(pJobSetup->GetPaperFormat()), RTL_TEXTENCODING_ISO_8859_1);
+
+ pKey = aData.m_pParser->getKey( "PageSize" );
+ pValue = pKey ? pKey->getValueCaseInsensitive( aPaper ) : nullptr;
+
+ // some PPD files do not specify the standard paper names (e.g. C5 instead of EnvC5)
+ // try to find the correct paper anyway using the size
+ if( pKey && ! pValue && pJobSetup->GetPaperFormat() != PAPER_USER )
+ {
+ PaperInfo aInfo( pJobSetup->GetPaperFormat() );
+ aPaper = aData.m_pParser->matchPaper(
+ TenMuToPt( aInfo.getWidth() ),
+ TenMuToPt( aInfo.getHeight() ) );
+ pValue = pKey->getValueCaseInsensitive( aPaper );
+ }
+
+ if( ! ( pKey && pValue && aData.m_aContext.setValue( pKey, pValue ) == pValue ) )
+ return false;
+ }
+
+ // merge paperbin if necessary
+ if( nSetDataFlags & JobSetFlags::PAPERBIN )
+ {
+ pKey = aData.m_pParser->getKey( "InputSlot" );
+ if( pKey )
+ {
+ int nPaperBin = pJobSetup->GetPaperBin();
+ if( nPaperBin >= pKey->countValues() )
+ pValue = pKey->getDefaultValue();
+ else
+ pValue = pKey->getValue( pJobSetup->GetPaperBin() );
+
+ // may fail due to constraints;
+ // real paper bin is copied back to jobsetup in that case
+ aData.m_aContext.setValue( pKey, pValue );
+ }
+ // if printer has no InputSlot key simply ignore this setting
+ // (e.g. SGENPRT has no InputSlot)
+ }
+
+ // merge orientation if necessary
+ if( nSetDataFlags & JobSetFlags::ORIENTATION )
+ aData.m_eOrientation = pJobSetup->GetOrientation() == Orientation::Landscape ? orientation::Landscape : orientation::Portrait;
+
+ // merge duplex if necessary
+ if( nSetDataFlags & JobSetFlags::DUPLEXMODE )
+ {
+ pKey = aData.m_pParser->getKey( "Duplex" );
+ if( pKey )
+ {
+ pValue = nullptr;
+ switch( pJobSetup->GetDuplexMode() )
+ {
+ case DuplexMode::Off:
+ pValue = pKey->getValue( "None" );
+ if( pValue == nullptr )
+ pValue = pKey->getValue( "SimplexNoTumble" );
+ break;
+ case DuplexMode::ShortEdge:
+ pValue = pKey->getValue( "DuplexTumble" );
+ break;
+ case DuplexMode::LongEdge:
+ pValue = pKey->getValue( "DuplexNoTumble" );
+ break;
+ case DuplexMode::Unknown:
+ default:
+ pValue = nullptr;
+ break;
+ }
+ if( ! pValue )
+ pValue = pKey->getDefaultValue();
+ aData.m_aContext.setValue( pKey, pValue );
+ }
+ }
+ aData.m_bPapersizeFromSetup = pJobSetup->GetPapersizeFromSetup();
+
+ m_aJobData = aData;
+ copyJobDataToJobSetup( pJobSetup, aData );
+ return true;
+ }
+
+ return false;
+}
+
+void PspSalInfoPrinter::GetPageInfo(
+ const ImplJobSetup* pJobSetup,
+ tools::Long& rOutWidth, tools::Long& rOutHeight,
+ Point& rPageOffset,
+ Size& rPaperSize )
+{
+ if( ! pJobSetup )
+ return;
+
+ JobData aData;
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+
+ // get the selected page size
+ if( !aData.m_pParser )
+ return;
+
+
+ OUString aPaper;
+ int width, height;
+ int left = 0, top = 0, right = 0, bottom = 0;
+ int nDPI = aData.m_aContext.getRenderResolution();
+
+ if( aData.m_eOrientation == psp::orientation::Portrait )
+ {
+ aData.m_aContext.getPageSize( aPaper, width, height );
+ aData.m_pParser->getMargins( aPaper, left, right, top, bottom );
+ }
+ else
+ {
+ aData.m_aContext.getPageSize( aPaper, height, width );
+ aData.m_pParser->getMargins( aPaper, top, bottom, right, left );
+ }
+
+ rPaperSize.setWidth( width * nDPI / 72 );
+ rPaperSize.setHeight( height * nDPI / 72 );
+ rPageOffset.setX( left * nDPI / 72 );
+ rPageOffset.setY( top * nDPI / 72 );
+ rOutWidth = ( width - left - right ) * nDPI / 72;
+ rOutHeight = ( height - top - bottom ) * nDPI / 72;
+
+}
+
+sal_uInt16 PspSalInfoPrinter::GetPaperBinCount( const ImplJobSetup* pJobSetup )
+{
+ if( ! pJobSetup )
+ return 0;
+
+ JobData aData;
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+
+ const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey( "InputSlot" ): nullptr;
+ return pKey ? pKey->countValues() : 0;
+}
+
+OUString PspSalInfoPrinter::GetPaperBinName( const ImplJobSetup* pJobSetup, sal_uInt16 nPaperBin )
+{
+ JobData aData;
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+
+ if( aData.m_pParser )
+ {
+ const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey( "InputSlot" ): nullptr;
+ if( ! pKey || nPaperBin >= o3tl::make_unsigned(pKey->countValues()) )
+ return aData.m_pParser->getDefaultInputSlot();
+ const PPDValue* pValue = pKey->getValue( nPaperBin );
+ if( pValue )
+ return aData.m_pParser->translateOption( pKey->getKey(), pValue->m_aOption );
+ }
+
+ return OUString();
+}
+
+sal_uInt32 PspSalInfoPrinter::GetCapabilities( const ImplJobSetup* pJobSetup, PrinterCapType nType )
+{
+ switch( nType )
+ {
+ case PrinterCapType::SupportDialog:
+ return 1;
+ case PrinterCapType::Copies:
+ return 0xffff;
+ case PrinterCapType::CollateCopies:
+ {
+ // PPDs don't mention the number of possible collated copies.
+ // so let's guess as many as we want ?
+ return 0xffff;
+ }
+ case PrinterCapType::SetOrientation:
+ return 1;
+ case PrinterCapType::SetPaperSize:
+ return 1;
+ case PrinterCapType::SetPaper:
+ return 0;
+ case PrinterCapType::Fax:
+ {
+ // see if the PPD contains the fax4CUPS "Dial" option and that it's not set
+ // to "manually"
+ JobData aData = PrinterInfoManager::get().getPrinterInfo(pJobSetup->GetPrinterName());
+ if( pJobSetup->GetDriverData() )
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+ const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey("Dial") : nullptr;
+ const PPDValue* pValue = pKey ? aData.m_aContext.getValue(pKey) : nullptr;
+ if (pValue && !pValue->m_aOption.equalsIgnoreAsciiCase("Manually"))
+ return 1;
+ return 0;
+ }
+
+ case PrinterCapType::PDF:
+ if( PrinterInfoManager::get().checkFeatureToken( pJobSetup->GetPrinterName(), "pdf" ) )
+ return 1;
+ else
+ {
+ // see if the PPD contains a value to set PDF device
+ JobData aData = PrinterInfoManager::get().getPrinterInfo( pJobSetup->GetPrinterName() );
+ if( pJobSetup->GetDriverData() )
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+ return aData.m_nPDFDevice > 0 ? 1 : 0;
+ }
+ case PrinterCapType::ExternalDialog:
+ return PrinterInfoManager::get().checkFeatureToken( pJobSetup->GetPrinterName(), "external_dialog" ) ? 1 : 0;
+ case PrinterCapType::UsePullModel:
+ {
+ // see if the PPD contains a value to set PDF device
+ JobData aData = PrinterInfoManager::get().getPrinterInfo( pJobSetup->GetPrinterName() );
+ if( pJobSetup->GetDriverData() )
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
+ return aData.m_nPDFDevice > 0 ? 1 : 0;
+ }
+ default: break;
+ }
+ return 0;
+}
+
+/*
+ * SalPrinter
+ */
+PspSalPrinter::PspSalPrinter( SalInfoPrinter* pInfoPrinter )
+ : m_pInfoPrinter( pInfoPrinter )
+ , m_nCopies( 1 )
+ , m_bCollate( false )
+ , m_bPdf( false )
+ , m_bIsPDFWriterJob( false )
+{
+}
+
+PspSalPrinter::~PspSalPrinter()
+{
+}
+
+static OUString getTmpName()
+{
+ OUString aTmp, aSys;
+ osl_createTempFile( nullptr, nullptr, &aTmp.pData );
+ osl_getSystemPathFromFileURL( aTmp.pData, &aSys.pData );
+
+ return aSys;
+}
+
+bool PspSalPrinter::StartJob(
+ const OUString* pFileName,
+ const OUString& rJobName,
+ const OUString& rAppName,
+ sal_uInt32 nCopies,
+ bool bCollate,
+ bool bDirect,
+ ImplJobSetup* pJobSetup )
+{
+ SAL_INFO( "vcl.unx.print", "PspSalPrinter::StartJob");
+ GetSalInstance()->jobStartedPrinterUpdate();
+ m_bPdf = false;
+ if (pFileName)
+ m_aFileName = *pFileName;
+ else
+ m_aFileName.clear();
+ m_aTmpFile.clear();
+ m_nCopies = nCopies;
+ m_bCollate = bCollate;
+
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), m_aJobData );
+ if( m_nCopies > 1 )
+ {
+ // in case user did not do anything (m_nCopies=1)
+ // take the default from jobsetup
+ m_aJobData.m_nCopies = m_nCopies;
+ m_aJobData.setCollate( bCollate );
+ }
+
+ int nMode = 0;
+ // check whether this printer is configured as fax
+ const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( m_aJobData.m_aPrinterName ) );
+ OUString sPdfDir;
+ if (getPdfDir(rInfo, sPdfDir))
+ {
+ m_bPdf = true;
+ m_aTmpFile = getTmpName();
+ nMode = S_IRUSR | S_IWUSR;
+
+ if( m_aFileName.isEmpty() )
+ m_aFileName = sPdfDir + "/" + rJobName + ".pdf";
+ }
+ m_aPrinterGfx.Init( m_aJobData );
+
+ return m_aPrintJob.StartJob( ! m_aTmpFile.isEmpty() ? m_aTmpFile : m_aFileName, nMode, rJobName, rAppName, m_aJobData, &m_aPrinterGfx, bDirect );
+}
+
+bool PspSalPrinter::EndJob()
+{
+ bool bSuccess = false;
+ if( m_bIsPDFWriterJob )
+ bSuccess = true;
+ else
+ {
+ bSuccess = m_aPrintJob.EndJob();
+ SAL_INFO( "vcl.unx.print", "PspSalPrinter::EndJob " << bSuccess);
+
+ if( bSuccess && m_bPdf )
+ {
+ const PrinterInfo& rInfo( PrinterInfoManager::get().getPrinterInfo( m_aJobData.m_aPrinterName ) );
+ bSuccess = createPdf( m_aFileName, m_aTmpFile, rInfo.m_aCommand );
+ }
+ }
+ GetSalInstance()->jobEndedPrinterUpdate();
+ return bSuccess;
+}
+
+SalGraphics* PspSalPrinter::StartPage( ImplJobSetup* pJobSetup, bool )
+{
+ SAL_INFO( "vcl.unx.print", "PspSalPrinter::StartPage");
+
+ JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), m_aJobData );
+ m_xGraphics = GetGenericInstance()->CreatePrintGraphics();
+ m_xGraphics->Init(&m_aJobData, &m_aPrinterGfx);
+
+ if( m_nCopies > 1 )
+ {
+ // in case user did not do anything (m_nCopies=1)
+ // take the default from jobsetup
+ m_aJobData.m_nCopies = m_nCopies;
+ m_aJobData.setCollate( m_nCopies > 1 && m_bCollate );
+ }
+
+ m_aPrintJob.StartPage( m_aJobData );
+ m_aPrinterGfx.Init( m_aPrintJob );
+
+ return m_xGraphics.get();
+}
+
+void PspSalPrinter::EndPage()
+{
+ m_aPrintJob.EndPage();
+ m_aPrinterGfx.Clear();
+ SAL_INFO( "vcl.unx.print", "PspSalPrinter::EndPage");
+}
+
+namespace {
+
+struct PDFNewJobParameters
+{
+ Size maPageSize;
+ sal_uInt16 mnPaperBin;
+
+ PDFNewJobParameters( const Size& i_rSize = Size(),
+ sal_uInt16 i_nPaperBin = 0xffff )
+ : maPageSize( i_rSize ), mnPaperBin( i_nPaperBin ) {}
+
+ bool operator==(const PDFNewJobParameters& rComp ) const
+ {
+ const tools::Long nRotatedWidth = rComp.maPageSize.Height();
+ const tools::Long nRotatedHeight = rComp.maPageSize.Width();
+ Size aCompLSSize(nRotatedWidth, nRotatedHeight);
+ return
+ (maPageSize == rComp.maPageSize || maPageSize == aCompLSSize)
+ && mnPaperBin == rComp.mnPaperBin
+ ;
+ }
+
+ bool operator!=(const PDFNewJobParameters& rComp) const
+ {
+ return ! operator==(rComp);
+ }
+};
+
+struct PDFPrintFile
+{
+ OUString maTmpURL;
+ PDFNewJobParameters maParameters;
+
+ PDFPrintFile( const OUString& i_rURL, const PDFNewJobParameters& i_rNewParameters )
+ : maTmpURL( i_rURL )
+ , maParameters( i_rNewParameters ) {}
+};
+
+}
+
+bool PspSalPrinter::StartJob( const OUString* i_pFileName, const OUString& i_rJobName, const OUString& i_rAppName,
+ ImplJobSetup* i_pSetupData, vcl::PrinterController& i_rController )
+{
+ SAL_INFO( "vcl.unx.print", "StartJob with controller: pFilename = " << (i_pFileName ? *i_pFileName : "<nil>") );
+ // mark for endjob
+ m_bIsPDFWriterJob = true;
+ // reset IsLastPage
+ i_rController.setLastPage( false );
+ // is this a fax device
+ bool bFax = m_pInfoPrinter->GetCapabilities(i_pSetupData, PrinterCapType::Fax) == 1;
+
+ // update job data
+ if( i_pSetupData )
+ JobData::constructFromStreamBuffer( i_pSetupData->GetDriverData(), i_pSetupData->GetDriverDataLen(), m_aJobData );
+
+ OSL_ASSERT( m_aJobData.m_nPDFDevice > 0 );
+ m_aJobData.m_nPDFDevice = 1;
+
+ // possibly create one job for collated output
+ int nCopies = i_rController.getPrinter()->GetCopyCount();
+ bool bCollate = i_rController.getPrinter()->IsCollateCopy();
+ bool bSinglePrintJobs = i_rController.getPrinter()->IsSinglePrintJobs();
+
+ // notify start of real print job
+ i_rController.jobStarted();
+
+ // setup PDFWriter context
+ vcl::PDFWriter::PDFWriterContext aContext;
+ aContext.Version = vcl::PDFWriter::PDFVersion::PDF_1_4;
+ aContext.Tagged = false;
+ aContext.DocumentLocale = Application::GetSettings().GetLanguageTag().getLocale();
+ aContext.ColorMode = i_rController.getPrinter()->GetPrinterOptions().IsConvertToGreyscales()
+ ? vcl::PDFWriter::DrawGreyscale : vcl::PDFWriter::DrawColor;
+
+ // prepare doc info
+ aContext.DocumentInfo.Title = i_rJobName;
+ aContext.DocumentInfo.Creator = i_rAppName;
+ aContext.DocumentInfo.Producer = i_rAppName;
+
+ // define how we handle metafiles in PDFWriter
+ vcl::PDFWriter::PlayMetafileContext aMtfContext;
+ aMtfContext.m_bOnlyLosslessCompression = true;
+
+ std::shared_ptr<vcl::PDFWriter> xWriter;
+ std::vector< PDFPrintFile > aPDFFiles;
+ VclPtr<Printer> xPrinter( i_rController.getPrinter() );
+ int nAllPages = i_rController.getFilteredPageCount();
+ i_rController.createProgressDialog();
+ bool bAborted = false;
+ PDFNewJobParameters aLastParm;
+
+ aContext.DPIx = xPrinter->GetDPIX();
+ aContext.DPIy = xPrinter->GetDPIY();
+ for( int nPage = 0; nPage < nAllPages && ! bAborted; nPage++ )
+ {
+ if( nPage == nAllPages-1 )
+ i_rController.setLastPage( true );
+
+ // get the page's metafile
+ GDIMetaFile aPageFile;
+ vcl::PrinterController::PageSize aPageSize = i_rController.getFilteredPageFile( nPage, aPageFile );
+ if( i_rController.isProgressCanceled() )
+ {
+ bAborted = true;
+ if( nPage != nAllPages-1 )
+ {
+ i_rController.createProgressDialog();
+ i_rController.setLastPage( true );
+ i_rController.getFilteredPageFile( nPage, aPageFile );
+ }
+ }
+ else
+ {
+ xPrinter->SetMapMode( MapMode( MapUnit::Map100thMM ) );
+ xPrinter->SetPaperSizeUser( aPageSize.aSize );
+ PDFNewJobParameters aNewParm(xPrinter->GetPaperSize(), xPrinter->GetPaperBin());
+
+ // create PDF writer on demand
+ // either on first page
+ // or on paper format change - cups does not support multiple paper formats per job (yet?)
+ // so we need to start a new job to get a new paper format from the printer
+ // orientation switches (that is switch of height and width) is handled transparently by CUPS
+ if( ! xWriter ||
+ (aNewParm != aLastParm && ! i_pFileName ) )
+ {
+ if( xWriter )
+ {
+ xWriter->Emit();
+ }
+ // produce PDF file
+ OUString aPDFUrl;
+ if( i_pFileName )
+ aPDFUrl = *i_pFileName;
+ else
+ osl_createTempFile( nullptr, nullptr, &aPDFUrl.pData );
+ // normalize to file URL
+ if( !comphelper::isFileUrl(aPDFUrl) )
+ {
+ // this is not a file URL, but it should
+ // form it into an osl friendly file URL
+ OUString aTmp;
+ osl_getFileURLFromSystemPath( aPDFUrl.pData, &aTmp.pData );
+ aPDFUrl = aTmp;
+ }
+ // save current file and paper format
+ aLastParm = aNewParm;
+ aPDFFiles.emplace_back( aPDFUrl, aNewParm );
+ // update context
+ aContext.URL = aPDFUrl;
+
+ // create and initialize PDFWriter
+ xWriter = std::make_shared<vcl::PDFWriter>( aContext, uno::Reference< beans::XMaterialHolder >() );
+ }
+
+ xWriter->NewPage( TenMuToPt( aNewParm.maPageSize.Width() ),
+ TenMuToPt( aNewParm.maPageSize.Height() ),
+ vcl::PDFWriter::Orientation::Portrait );
+
+ xWriter->PlayMetafile( aPageFile, aMtfContext );
+ }
+ }
+
+ // emit the last file
+ if( xWriter )
+ xWriter->Emit();
+
+ // handle collate, copy count and multiple jobs correctly
+ int nOuterJobs = 1;
+ if( bSinglePrintJobs )
+ {
+ nOuterJobs = nCopies;
+ m_aJobData.m_nCopies = 1;
+ }
+ else
+ {
+ if( bCollate )
+ {
+ if (aPDFFiles.size() == 1 && xPrinter->HasSupport(PrinterSupport::CollateCopy))
+ {
+ m_aJobData.setCollate( true );
+ m_aJobData.m_nCopies = nCopies;
+ }
+ else
+ {
+ nOuterJobs = nCopies;
+ m_aJobData.m_nCopies = 1;
+ }
+ }
+ else
+ {
+ m_aJobData.setCollate( false );
+ m_aJobData.m_nCopies = nCopies;
+ }
+ }
+
+ std::vector<OUString> aFaxNumbers;
+
+ // check for fax numbers
+ if (!bAborted && bFax)
+ {
+ aFaxNumbers = getFaxNumbers();
+ bAborted = aFaxNumbers.empty();
+ }
+
+ bool bSuccess(true);
+ // spool files
+ if( ! i_pFileName && ! bAborted )
+ {
+ do
+ {
+ OUString sFaxNumber;
+ if (!aFaxNumbers.empty())
+ {
+ sFaxNumber = aFaxNumbers.back();
+ aFaxNumbers.pop_back();
+ }
+
+ bool bFirstJob = true;
+ for( int nCurJob = 0; nCurJob < nOuterJobs; nCurJob++ )
+ {
+ for( size_t i = 0; i < aPDFFiles.size(); i++ )
+ {
+ oslFileHandle pFile = nullptr;
+ osl_openFile( aPDFFiles[i].maTmpURL.pData, &pFile, osl_File_OpenFlag_Read );
+ if (pFile && (osl_setFilePos(pFile, osl_Pos_Absolut, 0) == osl_File_E_None))
+ {
+ std::vector< char > buffer( 0x10000, 0 );
+ // update job data with current page size
+ Size aPageSize( aPDFFiles[i].maParameters.maPageSize );
+ m_aJobData.setPaper( TenMuToPt( aPageSize.Width() ), TenMuToPt( aPageSize.Height() ) );
+ // update job data with current paperbin
+ m_aJobData.setPaperBin( aPDFFiles[i].maParameters.mnPaperBin );
+
+ // spool current file
+ FILE* fp = PrinterInfoManager::get().startSpool(xPrinter->GetName(), i_rController.isDirectPrint());
+ if( fp )
+ {
+ sal_uInt64 nBytesRead = 0;
+ do
+ {
+ osl_readFile( pFile, buffer.data(), buffer.size(), &nBytesRead );
+ if( nBytesRead > 0 )
+ {
+ size_t nBytesWritten = fwrite(buffer.data(), 1, nBytesRead, fp);
+ OSL_ENSURE(nBytesRead == nBytesWritten, "short write");
+ if (nBytesRead != nBytesWritten)
+ break;
+ }
+ } while( nBytesRead == buffer.size() );
+ OUStringBuffer aBuf( i_rJobName.getLength() + 8 );
+ aBuf.append( i_rJobName );
+ if( i > 0 || nCurJob > 0 )
+ {
+ aBuf.append( ' ' );
+ aBuf.append( sal_Int32( i + nCurJob * aPDFFiles.size() ) );
+ }
+ bSuccess &=
+ PrinterInfoManager::get().endSpool(xPrinter->GetName(), aBuf.makeStringAndClear(), fp, m_aJobData, bFirstJob, sFaxNumber);
+ bFirstJob = false;
+ }
+ }
+ osl_closeFile( pFile );
+ }
+ }
+ }
+ while (!aFaxNumbers.empty());
+ }
+
+ // job has been spooled
+ i_rController.setJobState( bAborted
+ ? view::PrintableState_JOB_ABORTED
+ : (bSuccess ? view::PrintableState_JOB_SPOOLED
+ : view::PrintableState_JOB_SPOOLING_FAILED));
+
+ // clean up the temporary PDF files
+ if( ! i_pFileName || bAborted )
+ {
+ for(PDFPrintFile & rPDFFile : aPDFFiles)
+ {
+ osl_removeFile( rPDFFile.maTmpURL.pData );
+ SAL_INFO( "vcl.unx.print", "removed print PDF file " << rPDFFile.maTmpURL );
+ }
+ }
+
+ return true;
+}
+
+namespace {
+
+class PrinterUpdate
+{
+ static Idle* pPrinterUpdateIdle;
+ static int nActiveJobs;
+
+ static void doUpdate();
+ DECL_STATIC_LINK( PrinterUpdate, UpdateTimerHdl, Timer*, void );
+public:
+ static void update(SalGenericInstance const &rInstance);
+ static void jobStarted() { nActiveJobs++; }
+ static void jobEnded();
+};
+
+}
+
+Idle* PrinterUpdate::pPrinterUpdateIdle = nullptr;
+int PrinterUpdate::nActiveJobs = 0;
+
+void PrinterUpdate::doUpdate()
+{
+ ::psp::PrinterInfoManager& rManager( ::psp::PrinterInfoManager::get() );
+ SalGenericInstance *pInst = GetGenericInstance();
+ if( pInst && rManager.checkPrintersChanged( false ) )
+ pInst->PostPrintersChanged();
+}
+
+IMPL_STATIC_LINK_NOARG( PrinterUpdate, UpdateTimerHdl, Timer*, void )
+{
+ if( nActiveJobs < 1 )
+ {
+ doUpdate();
+ delete pPrinterUpdateIdle;
+ pPrinterUpdateIdle = nullptr;
+ }
+ else
+ pPrinterUpdateIdle->Start();
+}
+
+void PrinterUpdate::update(SalGenericInstance const &rInstance)
+{
+ if( Application::GetSettings().GetMiscSettings().GetDisablePrinting() )
+ return;
+
+ if( ! rInstance.isPrinterInit() )
+ {
+ // #i45389# start background printer detection
+ psp::PrinterInfoManager::get();
+ return;
+ }
+
+ if( nActiveJobs < 1 )
+ doUpdate();
+ else if( ! pPrinterUpdateIdle )
+ {
+ pPrinterUpdateIdle = new Idle("PrinterUpdateTimer");
+ pPrinterUpdateIdle->SetPriority( TaskPriority::LOWEST );
+ pPrinterUpdateIdle->SetInvokeHandler( LINK( nullptr, PrinterUpdate, UpdateTimerHdl ) );
+ pPrinterUpdateIdle->Start();
+ }
+}
+
+void SalGenericInstance::updatePrinterUpdate()
+{
+ PrinterUpdate::update(*this);
+}
+
+void SalGenericInstance::jobStartedPrinterUpdate()
+{
+ PrinterUpdate::jobStarted();
+}
+
+void PrinterUpdate::jobEnded()
+{
+ nActiveJobs--;
+ if( nActiveJobs < 1 )
+ {
+ if( pPrinterUpdateIdle )
+ {
+ pPrinterUpdateIdle->Stop();
+ delete pPrinterUpdateIdle;
+ pPrinterUpdateIdle = nullptr;
+ doUpdate();
+ }
+ }
+}
+
+void SalGenericInstance::jobEndedPrinterUpdate()
+{
+ PrinterUpdate::jobEnded();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/genpspgraphics.cxx b/vcl/unx/generic/print/genpspgraphics.cxx
new file mode 100644
index 000000000..7c4e14b27
--- /dev/null
+++ b/vcl/unx/generic/print/genpspgraphics.cxx
@@ -0,0 +1,521 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <vector>
+
+#include <sal/types.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <i18nlangtag/mslangid.hxx>
+#include <jobdata.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <config_cairo_canvas.h>
+
+#include <fontsubset.hxx>
+#include <unx/freetype_glyphcache.hxx>
+#include <unx/geninst.h>
+#include <unx/genpspgraphics.h>
+#include <unx/printergfx.hxx>
+#include <langboost.hxx>
+#include <fontinstance.hxx>
+#include <fontattributes.hxx>
+#include <impfontmetricdata.hxx>
+#include <font/FontSelectPattern.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <font/PhysicalFontFace.hxx>
+#include <o3tl/string_view.hxx>
+#include <sallayout.hxx>
+
+using namespace psp;
+
+/*******************************************************
+ * GenPspGraphics
+ *******************************************************/
+
+GenPspGraphics::GenPspGraphics()
+ : m_pJobData( nullptr )
+ , m_pPrinterGfx( nullptr )
+{
+}
+
+void GenPspGraphics::Init(psp::JobData* pJob, psp::PrinterGfx* pGfx)
+{
+ m_pBackend = std::make_unique<GenPspGfxBackend>(pGfx);
+ m_pJobData = pJob;
+ m_pPrinterGfx = pGfx;
+ SetLayout( SalLayoutFlags::NONE );
+}
+
+GenPspGraphics::~GenPspGraphics()
+{
+ ReleaseFonts();
+}
+
+void GenPspGraphics::GetResolution( sal_Int32 &rDPIX, sal_Int32 &rDPIY )
+{
+ if (m_pJobData != nullptr)
+ {
+ int x = m_pJobData->m_aContext.getRenderResolution();
+
+ rDPIX = x;
+ rDPIY = x;
+ }
+}
+
+namespace {
+
+class ImplPspFontData : public FreetypeFontFace
+{
+private:
+ sal_IntPtr mnFontId;
+
+public:
+ explicit ImplPspFontData( const psp::FastPrintFontInfo& );
+ virtual sal_IntPtr GetFontId() const override { return mnFontId; }
+};
+
+}
+
+ImplPspFontData::ImplPspFontData(const psp::FastPrintFontInfo& rInfo)
+: FreetypeFontFace(nullptr, GenPspGraphics::Info2FontAttributes(rInfo)),
+ mnFontId( rInfo.m_nID )
+{}
+
+namespace {
+
+class PspSalLayout : public GenericSalLayout
+{
+public:
+ PspSalLayout(psp::PrinterGfx&, LogicalFontInstance &rFontInstance);
+
+ void InitFont() const final override;
+
+private:
+ ::psp::PrinterGfx& mrPrinterGfx;
+ sal_IntPtr mnFontID;
+ int mnFontHeight;
+ int mnFontWidth;
+ bool mbVertical;
+ bool mbArtItalic;
+ bool mbArtBold;
+};
+
+}
+
+PspSalLayout::PspSalLayout(::psp::PrinterGfx& rGfx, LogicalFontInstance &rFontInstance)
+: GenericSalLayout(rFontInstance)
+, mrPrinterGfx(rGfx)
+{
+ mnFontID = mrPrinterGfx.GetFontID();
+ mnFontHeight = mrPrinterGfx.GetFontHeight();
+ mnFontWidth = mrPrinterGfx.GetFontWidth();
+ mbVertical = mrPrinterGfx.GetFontVertical();
+ mbArtItalic = mrPrinterGfx.GetArtificialItalic();
+ mbArtBold = mrPrinterGfx.GetArtificialBold();
+}
+
+void PspSalLayout::InitFont() const
+{
+ GenericSalLayout::InitFont();
+ mrPrinterGfx.SetFont(mnFontID, mnFontHeight, mnFontWidth,
+ mnOrientation, mbVertical, mbArtItalic, mbArtBold);
+}
+
+void GenPspGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
+{
+ const GlyphItem* pGlyph;
+ DevicePoint aPos;
+ int nStart = 0;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ m_pPrinterGfx->DrawGlyph(Point(aPos.getX(), aPos.getY()), *pGlyph);
+}
+
+FontCharMapRef GenPspGraphics::GetFontCharMap() const
+{
+ if (!m_pFreetypeFont[0])
+ return nullptr;
+
+ return m_pFreetypeFont[0]->GetFreetypeFont().GetFontCharMap();
+}
+
+bool GenPspGraphics::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ if (!m_pFreetypeFont[0])
+ return false;
+
+ return m_pFreetypeFont[0]->GetFreetypeFont().GetFontCapabilities(rFontCapabilities);
+}
+
+void GenPspGraphics::SetFont(LogicalFontInstance *pFontInstance, int nFallbackLevel)
+{
+ // release all fonts that are to be overridden
+ for( int i = nFallbackLevel; i < MAX_FALLBACK; ++i )
+ {
+ // old server side font is no longer referenced
+ m_pFreetypeFont[i] = nullptr;
+ }
+
+ // return early if there is no new font
+ if (!pFontInstance)
+ return;
+
+ sal_IntPtr nID = pFontInstance->GetFontFace()->GetFontId();
+
+ const vcl::font::FontSelectPattern& rEntry = pFontInstance->GetFontSelectPattern();
+
+ // determine which font attributes need to be emulated
+ bool bArtItalic = false;
+ bool bArtBold = false;
+ if( rEntry.GetItalic() == ITALIC_OBLIQUE || rEntry.GetItalic() == ITALIC_NORMAL )
+ {
+ FontItalic eItalic = m_pPrinterGfx->GetFontMgr().getFontItalic( nID );
+ if( eItalic != ITALIC_NORMAL && eItalic != ITALIC_OBLIQUE )
+ bArtItalic = true;
+ }
+ FontWeight nWeight = rEntry.GetWeight();
+ FontWeight nRealWeight = m_pPrinterGfx->GetFontMgr().getFontWeight( nID );
+ if( nRealWeight <= WEIGHT_MEDIUM && nWeight > WEIGHT_MEDIUM )
+ {
+ bArtBold = true;
+ }
+
+ // also set the serverside font for layouting
+ // requesting a font provided by builtin rasterizer
+ FreetypeFontInstance* pFreetypeFont = static_cast<FreetypeFontInstance*>(pFontInstance);
+ m_pFreetypeFont[ nFallbackLevel ] = pFreetypeFont;
+
+ // ignore fonts with e.g. corrupted font files
+ if (!m_pFreetypeFont[nFallbackLevel]->GetFreetypeFont().TestFont())
+ m_pFreetypeFont[nFallbackLevel] = nullptr;
+
+ // set the printer font
+ m_pPrinterGfx->SetFont( nID,
+ rEntry.mnHeight,
+ rEntry.mnWidth,
+ rEntry.mnOrientation,
+ rEntry.mbVertical,
+ bArtItalic,
+ bArtBold
+ );
+}
+
+void GenPspGraphics::SetTextColor( Color nColor )
+{
+ psp::PrinterColor aColor (nColor.GetRed(),
+ nColor.GetGreen(),
+ nColor.GetBlue());
+ m_pPrinterGfx->SetTextColor (aColor);
+}
+
+bool GenPspGraphics::AddTempDevFont( vcl::font::PhysicalFontCollection*, const OUString&,const OUString& )
+{
+ return false;
+}
+
+bool GenPspGraphics::AddTempDevFontHelper( vcl::font::PhysicalFontCollection* pFontCollection,
+ std::u16string_view rFileURL,
+ const OUString& rFontName)
+{
+ // inform PSP font manager
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ std::vector<psp::fontID> aFontIds = rMgr.addFontFile( rFileURL );
+ if( aFontIds.empty() )
+ return false;
+
+ FreetypeManager& rFreetypeManager = FreetypeManager::get();
+ for (auto const& elem : aFontIds)
+ {
+ // prepare font data
+ psp::FastPrintFontInfo aInfo;
+ rMgr.getFontFastInfo( elem, aInfo );
+ if (!rFontName.isEmpty())
+ aInfo.m_aFamilyName = rFontName;
+
+ // inform glyph cache of new font
+ FontAttributes aDFA = GenPspGraphics::Info2FontAttributes( aInfo );
+ aDFA.IncreaseQualityBy( 5800 );
+
+ int nFaceNum = rMgr.getFontFaceNumber( aInfo.m_nID );
+ int nVariantNum = rMgr.getFontFaceVariation( aInfo.m_nID );
+
+ const OString& rFileName = rMgr.getFontFileSysPath( aInfo.m_nID );
+ rFreetypeManager.AddFontFile(rFileName, nFaceNum, nVariantNum, aInfo.m_nID, aDFA);
+ }
+
+ // announce new font to device's font list
+ rFreetypeManager.AnnounceFonts(pFontCollection);
+ return true;
+}
+
+void GenPspGraphics::GetDevFontList( vcl::font::PhysicalFontCollection *pFontCollection )
+{
+ ::std::vector< psp::fontID > aList;
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ rMgr.getFontList( aList );
+
+ psp::FastPrintFontInfo aInfo;
+ for (auto const& elem : aList)
+ if (rMgr.getFontFastInfo (elem, aInfo))
+ AnnounceFonts( pFontCollection, aInfo );
+
+ // register platform specific font substitutions if available
+ SalGenericInstance::RegisterFontSubstitutors( pFontCollection );
+}
+
+void GenPspGraphics::ClearDevFontCache()
+{
+ FreetypeManager::get().ClearFontCache();
+}
+
+void GenPspGraphics::GetFontMetric(ImplFontMetricDataRef& rxFontMetric, int nFallbackLevel)
+{
+ if (nFallbackLevel >= MAX_FALLBACK)
+ return;
+
+ if (m_pFreetypeFont[nFallbackLevel])
+ m_pFreetypeFont[nFallbackLevel]->GetFreetypeFont().GetFontMetric(rxFontMetric);
+}
+
+std::unique_ptr<GenericSalLayout> GenPspGraphics::GetTextLayout(int nFallbackLevel)
+{
+ assert(m_pFreetypeFont[nFallbackLevel]);
+ if (!m_pFreetypeFont[nFallbackLevel])
+ return nullptr;
+ return std::make_unique<PspSalLayout>(*m_pPrinterGfx, *m_pFreetypeFont[nFallbackLevel]);
+}
+
+bool GenPspGraphics::CreateFontSubset(
+ const OUString& rToFile,
+ const vcl::font::PhysicalFontFace* pFont,
+ const sal_GlyphId* pGlyphIds,
+ const sal_uInt8* pEncoding,
+ sal_Int32* pWidths,
+ int nGlyphCount,
+ FontSubsetInfo& rInfo
+ )
+{
+ // in this context the pFont->GetFontId() is a valid PSP
+ // font since they are the only ones left after the PDF
+ // export has filtered its list of subsettable fonts (for
+ // which this method was created). The correct way would
+ // be to have the FreetypeManager search for the PhysicalFontFace pFont
+ psp::fontID aFont = pFont->GetFontId();
+
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ bool bSuccess = rMgr.createFontSubset( rInfo,
+ aFont,
+ rToFile,
+ pGlyphIds,
+ pEncoding,
+ pWidths,
+ nGlyphCount );
+ return bSuccess;
+}
+
+void GenPspGraphics::GetGlyphWidths( const vcl::font::PhysicalFontFace* pFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc )
+{
+ // in this context the pFont->GetFontId() is a valid PSP
+ // font since they are the only ones left after the PDF
+ // export has filtered its list of subsettable fonts (for
+ // which this method was created). The correct way would
+ // be to have the FreetypeManager search for the PhysicalFontFace pFont
+ psp::fontID aFont = pFont->GetFontId();
+ GenPspGraphics::DoGetGlyphWidths( aFont, bVertical, rWidths, rUnicodeEnc );
+}
+
+void GenPspGraphics::DoGetGlyphWidths( psp::fontID aFont,
+ bool bVertical,
+ std::vector< sal_Int32 >& rWidths,
+ Ucs2UIntMap& rUnicodeEnc )
+{
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ rMgr.getGlyphWidths( aFont, bVertical, rWidths, rUnicodeEnc );
+}
+
+FontAttributes GenPspGraphics::Info2FontAttributes( const psp::FastPrintFontInfo& rInfo )
+{
+ FontAttributes aDFA;
+ aDFA.SetFamilyName( rInfo.m_aFamilyName );
+ aDFA.SetStyleName( rInfo.m_aStyleName );
+ aDFA.SetFamilyType( rInfo.m_eFamilyStyle );
+ aDFA.SetWeight( rInfo.m_eWeight );
+ aDFA.SetItalic( rInfo.m_eItalic );
+ aDFA.SetWidthType( rInfo.m_eWidth );
+ aDFA.SetPitch( rInfo.m_ePitch );
+ aDFA.SetSymbolFlag( rInfo.m_aEncoding == RTL_TEXTENCODING_SYMBOL );
+ aDFA.SetQuality(512);
+
+ // add font family name aliases
+ for (auto const& alias : rInfo.m_aAliases)
+ aDFA.AddMapName(alias);
+
+#if OSL_DEBUG_LEVEL > 2
+ if( aDFA.GetMapNames().getLength() > 0 )
+ {
+ SAL_INFO( "vcl.fonts", "using alias names " << aDFA.GetMapNames() << " for font family " << aDFA.GetFamilyName() );
+ }
+#endif
+
+ return aDFA;
+}
+
+namespace vcl
+{
+ const char* getLangBoost()
+ {
+ const char* pLangBoost;
+ const LanguageType eLang = Application::GetSettings().GetUILanguageTag().getLanguageType();
+ if (eLang == LANGUAGE_JAPANESE)
+ pLangBoost = "jan";
+ else if (MsLangId::isKorean(eLang))
+ pLangBoost = "kor";
+ else if (MsLangId::isSimplifiedChinese(eLang))
+ pLangBoost = "zhs";
+ else if (MsLangId::isTraditionalChinese(eLang))
+ pLangBoost = "zht";
+ else
+ pLangBoost = nullptr;
+ return pLangBoost;
+ }
+}
+
+void GenPspGraphics::AnnounceFonts( vcl::font::PhysicalFontCollection* pFontCollection, const psp::FastPrintFontInfo& aInfo )
+{
+ int nQuality = 0;
+
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+ OString aFileName( rMgr.getFontFileSysPath( aInfo.m_nID ) );
+ int nPos = aFileName.lastIndexOf( '_' );
+ if( nPos == -1 || aFileName[nPos+1] == '.' )
+ nQuality += 5;
+ else
+ {
+ static const char* pLangBoost = nullptr;
+ static bool bOnce = true;
+ if( bOnce )
+ {
+ bOnce = false;
+ pLangBoost = vcl::getLangBoost();
+ }
+
+ if( pLangBoost )
+ if( o3tl::equalsIgnoreAsciiCase(aFileName.subView( nPos+1, 3 ), pLangBoost ) )
+ nQuality += 10;
+ }
+
+ rtl::Reference<ImplPspFontData> pFD(new ImplPspFontData( aInfo ));
+ pFD->IncreaseQualityBy( nQuality );
+ pFontCollection->Add( pFD.get() );
+}
+
+SystemGraphicsData GenPspGraphics::GetGraphicsData() const
+{
+ return SystemGraphicsData();
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+bool GenPspGraphics::SupportsCairo() const
+{
+ return false;
+}
+
+cairo::SurfaceSharedPtr GenPspGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& /*rSurface*/) const
+{
+ return cairo::SurfaceSharedPtr();
+}
+
+cairo::SurfaceSharedPtr GenPspGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int /*x*/, int /*y*/, int /*width*/, int /*height*/) const
+{
+ return cairo::SurfaceSharedPtr();
+}
+
+cairo::SurfaceSharedPtr GenPspGraphics::CreateBitmapSurface(const OutputDevice& /*rRefDevice*/, const BitmapSystemData& /*rData*/, const Size& /*rSize*/) const
+{
+ return cairo::SurfaceSharedPtr();
+}
+
+css::uno::Any GenPspGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& /*rSurface*/, const basegfx::B2ISize& /*rSize*/) const
+{
+ return css::uno::Any();
+}
+
+#endif // ENABLE_CAIRO_CANVAS
+
+void GenPspGraphics::DoFreeEmbedFontData( const void* pData, tools::Long nLen )
+{
+ if( pData )
+ munmap( const_cast<void *>(pData), nLen );
+}
+
+const void* GenPspGraphics::DoGetEmbedFontData(psp::fontID aFont, tools::Long* pDataLen)
+{
+
+ psp::PrintFontManager& rMgr = psp::PrintFontManager::get();
+
+ OString aSysPath = rMgr.getFontFileSysPath( aFont );
+
+ int fd = open( aSysPath.getStr(), O_RDONLY );
+ if( fd < 0 )
+ return nullptr;
+ struct stat aStat;
+ if( fstat( fd, &aStat ) )
+ {
+ close( fd );
+ return nullptr;
+ }
+ void* pFile = mmap( nullptr, aStat.st_size, PROT_READ, MAP_SHARED, fd, 0 );
+ close( fd );
+ if( pFile == MAP_FAILED )
+ return nullptr;
+ *pDataLen = aStat.st_size;
+
+ return pFile;
+}
+
+void GenPspGraphics::FreeEmbedFontData( const void* pData, tools::Long nLen )
+{
+ DoFreeEmbedFontData( pData, nLen );
+}
+
+const void* GenPspGraphics::GetEmbedFontData(const vcl::font::PhysicalFontFace* pFont, tools::Long* pDataLen)
+{
+ // in this context the pFont->GetFontId() is a valid PSP
+ // font since they are the only ones left after the PDF
+ // export has filtered its list of subsettable fonts (for
+ // which this method was created). The correct way would
+ // be to have the FreetypeManager search for the PhysicalFontFace pFont
+ psp::fontID aFont = pFont->GetFontId();
+ return DoGetEmbedFontData(aFont, pDataLen);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/glyphset.cxx b/vcl/unx/generic/print/glyphset.cxx
new file mode 100644
index 000000000..6b0475a62
--- /dev/null
+++ b/vcl/unx/generic/print/glyphset.cxx
@@ -0,0 +1,301 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "glyphset.hxx"
+
+#include <sft.hxx>
+
+#include <unx/printergfx.hxx>
+#include <fontsubset.hxx>
+#include <unx/fontmanager.hxx>
+
+#include <tools/gen.hxx>
+
+#include <osl/thread.h>
+
+#include <rtl/ustring.hxx>
+#include <rtl/strbuf.hxx>
+
+#include <unotools/tempfile.hxx>
+
+#include <algorithm>
+
+using namespace vcl;
+using namespace psp;
+
+GlyphSet::GlyphSet (sal_Int32 nFontID, bool bVertical)
+ : mnFontID (nFontID),
+ mbVertical (bVertical)
+{
+ PrintFontManager &rMgr = PrintFontManager::get();
+ maBaseName = OUStringToOString (rMgr.getPSName(mnFontID),
+ RTL_TEXTENCODING_ASCII_US);
+}
+
+void
+GlyphSet::GetGlyphID (
+ sal_GlyphId nGlyph,
+ unsigned char* nOutGlyphID,
+ sal_Int32* nOutGlyphSetID
+ )
+{
+ if (!LookupGlyphID(nGlyph, nOutGlyphID, nOutGlyphSetID))
+ AddGlyphID(nGlyph, nOutGlyphID, nOutGlyphSetID);
+}
+
+bool
+GlyphSet::LookupGlyphID (
+ sal_GlyphId nGlyph,
+ unsigned char* nOutGlyphID,
+ sal_Int32* nOutGlyphSetID
+ )
+{
+ sal_Int32 nGlyphSetID = 1;
+
+ // loop through all the font subsets
+ for (auto const& glyph : maGlyphList)
+ {
+ // check every subset if it contains the queried unicode char
+ glyph_map_t::const_iterator aGlyph = glyph.find (nGlyph);
+ if (aGlyph != glyph.end())
+ {
+ // success: found the glyph id, return the mapped glyphid and the glyphsetid
+ *nOutGlyphSetID = nGlyphSetID;
+ *nOutGlyphID = aGlyph->second;
+ return true;
+ }
+ ++nGlyphSetID;
+ }
+
+ *nOutGlyphSetID = -1;
+ *nOutGlyphID = 0;
+ return false;
+}
+
+void
+GlyphSet::AddNotdef (glyph_map_t &rGlyphMap)
+{
+ if (rGlyphMap.empty())
+ rGlyphMap[0] = 0;
+}
+
+void
+GlyphSet::AddGlyphID (
+ sal_GlyphId nGlyph,
+ unsigned char* nOutGlyphID,
+ sal_Int32* nOutGlyphSetID
+ )
+{
+ // create an empty glyphmap that is reserved for unencoded symbol glyphs,
+ // and a second map that takes any other
+ if (maGlyphList.empty())
+ {
+ glyph_map_t aMap, aMapp;
+
+ maGlyphList.push_back (aMap);
+ maGlyphList.push_back (aMapp);
+ }
+ // if the last map is full, create a new one
+ if (maGlyphList.back().size() == 255)
+ {
+ glyph_map_t aMap;
+ maGlyphList.push_back (aMap);
+ }
+
+ glyph_map_t& aGlyphSet = maGlyphList.back();
+ AddNotdef (aGlyphSet);
+
+ int nSize = aGlyphSet.size();
+
+ aGlyphSet [nGlyph] = nSize;
+ *nOutGlyphSetID = maGlyphList.size();
+ *nOutGlyphID = aGlyphSet [nGlyph];
+}
+
+OString
+GlyphSet::GetGlyphSetName (sal_Int32 nGlyphSetID)
+{
+ OStringBuffer aSetName( maBaseName.getLength() + 32 );
+ aSetName.append( maBaseName );
+ aSetName.append( "FID" );
+ aSetName.append( mnFontID );
+ aSetName.append( mbVertical ? "VGSet" : "HGSet" );
+ aSetName.append( nGlyphSetID );
+ return aSetName.makeStringAndClear();
+}
+
+OString
+GlyphSet::GetReencodedFontName (rtl_TextEncoding nEnc, std::string_view rFontName)
+{
+ if ( nEnc == RTL_TEXTENCODING_MS_1252
+ || nEnc == RTL_TEXTENCODING_ISO_8859_1)
+ {
+ return OString::Concat(rFontName) + "-iso1252";
+ }
+ else
+ if (nEnc >= RTL_TEXTENCODING_USER_START && nEnc <= RTL_TEXTENCODING_USER_END)
+ {
+ return OString::Concat(rFontName)
+ + "-enc"
+ + OString::number(nEnc - RTL_TEXTENCODING_USER_START);
+ }
+ else
+ {
+ return OString();
+ }
+}
+
+void GlyphSet::DrawGlyph(PrinterGfx& rGfx,
+ const Point& rPoint,
+ const sal_GlyphId nGlyphId)
+{
+ unsigned char nGlyphID;
+ sal_Int32 nGlyphSetID;
+
+ // convert to font glyph id and font subset
+ GetGlyphID (nGlyphId, &nGlyphID, &nGlyphSetID);
+
+ OString aGlyphSetName = GetGlyphSetName(nGlyphSetID);
+
+ rGfx.PSSetFont (aGlyphSetName, RTL_TEXTENCODING_DONTKNOW);
+ rGfx.PSMoveTo (rPoint);
+ rGfx.PSShowGlyph(nGlyphID);
+}
+
+namespace {
+
+struct EncEntry
+{
+ unsigned char aEnc;
+ tools::Long aGID;
+
+ EncEntry() : aEnc( 0 ), aGID( 0 ) {}
+
+ bool operator<( const EncEntry& rRight ) const
+ { return aEnc < rRight.aEnc; }
+};
+
+}
+
+static void CreatePSUploadableFont( TrueTypeFont* pSrcFont, FILE* pTmpFile,
+ const char* pGlyphSetName, int nGlyphCount,
+ /*const*/ const sal_uInt16* pRequestedGlyphs, /*const*/ const unsigned char* pEncoding,
+ bool bAllowType42 )
+{
+ // match the font-subset to the printer capabilities
+ // TODO: allow CFF for capable printers
+ FontType nTargetMask = FontType::TYPE1_PFA | FontType::TYPE3_FONT;
+ if( bAllowType42 )
+ nTargetMask |= FontType::TYPE42_FONT;
+
+ std::vector< EncEntry > aSorted( nGlyphCount, EncEntry() );
+ for( int i = 0; i < nGlyphCount; i++ )
+ {
+ aSorted[i].aEnc = pEncoding[i];
+ aSorted[i].aGID = pRequestedGlyphs[i];
+ }
+
+ std::stable_sort( aSorted.begin(), aSorted.end() );
+
+ std::vector< unsigned char > aEncoding( nGlyphCount );
+ std::vector< sal_GlyphId > aRequestedGlyphs( nGlyphCount );
+
+ for( int i = 0; i < nGlyphCount; i++ )
+ {
+ aEncoding[i] = aSorted[i].aEnc;
+ aRequestedGlyphs[i] = aSorted[i].aGID;
+ }
+
+ FontSubsetInfo aInfo;
+ aInfo.LoadFont( pSrcFont );
+
+ aInfo.CreateFontSubset( nTargetMask, pTmpFile, pGlyphSetName,
+ aRequestedGlyphs.data(), aEncoding.data(), nGlyphCount );
+}
+
+void
+GlyphSet::PSUploadFont (osl::File& rOutFile, PrinterGfx &rGfx, bool bAllowType42, std::vector< OString >& rSuppliedFonts )
+{
+ TrueTypeFont *pTTFont;
+ OString aTTFileName (rGfx.GetFontMgr().getFontFileSysPath(mnFontID));
+ int nFace = rGfx.GetFontMgr().getFontFaceNumber(mnFontID);
+ SFErrCodes nSuccess = OpenTTFontFile(aTTFileName.getStr(), nFace, &pTTFont);
+ if (nSuccess != SFErrCodes::Ok)
+ return;
+
+ utl::TempFile aTmpFile;
+ aTmpFile.EnableKillingFile();
+ FILE* pTmpFile = fopen(OUStringToOString(aTmpFile.GetFileName(), osl_getThreadTextEncoding()).getStr(), "w+b");
+ if (pTmpFile == nullptr)
+ return;
+
+ // encoding vector maps character encoding to the ordinal number
+ // of the glyph in the output file
+ unsigned char pEncoding[256];
+ sal_uInt16 pTTGlyphMapping[256];
+
+ // loop through all the font glyph subsets
+ sal_Int32 nGlyphSetID = 1;
+ for (auto const& glyph : maGlyphList)
+ {
+ if (glyph.empty())
+ {
+ ++nGlyphSetID;
+ continue;
+ }
+
+ // loop through all the glyphs in the subset
+ sal_Int32 n = 0;
+ for (auto const& elem : glyph)
+ {
+ pTTGlyphMapping [n] = elem.first;
+ pEncoding [n] = elem.second;
+ n++;
+ }
+
+ // create the current subset
+ OString aGlyphSetName = GetGlyphSetName(nGlyphSetID);
+ fprintf( pTmpFile, "%%%%BeginResource: font %s\n", aGlyphSetName.getStr() );
+ CreatePSUploadableFont( pTTFont, pTmpFile, aGlyphSetName.getStr(), glyph.size(),
+ pTTGlyphMapping, pEncoding, bAllowType42 );
+ fprintf( pTmpFile, "%%%%EndResource\n" );
+ rSuppliedFonts.push_back( aGlyphSetName );
+ ++nGlyphSetID;
+ }
+
+ // copy the file into the page header
+ rewind(pTmpFile);
+ fflush(pTmpFile);
+
+ unsigned char pBuffer[0x2000];
+ sal_uInt64 nIn;
+ sal_uInt64 nOut;
+ do
+ {
+ nIn = fread(pBuffer, 1, sizeof(pBuffer), pTmpFile);
+ rOutFile.write (pBuffer, nIn, nOut);
+ }
+ while ((nIn == nOut) && !feof(pTmpFile));
+
+ // cleanup
+ CloseTTFont (pTTFont);
+ fclose (pTmpFile);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/glyphset.hxx b/vcl/unx/generic/print/glyphset.hxx
new file mode 100644
index 000000000..db7fe72ef
--- /dev/null
+++ b/vcl/unx/generic/print/glyphset.hxx
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osl/file.hxx>
+
+#include <rtl/string.hxx>
+
+#include <glyphid.hxx>
+
+#include <string_view>
+#include <vector>
+#include <unordered_map>
+
+class Point;
+
+namespace psp {
+
+class PrinterGfx;
+class PrintFontManager;
+
+class GlyphSet
+{
+private:
+
+ sal_Int32 mnFontID;
+ bool mbVertical;
+ OString maBaseName;
+
+ typedef std::unordered_map< sal_GlyphId, sal_uInt8 > glyph_map_t;
+ std::vector< glyph_map_t > maGlyphList;
+
+ OString GetGlyphSetName (sal_Int32 nGlyphSetID);
+
+ void GetGlyphID (sal_GlyphId nGlyphId,
+ unsigned char* nOutGlyphID, sal_Int32* nOutGlyphSetID);
+ bool LookupGlyphID (sal_GlyphId nGlyphId,
+ unsigned char* nOutGlyphID, sal_Int32* nOutGlyphSetID);
+ void AddGlyphID (sal_GlyphId nGlyphId,
+ unsigned char* nOutGlyphID,
+ sal_Int32* nOutGlyphSetID);
+ static void AddNotdef (glyph_map_t &rGlyphMap);
+
+public:
+
+ GlyphSet (sal_Int32 nFontID, bool bVertical);
+ /* FIXME delete the glyphlist in ~GlyphSet ??? */
+
+ sal_Int32 GetFontID () const { return mnFontID;}
+ static OString
+ GetReencodedFontName (rtl_TextEncoding nEnc,
+ std::string_view rFontName);
+
+ bool IsVertical () const { return mbVertical;}
+
+ void DrawGlyph (PrinterGfx& rGfx,
+ const Point& rPoint,
+ const sal_GlyphId nGlyphId);
+ void PSUploadFont (osl::File& rOutFile, PrinterGfx &rGfx, bool bAsType42, std::vector< OString >& rSuppliedFonts );
+};
+
+} /* namespace psp */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/printerjob.cxx b/vcl/unx/generic/print/printerjob.cxx
new file mode 100644
index 000000000..233bd2195
--- /dev/null
+++ b/vcl/unx/generic/print/printerjob.cxx
@@ -0,0 +1,973 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <stdio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "psputil.hxx"
+
+#include <unx/printerjob.hxx>
+#include <unx/printergfx.hxx>
+#include <ppdparser.hxx>
+#include <strhelper.hxx>
+#include <printerinfomanager.hxx>
+
+#include <rtl/ustring.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <osl/thread.h>
+#include <osl/security.hxx>
+
+#include <algorithm>
+#include <cstddef>
+#include <deque>
+#include <vector>
+
+using namespace psp;
+
+#define nBLOCKSIZE 0x2000
+
+namespace psp
+{
+
+static bool
+AppendPS (FILE* pDst, osl::File* pSrc, unsigned char* pBuffer)
+{
+ assert(pBuffer);
+ if ((pDst == nullptr) || (pSrc == nullptr))
+ return false;
+
+ if (pSrc->setPos(osl_Pos_Absolut, 0) != osl::FileBase::E_None)
+ return false;
+
+ sal_uInt64 nIn = 0;
+ sal_uInt64 nOut = 0;
+ do
+ {
+ pSrc->read (pBuffer, nBLOCKSIZE, nIn);
+ if (nIn > 0)
+ nOut = fwrite (pBuffer, 1, sal::static_int_cast<sal_uInt32>(nIn), pDst);
+ }
+ while ((nIn > 0) && (nIn == nOut));
+
+ return true;
+}
+
+} // namespace psp
+
+/*
+ * private convenience routines for file handling
+ */
+
+std::unique_ptr<osl::File>
+PrinterJob::CreateSpoolFile (std::u16string_view rName, std::u16string_view rExtension) const
+{
+ OUString aFile = OUString::Concat(rName) + rExtension;
+ OUString aFileURL;
+ osl::File::RC nError = osl::File::getFileURLFromSystemPath( aFile, aFileURL );
+ if (nError != osl::File::E_None)
+ return nullptr;
+ aFileURL = maSpoolDirName + "/" + aFileURL;
+
+ std::unique_ptr<osl::File> pFile( new osl::File (aFileURL) );
+ nError = pFile->open (osl_File_OpenFlag_Read | osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
+ if (nError != osl::File::E_None)
+ {
+ return nullptr;
+ }
+
+ osl::File::setAttributes (aFileURL,
+ osl_File_Attribute_OwnWrite | osl_File_Attribute_OwnRead);
+ return pFile;
+}
+
+/*
+ * public methods of PrinterJob: for use in PrinterGfx
+ */
+
+void
+PrinterJob::GetScale (double &rXScale, double &rYScale) const
+{
+ rXScale = mfXScale;
+ rYScale = mfYScale;
+}
+
+sal_uInt16
+PrinterJob::GetDepth () const
+{
+ sal_Int32 nLevel = GetPostscriptLevel();
+ bool bColor = IsColorPrinter ();
+
+ return nLevel > 1 && bColor ? 24 : 8;
+}
+
+sal_uInt16
+PrinterJob::GetPostscriptLevel (const JobData *pJobData) const
+{
+ sal_uInt16 nPSLevel = 2;
+
+ if( pJobData == nullptr )
+ pJobData = &m_aLastJobData;
+
+ if( pJobData->m_nPSLevel )
+ nPSLevel = pJobData->m_nPSLevel;
+ else
+ if( pJobData->m_pParser )
+ nPSLevel = pJobData->m_pParser->getLanguageLevel();
+
+ return nPSLevel;
+}
+
+bool
+PrinterJob::IsColorPrinter () const
+{
+ bool bColor = false;
+
+ if( m_aLastJobData.m_nColorDevice )
+ bColor = m_aLastJobData.m_nColorDevice != -1;
+ else if( m_aLastJobData.m_pParser )
+ bColor = m_aLastJobData.m_pParser->isColorDevice();
+
+ return bColor;
+}
+
+osl::File*
+PrinterJob::GetCurrentPageBody ()
+{
+ return maPageVector.back().get();
+}
+
+/*
+ * public methods of PrinterJob: the actual job / spool handling
+ */
+PrinterJob::PrinterJob()
+ : mnFileMode(0)
+ , m_pGraphics(nullptr)
+ , mnResolution(96)
+ , mnWidthPt(0)
+ , mnHeightPt(0)
+ , mnMaxWidthPt(0)
+ , mnMaxHeightPt(0)
+ , mnLandscapes(0)
+ , mnPortraits(0)
+ , mnLMarginPt(0)
+ , mnRMarginPt(0)
+ , mnTMarginPt(0)
+ , mnBMarginPt(0)
+ , mfXScale(1)
+ , mfYScale(1)
+ , m_bQuickJob(false)
+{
+}
+
+/* remove all our temporary files, uses external program "rm", since
+ osl functionality is inadequate */
+static void
+removeSpoolDir (const OUString& rSpoolDir)
+{
+ OUString aSysPath;
+ if( osl::File::E_None != osl::File::getSystemPathFromFileURL( rSpoolDir, aSysPath ) )
+ {
+ // Conversion did not work, as this is quite a dangerous action,
+ // we should abort here...
+ OSL_FAIL( "psprint: couldn't remove spool directory" );
+ return;
+ }
+ OString aSysPathByte =
+ OUStringToOString (aSysPath, osl_getThreadTextEncoding());
+ if (system (OString("rm -rf " + aSysPathByte).getStr()) == -1)
+ OSL_FAIL( "psprint: couldn't remove spool directory" );
+}
+
+/* creates a spool directory with a "pidgin random" value based on
+ current system time */
+static OUString
+createSpoolDir ()
+{
+ TimeValue aCur;
+ osl_getSystemTime( &aCur );
+ sal_Int32 nRand = aCur.Seconds ^ (aCur.Nanosec/1000);
+
+ OUString aTmpDir;
+ osl_getTempDirURL( &aTmpDir.pData );
+
+ do
+ {
+ OUString aDir = aTmpDir + "/psp" + OUString::number(nRand);
+ if( osl::Directory::create( aDir ) == osl::FileBase::E_None )
+ {
+ osl::File::setAttributes( aDir,
+ osl_File_Attribute_OwnWrite
+ | osl_File_Attribute_OwnRead
+ | osl_File_Attribute_OwnExe );
+ return aDir;
+ }
+ nRand++;
+ } while( nRand );
+ return OUString();
+}
+
+PrinterJob::~PrinterJob ()
+{
+ maPageVector.clear();
+ maHeaderVector.clear();
+
+ // mpJobHeader->remove();
+ mpJobHeader.reset();
+ // mpJobTrailer->remove();
+ mpJobTrailer.reset();
+
+ // XXX should really call osl::remove routines
+ if( !maSpoolDirName.isEmpty() )
+ removeSpoolDir (maSpoolDirName);
+
+ // osl::Directory::remove (maSpoolDirName);
+}
+
+static void WriteLocalTimePS( osl::File *rFile )
+{
+ TimeValue aStartTime, tLocal;
+ oslDateTime date_time;
+ if (osl_getSystemTime( &aStartTime ) &&
+ osl_getLocalTimeFromSystemTime( &aStartTime, &tLocal ) &&
+ osl_getDateTimeFromTimeValue( &tLocal, &date_time ))
+ {
+ char ar[ 256 ];
+ snprintf(
+ ar, sizeof (ar),
+ "%04d-%02d-%02d %02d:%02d:%02d ",
+ date_time.Year, date_time.Month, date_time.Day,
+ date_time.Hours, date_time.Minutes, date_time.Seconds );
+ WritePS( rFile, ar );
+ }
+ else
+ WritePS( rFile, "Unknown-Time" );
+}
+
+static bool isAscii( const OUString& rStr )
+{
+ sal_Int32 nLen = rStr.getLength();
+ for( sal_Int32 i = 0; i < nLen; i++ )
+ if( rStr[i] > 127 )
+ return false;
+ return true;
+}
+
+bool
+PrinterJob::StartJob (
+ const OUString& rFileName,
+ int nMode,
+ const OUString& rJobName,
+ std::u16string_view rAppName,
+ const JobData& rSetupData,
+ PrinterGfx* pGraphics,
+ bool bIsQuickJob
+ )
+{
+ m_bQuickJob = bIsQuickJob;
+ mnMaxWidthPt = mnMaxHeightPt = 0;
+ mnLandscapes = mnPortraits = 0;
+ m_pGraphics = pGraphics;
+ InitPaperSize (rSetupData);
+
+ // create file container for document header and trailer
+ maFileName = rFileName;
+ mnFileMode = nMode;
+ maSpoolDirName = createSpoolDir ();
+ maJobTitle = rJobName;
+
+ OUString aExt(".ps");
+ mpJobHeader = CreateSpoolFile (u"psp_head", aExt);
+ mpJobTrailer = CreateSpoolFile (u"psp_tail", aExt);
+ if( ! (mpJobHeader && mpJobTrailer) ) // existing files are removed in destructor
+ return false;
+
+ // write document header according to Document Structuring Conventions (DSC)
+ WritePS (mpJobHeader.get(),
+ "%!PS-Adobe-3.0\n"
+ "%%BoundingBox: (atend)\n" );
+
+ // Creator (this application)
+ OUString aFilterWS = WhitespaceToSpace( rAppName, false );
+ WritePS (mpJobHeader.get(), "%%Creator: (");
+ WritePS (mpJobHeader.get(), aFilterWS);
+ WritePS (mpJobHeader.get(), ")\n");
+
+ // For (user name)
+ osl::Security aSecurity;
+ OUString aUserName;
+ if( aSecurity.getUserName( aUserName ) )
+ {
+ WritePS (mpJobHeader.get(), "%%For: (");
+ WritePS (mpJobHeader.get(), aUserName);
+ WritePS (mpJobHeader.get(), ")\n");
+ }
+
+ // Creation Date (locale independent local time)
+ WritePS (mpJobHeader.get(), "%%CreationDate: (");
+ WriteLocalTimePS (mpJobHeader.get());
+ WritePS (mpJobHeader.get(), ")\n");
+
+ // Document Title
+ /* #i74335#
+ * The title should be clean ascii; rJobName however may
+ * contain any Unicode character. So implement the following
+ * algorithm:
+ * use rJobName, if it contains only ascii
+ * use the filename, if it contains only ascii
+ * else omit %%Title
+ */
+ aFilterWS = WhitespaceToSpace( rJobName, false );
+ OUString aTitle( aFilterWS );
+ if( ! isAscii( aTitle ) )
+ {
+ aTitle = WhitespaceToSpace( rFileName.subView(rFileName.lastIndexOf('/')+1), false );
+ if( ! isAscii( aTitle ) )
+ aTitle.clear();
+ }
+
+ maJobTitle = aFilterWS;
+ if( !aTitle.isEmpty() )
+ {
+ WritePS (mpJobHeader.get(), "%%Title: (");
+ WritePS (mpJobHeader.get(), aTitle);
+ WritePS (mpJobHeader.get(), ")\n");
+ }
+
+ // Language Level
+ OStringBuffer pLevel;
+ getValueOf(GetPostscriptLevel(&rSetupData), pLevel);
+ pLevel.append('\n');
+ WritePS (mpJobHeader.get(), "%%LanguageLevel: ");
+ WritePS (mpJobHeader.get(), pLevel.makeStringAndClear());
+
+ // Other
+ WritePS (mpJobHeader.get(), "%%DocumentData: Clean7Bit\n");
+ WritePS (mpJobHeader.get(), "%%Pages: (atend)\n");
+ WritePS (mpJobHeader.get(), "%%Orientation: (atend)\n");
+ WritePS (mpJobHeader.get(), "%%PageOrder: Ascend\n");
+ WritePS (mpJobHeader.get(), "%%EndComments\n");
+
+ // write Prolog
+ writeProlog (mpJobHeader.get(), rSetupData);
+
+ // mark last job setup as not set
+ m_aLastJobData.m_pParser = nullptr;
+ m_aLastJobData.m_aContext.setParser( nullptr );
+
+ return true;
+}
+
+bool
+PrinterJob::EndJob()
+{
+ // no pages ? that really means no print job
+ if( maPageVector.empty() )
+ return false;
+
+ // write document setup (done here because it
+ // includes the accumulated fonts
+ if( mpJobHeader )
+ writeSetup( mpJobHeader.get(), m_aDocumentJobData );
+ m_pGraphics->OnEndJob();
+ if( ! (mpJobHeader && mpJobTrailer) )
+ return false;
+
+ // write document trailer according to Document Structuring Conventions (DSC)
+ OStringBuffer aTrailer(512);
+ aTrailer.append( "%%Trailer\n" );
+ aTrailer.append( "%%BoundingBox: 0 0 " );
+ aTrailer.append( static_cast<sal_Int32>(mnMaxWidthPt) );
+ aTrailer.append( " " );
+ aTrailer.append( static_cast<sal_Int32>(mnMaxHeightPt) );
+ if( mnLandscapes > mnPortraits )
+ aTrailer.append("\n%%Orientation: Landscape");
+ else
+ aTrailer.append("\n%%Orientation: Portrait");
+ aTrailer.append( "\n%%Pages: " );
+ aTrailer.append( static_cast<sal_Int32>(maPageVector.size()) );
+ aTrailer.append( "\n%%EOF\n" );
+ WritePS (mpJobTrailer.get(), aTrailer.getStr());
+
+ /*
+ * spool the set of files to their final destination, this is U**X dependent
+ */
+
+ FILE* pDestFILE = nullptr;
+
+ /* create a destination either as file or as a pipe */
+ bool bSpoolToFile = !maFileName.isEmpty();
+ if (bSpoolToFile)
+ {
+ const OString aFileName = OUStringToOString (maFileName,
+ osl_getThreadTextEncoding());
+ if( mnFileMode )
+ {
+ int nFile = open( aFileName.getStr(), O_CREAT | O_EXCL | O_RDWR, mnFileMode );
+ if( nFile != -1 )
+ {
+ pDestFILE = fdopen( nFile, "w" );
+ if( pDestFILE == nullptr )
+ {
+ close( nFile );
+ unlink( aFileName.getStr() );
+ return false;
+ }
+ }
+ else
+ {
+ (void)chmod( aFileName.getStr(), mnFileMode );
+ }
+ }
+ if (pDestFILE == nullptr)
+ pDestFILE = fopen (aFileName.getStr(), "w");
+
+ if (pDestFILE == nullptr)
+ return false;
+ }
+ else
+ {
+ PrinterInfoManager& rPrinterInfoManager = PrinterInfoManager::get ();
+ pDestFILE = rPrinterInfoManager.startSpool( m_aLastJobData.m_aPrinterName, m_bQuickJob );
+ if (pDestFILE == nullptr)
+ return false;
+ }
+
+ /* spool the document parts to the destination */
+
+ unsigned char pBuffer[ nBLOCKSIZE ];
+
+ AppendPS (pDestFILE, mpJobHeader.get(), pBuffer);
+ mpJobHeader->close();
+
+ bool bSuccess = true;
+ std::vector< std::unique_ptr<osl::File> >::iterator pPageBody;
+ std::vector< std::unique_ptr<osl::File> >::iterator pPageHead;
+ for (pPageBody = maPageVector.begin(), pPageHead = maHeaderVector.begin();
+ pPageBody != maPageVector.end() && pPageHead != maHeaderVector.end();
+ ++pPageBody, ++pPageHead)
+ {
+ if( *pPageHead )
+ {
+ osl::File::RC nError = (*pPageHead)->open(osl_File_OpenFlag_Read);
+ if (nError == osl::File::E_None)
+ {
+ AppendPS (pDestFILE, pPageHead->get(), pBuffer);
+ (*pPageHead)->close();
+ }
+ }
+ else
+ bSuccess = false;
+ if( *pPageBody )
+ {
+ osl::File::RC nError = (*pPageBody)->open(osl_File_OpenFlag_Read);
+ if (nError == osl::File::E_None)
+ {
+ AppendPS (pDestFILE, pPageBody->get(), pBuffer);
+ (*pPageBody)->close();
+ }
+ }
+ else
+ bSuccess = false;
+ }
+
+ AppendPS (pDestFILE, mpJobTrailer.get(), pBuffer);
+ mpJobTrailer->close();
+
+ /* well done */
+
+ if (bSpoolToFile)
+ fclose (pDestFILE);
+ else
+ {
+ PrinterInfoManager& rPrinterInfoManager = PrinterInfoManager::get();
+ if (!rPrinterInfoManager.endSpool( m_aLastJobData.m_aPrinterName,
+ maJobTitle, pDestFILE, m_aDocumentJobData, true, OUString()))
+ {
+ bSuccess = false;
+ }
+ }
+
+ return bSuccess;
+}
+
+void
+PrinterJob::InitPaperSize (const JobData& rJobSetup)
+{
+ int nRes = rJobSetup.m_aContext.getRenderResolution ();
+
+ OUString aPaper;
+ int nWidth, nHeight;
+ rJobSetup.m_aContext.getPageSize (aPaper, nWidth, nHeight);
+
+ int nLeft = 0, nRight = 0, nUpper = 0, nLower = 0;
+ const PPDParser* pParser = rJobSetup.m_aContext.getParser();
+ if (pParser != nullptr)
+ pParser->getMargins (aPaper, nLeft, nRight, nUpper, nLower);
+
+ mnResolution = nRes;
+
+ mnWidthPt = nWidth;
+ mnHeightPt = nHeight;
+
+ if( mnWidthPt > mnMaxWidthPt )
+ mnMaxWidthPt = mnWidthPt;
+ if( mnHeightPt > mnMaxHeightPt )
+ mnMaxHeightPt = mnHeightPt;
+
+ mnLMarginPt = nLeft;
+ mnRMarginPt = nRight;
+ mnTMarginPt = nUpper;
+ mnBMarginPt = nLower;
+
+ mfXScale = 72.0 / static_cast<double>(mnResolution);
+ mfYScale = -1.0 * 72.0 / static_cast<double>(mnResolution);
+}
+
+void
+PrinterJob::StartPage (const JobData& rJobSetup)
+{
+ InitPaperSize (rJobSetup);
+
+ OUString aPageNo = OUString::number (static_cast<sal_Int32>(maPageVector.size())+1); // sequential page number must start with 1
+ OUString aExt = aPageNo + ".ps";
+
+ maHeaderVector.push_back( CreateSpoolFile ( u"psp_pghead", aExt) );
+ maPageVector.push_back( CreateSpoolFile ( u"psp_pgbody", aExt) );
+
+ osl::File* pPageHeader = maHeaderVector.back().get();
+ osl::File* pPageBody = maPageVector.back().get();
+
+ if( ! (pPageHeader && pPageBody) )
+ return;
+
+ // write page header according to Document Structuring Conventions (DSC)
+ WritePS (pPageHeader, "%%Page: ");
+ WritePS (pPageHeader, aPageNo);
+ WritePS (pPageHeader, " ");
+ WritePS (pPageHeader, aPageNo);
+ WritePS (pPageHeader, "\n");
+
+ if( rJobSetup.m_eOrientation == orientation::Landscape )
+ {
+ WritePS (pPageHeader, "%%PageOrientation: Landscape\n");
+ mnLandscapes++;
+ }
+ else
+ {
+ WritePS (pPageHeader, "%%PageOrientation: Portrait\n");
+ mnPortraits++;
+ }
+
+ OStringBuffer pBBox;
+
+ psp::appendStr ("%%PageBoundingBox: ", pBBox);
+ psp::getValueOf (mnLMarginPt, pBBox);
+ psp::appendStr (" ", pBBox);
+ psp::getValueOf (mnBMarginPt, pBBox);
+ psp::appendStr (" ", pBBox);
+ psp::getValueOf (mnWidthPt - mnRMarginPt, pBBox);
+ psp::appendStr (" ", pBBox);
+ psp::getValueOf (mnHeightPt - mnTMarginPt, pBBox);
+ psp::appendStr ("\n", pBBox);
+
+ WritePS (pPageHeader, pBBox.makeStringAndClear());
+
+ /* #i7262# #i65491# write setup only before first page
+ * (to %%Begin(End)Setup, instead of %%Begin(End)PageSetup)
+ * don't do this in StartJob since the jobsetup there may be
+ * different.
+ */
+ bool bWriteFeatures = true;
+ if( 1 == maPageVector.size() )
+ {
+ m_aDocumentJobData = rJobSetup;
+ bWriteFeatures = false;
+ }
+
+ if ( writePageSetup( pPageHeader, rJobSetup, bWriteFeatures ) )
+ {
+ m_aLastJobData = rJobSetup;
+ }
+}
+
+void
+PrinterJob::EndPage ()
+{
+ osl::File* pPageHeader = maHeaderVector.back().get();
+ osl::File* pPageBody = maPageVector.back().get();
+
+ if( ! (pPageBody && pPageHeader) )
+ return;
+
+ // copy page to paper and write page trailer according to DSC
+
+ OStringBuffer pTrailer;
+ psp::appendStr ("grestore grestore\n", pTrailer);
+ psp::appendStr ("showpage\n", pTrailer);
+ psp::appendStr ("%%PageTrailer\n\n", pTrailer);
+ WritePS (pPageBody, pTrailer.makeStringAndClear());
+
+ // this page is done for now, close it to avoid having too many open fd's
+
+ pPageHeader->close();
+ pPageBody->close();
+}
+
+namespace {
+
+struct less_ppd_key
+{
+ bool operator()(const PPDKey* left, const PPDKey* right)
+ { return left->getOrderDependency() < right->getOrderDependency(); }
+};
+
+}
+
+static bool writeFeature( osl::File* pFile, const PPDKey* pKey, const PPDValue* pValue, bool bUseIncluseFeature )
+{
+ if( ! pKey || ! pValue )
+ return true;
+
+ OStringBuffer aFeature(256);
+ aFeature.append( "[{\n" );
+ if( bUseIncluseFeature )
+ aFeature.append( "%%IncludeFeature:" );
+ else
+ aFeature.append( "%%BeginFeature:" );
+ aFeature.append( " *" );
+ aFeature.append( OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US ) );
+ aFeature.append( ' ' );
+ aFeature.append( OUStringToOString( pValue->m_aOption, RTL_TEXTENCODING_ASCII_US ) );
+ if( !bUseIncluseFeature )
+ {
+ aFeature.append( '\n' );
+ aFeature.append( OUStringToOString( pValue->m_aValue, RTL_TEXTENCODING_ASCII_US ) );
+ aFeature.append( "\n%%EndFeature" );
+ }
+ aFeature.append( "\n} stopped cleartomark\n" );
+ sal_uInt64 nWritten = 0;
+ return !(pFile->write( aFeature.getStr(), aFeature.getLength(), nWritten )
+ || nWritten != static_cast<sal_uInt64>(aFeature.getLength()));
+}
+
+bool PrinterJob::writeFeatureList( osl::File* pFile, const JobData& rJob, bool bDocumentSetup ) const
+{
+ bool bSuccess = true;
+
+ // emit features ordered to OrderDependency
+ // ignore features that are set to default
+
+ // sanity check
+ if( rJob.m_pParser == rJob.m_aContext.getParser() &&
+ rJob.m_pParser &&
+ ( m_aLastJobData.m_pParser == rJob.m_pParser || m_aLastJobData.m_pParser == nullptr )
+ )
+ {
+ std::size_t i;
+ std::size_t nKeys = rJob.m_aContext.countValuesModified();
+ ::std::vector< const PPDKey* > aKeys( nKeys );
+ for( i = 0; i < nKeys; i++ )
+ aKeys[i] = rJob.m_aContext.getModifiedKey( i );
+ ::std::sort( aKeys.begin(), aKeys.end(), less_ppd_key() );
+
+ for( i = 0; i < nKeys && bSuccess; i++ )
+ {
+ const PPDKey* pKey = aKeys[i];
+ bool bEmit = false;
+ if( bDocumentSetup )
+ {
+ if( pKey->getSetupType() == PPDKey::SetupType::DocumentSetup )
+ bEmit = true;
+ }
+ if( pKey->getSetupType() == PPDKey::SetupType::PageSetup ||
+ pKey->getSetupType() == PPDKey::SetupType::AnySetup )
+ bEmit = true;
+ if( bEmit )
+ {
+ const PPDValue* pValue = rJob.m_aContext.getValue( pKey );
+ if( pValue
+ && pValue->m_eType == eInvocation
+ && ( m_aLastJobData.m_pParser == nullptr
+ || m_aLastJobData.m_aContext.getValue( pKey ) != pValue
+ || bDocumentSetup
+ )
+ )
+ {
+ // try to avoid PS level 2 feature commands if level is set to 1
+ if( GetPostscriptLevel( &rJob ) == 1 )
+ {
+ bool bHavePS2 =
+ ( pValue->m_aValue.indexOf( "<<" ) != -1 )
+ ||
+ ( pValue->m_aValue.indexOf( ">>" ) != -1 );
+ if( bHavePS2 )
+ continue;
+ }
+ bSuccess = writeFeature( pFile, pKey, pValue, PrinterInfoManager::get().getUseIncludeFeature() );
+ }
+ }
+ }
+ }
+ else
+ bSuccess = false;
+
+ return bSuccess;
+}
+
+bool PrinterJob::writePageSetup( osl::File* pFile, const JobData& rJob, bool bWriteFeatures )
+{
+ bool bSuccess = true;
+
+ WritePS (pFile, "%%BeginPageSetup\n%\n");
+ if ( bWriteFeatures )
+ bSuccess = writeFeatureList( pFile, rJob, false );
+ WritePS (pFile, "%%EndPageSetup\n");
+
+ OStringBuffer pTranslate;
+
+ if( rJob.m_eOrientation == orientation::Portrait )
+ {
+ psp::appendStr ("gsave\n[", pTranslate);
+ psp::getValueOfDouble ( pTranslate, mfXScale, 5);
+ psp::appendStr (" 0 0 ", pTranslate);
+ psp::getValueOfDouble ( pTranslate, mfYScale, 5);
+ psp::appendStr (" ", pTranslate);
+ psp::getValueOf (mnRMarginPt, pTranslate);
+ psp::appendStr (" ", pTranslate);
+ psp::getValueOf (mnHeightPt-mnTMarginPt,
+ pTranslate);
+ psp::appendStr ("] concat\ngsave\n",
+ pTranslate);
+ }
+ else
+ {
+ psp::appendStr ("gsave\n", pTranslate);
+ psp::appendStr ("[ 0 ", pTranslate);
+ psp::getValueOfDouble ( pTranslate, -mfYScale, 5);
+ psp::appendStr (" ", pTranslate);
+ psp::getValueOfDouble ( pTranslate, mfXScale, 5);
+ psp::appendStr (" 0 ", pTranslate );
+ psp::getValueOfDouble ( pTranslate, mnLMarginPt, 5 );
+ psp::appendStr (" ", pTranslate);
+ psp::getValueOf (mnBMarginPt, pTranslate );
+ psp::appendStr ("] concat\ngsave\n",
+ pTranslate);
+ }
+
+ WritePS (pFile, pTranslate.makeStringAndClear());
+
+ return bSuccess;
+}
+
+void PrinterJob::writeJobPatch( osl::File* pFile, const JobData& rJobData )
+{
+ if( ! PrinterInfoManager::get().getUseJobPatch() )
+ return;
+
+ const PPDKey* pKey = nullptr;
+
+ if( rJobData.m_pParser )
+ pKey = rJobData.m_pParser->getKey( "JobPatchFile" );
+ if( ! pKey )
+ return;
+
+ // order the patch files
+ // according to PPD spec the JobPatchFile options must be int
+ // and should be emitted in order
+ std::deque< sal_Int32 > patch_order;
+ int nValueCount = pKey->countValues();
+ for( int i = 0; i < nValueCount; i++ )
+ {
+ const PPDValue* pVal = pKey->getValue( i );
+ patch_order.push_back( pVal->m_aOption.toInt32() );
+ if( patch_order.back() == 0 && pVal->m_aOption != "0" )
+ {
+ WritePS( pFile, "% Warning: left out JobPatchFile option \"" );
+ OString aOption = OUStringToOString( pVal->m_aOption, RTL_TEXTENCODING_ASCII_US );
+ WritePS( pFile, aOption.getStr() );
+ WritePS( pFile,
+ "\"\n% as it violates the PPD spec;\n"
+ "% JobPatchFile options need to be numbered for ordering.\n" );
+ }
+ }
+
+ std::sort(patch_order.begin(), patch_order.end());
+ patch_order.erase(std::unique(patch_order.begin(), patch_order.end()), patch_order.end());
+
+ for (auto const& elem : patch_order)
+ {
+ // note: this discards patch files not adhering to the "int" scheme
+ // as there won't be a value for them
+ writeFeature( pFile, pKey, pKey->getValue( OUString::number(elem) ), false );
+ }
+}
+
+void PrinterJob::writeProlog (osl::File* pFile, const JobData& rJobData )
+{
+ WritePS( pFile, "%%BeginProlog\n" );
+
+ // JobPatchFile feature needs to be emitted at begin of prolog
+ writeJobPatch( pFile, rJobData );
+
+ static const char pProlog[] = {
+ "%%BeginResource: procset PSPrint-Prolog 1.0 0\n"
+ "/ISO1252Encoding [\n"
+ "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n"
+ "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n"
+ "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n"
+ "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n"
+ "/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle\n"
+ "/parenleft /parenright /asterisk /plus /comma /hyphen /period /slash\n"
+ "/zero /one /two /three /four /five /six /seven\n"
+ "/eight /nine /colon /semicolon /less /equal /greater /question\n"
+ "/at /A /B /C /D /E /F /G\n"
+ "/H /I /J /K /L /M /N /O\n"
+ "/P /Q /R /S /T /U /V /W\n"
+ "/X /Y /Z /bracketleft /backslash /bracketright /asciicircum /underscore\n"
+ "/grave /a /b /c /d /e /f /g\n"
+ "/h /i /j /k /l /m /n /o\n"
+ "/p /q /r /s /t /u /v /w\n"
+ "/x /y /z /braceleft /bar /braceright /asciitilde /unused\n"
+ "/Euro /unused /quotesinglbase /florin /quotedblbase /ellipsis /dagger /daggerdbl\n"
+ "/circumflex /perthousand /Scaron /guilsinglleft /OE /unused /Zcaron /unused\n"
+ "/unused /quoteleft /quoteright /quotedblleft /quotedblright /bullet /endash /emdash\n"
+ "/tilde /trademark /scaron /guilsinglright /oe /unused /zcaron /Ydieresis\n"
+ "/space /exclamdown /cent /sterling /currency /yen /brokenbar /section\n"
+ "/dieresis /copyright /ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron\n"
+ "/degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /periodcentered\n"
+ "/cedilla /onesuperior /ordmasculine /guillemotright /onequarter /onehalf /threequarters /questiondown\n"
+ "/Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla\n"
+ "/Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis\n"
+ "/Eth /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply\n"
+ "/Oslash /Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /germandbls\n"
+ "/agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla\n"
+ "/egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis\n"
+ "/eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide\n"
+ "/oslash /ugrave /uacute /ucircumflex /udieresis /yacute /thorn /ydieresis] def\n"
+ "\n"
+ "/psp_definefont { exch dup findfont dup length dict begin { 1 index /FID ne\n"
+ "{ def } { pop pop } ifelse } forall /Encoding 3 -1 roll def\n"
+ "currentdict end exch pop definefont pop } def\n"
+ "\n"
+ "/pathdict dup 8 dict def load begin\n"
+ "/rcmd { { currentfile 1 string readstring pop 0 get dup 32 gt { exit }\n"
+ "{ pop } ifelse } loop dup 126 eq { pop exit } if 65 sub dup 16#3 and 1\n"
+ "add exch dup 16#C and -2 bitshift 16#3 and 1 add exch 16#10 and 16#10\n"
+ "eq 3 1 roll exch } def\n"
+ "/rhex { dup 1 sub exch currentfile exch string readhexstring pop dup 0\n"
+ "get dup 16#80 and 16#80 eq dup 3 1 roll { 16#7f and } if 2 index 0 3\n"
+ "-1 roll put 3 1 roll 0 0 1 5 -1 roll { 2 index exch get add 256 mul }\n"
+ "for 256 div exch pop exch { neg } if } def\n"
+ "/xcmd { rcmd exch rhex exch rhex exch 5 -1 roll add exch 4 -1 roll add\n"
+ "1 index 1 index 5 -1 roll { moveto } { lineto } ifelse } def end\n"
+ "/readpath { 0 0 pathdict begin { xcmd } loop end pop pop } def\n"
+ "\n"
+ "systemdict /languagelevel known not {\n"
+ "/xshow { exch dup length 0 1 3 -1 roll 1 sub { dup 3 index exch get\n"
+ "exch 2 index exch get 1 string dup 0 4 -1 roll put currentpoint 3 -1\n"
+ "roll show moveto 0 rmoveto } for pop pop } def\n"
+ "/rectangle { 4 -2 roll moveto 1 index 0 rlineto 0 exch rlineto neg 0\n"
+ "rlineto closepath } def\n"
+ "/rectfill { rectangle fill } def\n"
+ "/rectstroke { rectangle stroke } def } if\n"
+ "/bshow { currentlinewidth 3 1 roll currentpoint 3 index show moveto\n"
+ "setlinewidth false charpath stroke setlinewidth } def\n"
+ "/bxshow { currentlinewidth 4 1 roll setlinewidth exch dup length 1 sub\n"
+ "0 1 3 -1 roll { 1 string 2 index 2 index get 1 index exch 0 exch put dup\n"
+ "currentpoint 3 -1 roll show moveto currentpoint 3 -1 roll false charpath\n"
+ "stroke moveto 2 index exch get 0 rmoveto } for pop pop setlinewidth } def\n"
+ "\n"
+ "/psp_lzwfilter { currentfile /ASCII85Decode filter /LZWDecode filter } def\n"
+ "/psp_ascii85filter { currentfile /ASCII85Decode filter } def\n"
+ "/psp_lzwstring { psp_lzwfilter 1024 string readstring } def\n"
+ "/psp_ascii85string { psp_ascii85filter 1024 string readstring } def\n"
+ "/psp_imagedict {\n"
+ "/psp_bitspercomponent { 3 eq { 1 }{ 8 } ifelse } def\n"
+ "/psp_decodearray { [ [0 1 0 1 0 1] [0 255] [0 1] [0 255] ] exch get }\n"
+ "def 7 dict dup\n"
+ "/ImageType 1 put dup\n"
+ "/Width 7 -1 roll put dup\n"
+ "/Height 5 index put dup\n"
+ "/BitsPerComponent 4 index psp_bitspercomponent put dup\n"
+ "/Decode 5 -1 roll psp_decodearray put dup\n"
+ "/ImageMatrix [1 0 0 1 0 0] dup 5 8 -1 roll put put dup\n"
+ "/DataSource 4 -1 roll 1 eq { psp_lzwfilter } { psp_ascii85filter } ifelse put\n"
+ "} def\n"
+ "%%EndResource\n"
+ "%%EndProlog\n"
+ };
+ WritePS (pFile, pProlog);
+}
+
+bool PrinterJob::writeSetup( osl::File* pFile, const JobData& rJob )
+{
+ WritePS (pFile, "%%BeginSetup\n%\n");
+
+ // download fonts
+ std::vector< OString > aFonts;
+ m_pGraphics->writeResources( pFile, aFonts );
+
+ if( !aFonts.empty() )
+ {
+ std::vector< OString >::const_iterator it = aFonts.begin();
+ OStringBuffer aLine( 256 );
+ aLine.append( "%%DocumentSuppliedResources: font " );
+ aLine.append( *it );
+ aLine.append( "\n" );
+ WritePS ( pFile, aLine.getStr() );
+ while( (++it) != aFonts.end() )
+ {
+ aLine.setLength(0);
+ aLine.append( "%%+ font " );
+ aLine.append( *it );
+ aLine.append( "\n" );
+ WritePS ( pFile, aLine.getStr() );
+ }
+ }
+
+ bool bSuccess = true;
+ // in case of external print dialog the number of copies is prepended
+ // to the job, let us not complicate things by emitting our own copy count
+ bool bExternalDialog = PrinterInfoManager::get().checkFeatureToken( GetPrinterName(), "external_dialog" );
+ if( ! bExternalDialog && rJob.m_nCopies > 1 )
+ {
+ // setup code
+ OString aLine = "/#copies " +
+ OString::number(static_cast<sal_Int32>(rJob.m_nCopies)) +
+ " def\n";
+ sal_uInt64 nWritten = 0;
+ bSuccess = !(pFile->write(aLine.getStr(), aLine.getLength(), nWritten)
+ || nWritten != static_cast<sal_uInt64>(aLine.getLength()));
+
+ if( bSuccess && GetPostscriptLevel( &rJob ) >= 2 )
+ WritePS (pFile, "<< /NumCopies null /Policies << /NumCopies 1 >> >> setpagedevice\n" );
+ }
+
+ bool bFeatureSuccess = writeFeatureList( pFile, rJob, true );
+
+ WritePS (pFile, "%%EndSetup\n");
+
+ return bSuccess && bFeatureSuccess;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/prtsetup.cxx b/vcl/unx/generic/print/prtsetup.cxx
new file mode 100644
index 000000000..56ee475e7
--- /dev/null
+++ b/vcl/unx/generic/print/prtsetup.cxx
@@ -0,0 +1,516 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "prtsetup.hxx"
+#include <svdata.hxx>
+#include <strings.hrc>
+
+#include <officecfg/Office/Common.hxx>
+
+using namespace psp;
+
+void RTSDialog::insertAllPPDValues(weld::ComboBox& rBox, const PPDParser* pParser, const PPDKey* pKey )
+{
+ if( ! pKey || ! pParser )
+ return;
+
+ const PPDValue* pValue = nullptr;
+ OUString aOptionText;
+
+ for (int i = 0; i < pKey->countValues(); ++i)
+ {
+ pValue = pKey->getValue( i );
+ if (pValue->m_bCustomOption)
+ continue;
+ aOptionText = pParser->translateOption( pKey->getKey(), pValue->m_aOption) ;
+
+ OUString sId(weld::toId(pValue));
+ int nCurrentPos = rBox.find_id(sId);
+ if( m_aJobData.m_aContext.checkConstraints( pKey, pValue ) )
+ {
+ if (nCurrentPos == -1)
+ rBox.append(sId, aOptionText);
+ }
+ else
+ {
+ if (nCurrentPos != -1)
+ rBox.remove(nCurrentPos);
+ }
+ }
+ pValue = m_aJobData.m_aContext.getValue( pKey );
+ if (pValue && !pValue->m_bCustomOption)
+ {
+ OUString sId(weld::toId(pValue));
+ int nPos = rBox.find_id(sId);
+ if (nPos != -1)
+ rBox.set_active(nPos);
+ }
+}
+
+/*
+ * RTSDialog
+ */
+
+RTSDialog::RTSDialog(const PrinterInfo& rJobData, weld::Window* pParent)
+ : GenericDialogController(pParent, "vcl/ui/printerpropertiesdialog.ui", "PrinterPropertiesDialog")
+ , m_aJobData(rJobData)
+ , m_bDataModified(false)
+ , m_xTabControl(m_xBuilder->weld_notebook("tabcontrol"))
+ , m_xOKButton(m_xBuilder->weld_button("ok"))
+ , m_xCancelButton(m_xBuilder->weld_button("cancel"))
+ , m_xPaperPage(new RTSPaperPage(m_xTabControl->get_page("paper"), this))
+ , m_xDevicePage(new RTSDevicePage(m_xTabControl->get_page("device"), this))
+{
+ OUString aTitle(m_xDialog->get_title());
+ m_xDialog->set_title(aTitle.replaceAll("%s", m_aJobData.m_aPrinterName));
+
+ m_xTabControl->connect_enter_page( LINK( this, RTSDialog, ActivatePage ) );
+ m_xOKButton->connect_clicked( LINK( this, RTSDialog, ClickButton ) );
+ m_xCancelButton->connect_clicked( LINK( this, RTSDialog, ClickButton ) );
+ ActivatePage(m_xTabControl->get_current_page_ident());
+}
+
+RTSDialog::~RTSDialog()
+{
+}
+
+IMPL_LINK(RTSDialog, ActivatePage, const OString&, rPage, void)
+{
+ if (rPage == "paper")
+ m_xPaperPage->update();
+}
+
+IMPL_LINK( RTSDialog, ClickButton, weld::Button&, rButton, void )
+{
+ if (&rButton == m_xOKButton.get())
+ {
+ // refresh the changed values
+ if (m_xPaperPage)
+ {
+ // orientation
+ m_aJobData.m_eOrientation = m_xPaperPage->getOrientation() == 0 ?
+ orientation::Portrait : orientation::Landscape;
+ // assume use of paper size from printer setup if the user
+ // got here via File > Printer Settings ...
+ if ( m_aJobData.meSetupMode == PrinterSetupMode::DocumentGlobal )
+ m_aJobData.m_bPapersizeFromSetup = true;
+ }
+ if( m_xDevicePage )
+ {
+ m_aJobData.m_nColorDepth = m_xDevicePage->getDepth();
+ m_aJobData.m_nColorDevice = m_xDevicePage->getColorDevice();
+ m_aJobData.m_nPSLevel = m_xDevicePage->getLevel();
+ m_aJobData.m_nPDFDevice = m_xDevicePage->getPDFDevice();
+ }
+ m_xDialog->response(RET_OK);
+ }
+ else if (&rButton == m_xCancelButton.get())
+ m_xDialog->response(RET_CANCEL);
+}
+
+/*
+ * RTSPaperPage
+ */
+
+RTSPaperPage::RTSPaperPage(weld::Widget* pPage, RTSDialog* pDialog)
+ : m_xBuilder(Application::CreateBuilder(pPage, "vcl/ui/printerpaperpage.ui"))
+ , m_pParent(pDialog)
+ , m_xContainer(m_xBuilder->weld_widget("PrinterPaperPage"))
+ , m_xCbFromSetup(m_xBuilder->weld_check_button("papersizefromsetup"))
+ , m_xPaperText(m_xBuilder->weld_label("paperft"))
+ , m_xPaperBox(m_xBuilder->weld_combo_box("paperlb"))
+ , m_xOrientText(m_xBuilder->weld_label("orientft"))
+ , m_xOrientBox(m_xBuilder->weld_combo_box("orientlb"))
+ , m_xDuplexText(m_xBuilder->weld_label("duplexft"))
+ , m_xDuplexBox(m_xBuilder->weld_combo_box("duplexlb"))
+ , m_xSlotText(m_xBuilder->weld_label("slotft"))
+ , m_xSlotBox(m_xBuilder->weld_combo_box("slotlb"))
+{
+ //PrinterPaperPage
+ m_xPaperBox->connect_changed( LINK( this, RTSPaperPage, SelectHdl ) );
+ m_xOrientBox->connect_changed( LINK( this, RTSPaperPage, SelectHdl ) );
+ m_xDuplexBox->connect_changed( LINK( this, RTSPaperPage, SelectHdl ) );
+ m_xSlotBox->connect_changed( LINK( this, RTSPaperPage, SelectHdl ) );
+ m_xCbFromSetup->connect_toggled( LINK( this, RTSPaperPage, CheckBoxHdl ) );
+
+ update();
+}
+
+RTSPaperPage::~RTSPaperPage()
+{
+}
+
+void RTSPaperPage::update()
+{
+ const PPDKey* pKey = nullptr;
+
+ // orientation
+ m_xOrientBox->set_active(m_pParent->m_aJobData.m_eOrientation == orientation::Portrait ? 0 : 1);
+
+ // duplex
+ if( m_pParent->m_aJobData.m_pParser &&
+ (pKey = m_pParent->m_aJobData.m_pParser->getKey( "Duplex" )) )
+ {
+ m_pParent->insertAllPPDValues( *m_xDuplexBox, m_pParent->m_aJobData.m_pParser, pKey );
+ }
+ else
+ {
+ m_xDuplexText->set_sensitive( false );
+ m_xDuplexBox->set_sensitive( false );
+ }
+
+ // paper
+ if( m_pParent->m_aJobData.m_pParser &&
+ (pKey = m_pParent->m_aJobData.m_pParser->getKey( "PageSize" )) )
+ {
+ m_pParent->insertAllPPDValues( *m_xPaperBox, m_pParent->m_aJobData.m_pParser, pKey );
+ }
+ else
+ {
+ m_xPaperText->set_sensitive( false );
+ m_xPaperBox->set_sensitive( false );
+ }
+
+ // input slots
+ if( m_pParent->m_aJobData.m_pParser &&
+ (pKey = m_pParent->m_aJobData.m_pParser->getKey( "InputSlot" )) )
+ {
+ m_pParent->insertAllPPDValues( *m_xSlotBox, m_pParent->m_aJobData.m_pParser, pKey );
+ }
+ else
+ {
+ m_xSlotText->set_sensitive( false );
+ m_xSlotBox->set_sensitive( false );
+ }
+
+ if ( m_pParent->m_aJobData.meSetupMode != PrinterSetupMode::SingleJob )
+ return;
+
+ m_xCbFromSetup->show();
+
+ if ( m_pParent->m_aJobData.m_bPapersizeFromSetup )
+ m_xCbFromSetup->set_active(m_pParent->m_aJobData.m_bPapersizeFromSetup);
+ // disable those, unless user wants to use papersize from printer prefs
+ // as they have no influence on what's going to be printed anyway
+ else
+ {
+ m_xPaperText->set_sensitive( false );
+ m_xPaperBox->set_sensitive( false );
+ m_xOrientText->set_sensitive( false );
+ m_xOrientBox->set_sensitive( false );
+ }
+}
+
+IMPL_LINK( RTSPaperPage, SelectHdl, weld::ComboBox&, rBox, void )
+{
+ const PPDKey* pKey = nullptr;
+ if( &rBox == m_xPaperBox.get() )
+ {
+ if( m_pParent->m_aJobData.m_pParser )
+ pKey = m_pParent->m_aJobData.m_pParser->getKey( "PageSize" );
+ }
+ else if( &rBox == m_xDuplexBox.get() )
+ {
+ if( m_pParent->m_aJobData.m_pParser )
+ pKey = m_pParent->m_aJobData.m_pParser->getKey( "Duplex" );
+ }
+ else if( &rBox == m_xSlotBox.get() )
+ {
+ if( m_pParent->m_aJobData.m_pParser )
+ pKey = m_pParent->m_aJobData.m_pParser->getKey( "InputSlot" );
+ }
+ else if( &rBox == m_xOrientBox.get() )
+ {
+ m_pParent->m_aJobData.m_eOrientation = m_xOrientBox->get_active() == 0 ? orientation::Portrait : orientation::Landscape;
+ }
+ if( pKey )
+ {
+ PPDValue* pValue = weld::fromId<PPDValue*>(rBox.get_active_id());
+ m_pParent->m_aJobData.m_aContext.setValue( pKey, pValue );
+ update();
+ }
+
+ m_pParent->SetDataModified( true );
+}
+
+IMPL_LINK_NOARG(RTSPaperPage, CheckBoxHdl, weld::Toggleable&, void)
+{
+ bool bFromSetup = m_xCbFromSetup->get_active();
+ m_pParent->m_aJobData.m_bPapersizeFromSetup = bFromSetup;
+ m_xPaperText->set_sensitive(bFromSetup);
+ m_xPaperBox->set_sensitive(bFromSetup);
+ m_xOrientText->set_sensitive(bFromSetup);
+ m_xOrientBox->set_sensitive(bFromSetup);
+ m_pParent->SetDataModified(true);
+}
+
+/*
+ * RTSDevicePage
+ */
+RTSDevicePage::RTSDevicePage(weld::Widget* pPage, RTSDialog* pParent)
+ : m_xBuilder(Application::CreateBuilder(pPage, "vcl/ui/printerdevicepage.ui"))
+ , m_pCustomValue(nullptr)
+ , m_pParent(pParent)
+ , m_xContainer(m_xBuilder->weld_widget("PrinterDevicePage"))
+ , m_xPPDKeyBox(m_xBuilder->weld_tree_view("options"))
+ , m_xPPDValueBox(m_xBuilder->weld_tree_view("values"))
+ , m_xCustomEdit(m_xBuilder->weld_entry("custom"))
+ , m_xLevelBox(m_xBuilder->weld_combo_box("level"))
+ , m_xSpaceBox(m_xBuilder->weld_combo_box("colorspace"))
+ , m_xDepthBox(m_xBuilder->weld_combo_box("colordepth"))
+ , m_aReselectCustomIdle("RTSDevicePage m_aReselectCustomIdle")
+{
+ m_aReselectCustomIdle.SetInvokeHandler(LINK(this, RTSDevicePage, ImplHandleReselectHdl));
+
+ m_xPPDKeyBox->set_size_request(m_xPPDKeyBox->get_approximate_digit_width() * 32,
+ m_xPPDKeyBox->get_height_rows(12));
+
+ m_xCustomEdit->connect_changed(LINK(this, RTSDevicePage, ModifyHdl));
+
+ m_xPPDKeyBox->connect_changed( LINK( this, RTSDevicePage, SelectHdl ) );
+ m_xPPDValueBox->connect_changed( LINK( this, RTSDevicePage, SelectHdl ) );
+
+ m_xLevelBox->connect_changed(LINK(this, RTSDevicePage, ComboChangedHdl));
+ m_xSpaceBox->connect_changed(LINK(this, RTSDevicePage, ComboChangedHdl));
+ m_xDepthBox->connect_changed(LINK(this, RTSDevicePage, ComboChangedHdl));
+
+ switch( m_pParent->m_aJobData.m_nColorDevice )
+ {
+ case 0:
+ m_xSpaceBox->set_active(0);
+ break;
+ case 1:
+ m_xSpaceBox->set_active(1);
+ break;
+ case -1:
+ m_xSpaceBox->set_active(2);
+ break;
+ }
+
+ sal_Int32 nLevelEntryData = 0; //automatic
+ if( m_pParent->m_aJobData.m_nPDFDevice == 2 ) //explicit PDF
+ nLevelEntryData = 10;
+ else if (m_pParent->m_aJobData.m_nPSLevel > 0) //explicit PS Level
+ nLevelEntryData = m_pParent->m_aJobData.m_nPSLevel+1;
+ else if (m_pParent->m_aJobData.m_nPDFDevice == 1) //automatically PDF
+ nLevelEntryData = 0;
+ else if (m_pParent->m_aJobData.m_nPDFDevice == -1) //explicitly PS from driver
+ nLevelEntryData = 1;
+
+ bool bAutoIsPDF = officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::get();
+
+ assert(nLevelEntryData != 0
+ || "Generic Printer" == m_pParent->m_aJobData.m_aPrinterName
+ || int(bAutoIsPDF) == m_pParent->m_aJobData.m_nPDFDevice);
+
+ OUString sStr = m_xLevelBox->get_text(0);
+ OUString sId = m_xLevelBox->get_id(0);
+ m_xLevelBox->insert(0, sStr.replaceAll("%s", bAutoIsPDF ? m_xLevelBox->get_text(5) : m_xLevelBox->get_text(1)), &sId, nullptr, nullptr);
+ m_xLevelBox->remove(1);
+
+ for (int i = 0; i < m_xLevelBox->get_count(); ++i)
+ {
+ if (m_xLevelBox->get_id(i).toInt32() == nLevelEntryData)
+ {
+ m_xLevelBox->set_active(i);
+ break;
+ }
+ }
+
+ if (m_pParent->m_aJobData.m_nColorDepth == 8)
+ m_xDepthBox->set_active(0);
+ else if (m_pParent->m_aJobData.m_nColorDepth == 24)
+ m_xDepthBox->set_active(1);
+
+ // fill ppd boxes
+ if( !m_pParent->m_aJobData.m_pParser )
+ return;
+
+ for( int i = 0; i < m_pParent->m_aJobData.m_pParser->getKeys(); i++ )
+ {
+ const PPDKey* pKey = m_pParent->m_aJobData.m_pParser->getKey( i );
+
+ // skip options already shown somewhere else
+ // also skip options from the "InstallableOptions" PPD group
+ // Options in that group define hardware features that are not
+ // job-specific and should better be handled in the system-wide
+ // printer configuration. Keyword is defined in PPD specification
+ // (version 4.3), section 5.4.
+ if( pKey->isUIKey() &&
+ pKey->getKey() != "PageSize" &&
+ pKey->getKey() != "InputSlot" &&
+ pKey->getKey() != "PageRegion" &&
+ pKey->getKey() != "Duplex" &&
+ pKey->getGroup() != "InstallableOptions")
+ {
+ OUString aEntry( m_pParent->m_aJobData.m_pParser->translateKey( pKey->getKey() ) );
+ m_xPPDKeyBox->append(weld::toId(pKey), aEntry);
+ }
+ }
+}
+
+RTSDevicePage::~RTSDevicePage()
+{
+}
+
+sal_uLong RTSDevicePage::getDepth() const
+{
+ sal_uInt16 nSelectPos = m_xDepthBox->get_active();
+ if (nSelectPos == 0)
+ return 8;
+ else
+ return 24;
+}
+
+sal_uLong RTSDevicePage::getColorDevice() const
+{
+ sal_uInt16 nSelectPos = m_xSpaceBox->get_active();
+ switch (nSelectPos)
+ {
+ case 0:
+ return 0;
+ case 1:
+ return 1;
+ case 2:
+ return -1;
+ }
+ return 0;
+}
+
+sal_uLong RTSDevicePage::getLevel() const
+{
+ auto nLevel = m_xLevelBox->get_active_id().toInt32();
+ if (nLevel == 0)
+ return 0; //automatic
+ return nLevel < 10 ? nLevel-1 : 0;
+}
+
+sal_uLong RTSDevicePage::getPDFDevice() const
+{
+ auto nLevel = m_xLevelBox->get_active_id().toInt32();
+ if (nLevel > 9)
+ return 2; //explicitly PDF
+ else if (nLevel == 0)
+ return 0; //automatic
+ return -1; //explicitly PS
+}
+
+IMPL_LINK(RTSDevicePage, ModifyHdl, weld::Entry&, rEdit, void)
+{
+ if (m_pCustomValue)
+ {
+ // tdf#123734 Custom PPD option values are a CUPS extension to PPDs and the user-set value
+ // needs to be prefixed with "Custom." in order to be processed properly
+ m_pCustomValue->m_aCustomOption = "Custom." + rEdit.get_text();
+ }
+}
+
+IMPL_LINK( RTSDevicePage, SelectHdl, weld::TreeView&, rBox, void )
+{
+ if (&rBox == m_xPPDKeyBox.get())
+ {
+ const PPDKey* pKey = weld::fromId<PPDKey*>(m_xPPDKeyBox->get_selected_id());
+ FillValueBox( pKey );
+ }
+ else if (&rBox == m_xPPDValueBox.get())
+ {
+ const PPDKey* pKey = weld::fromId<PPDKey*>(m_xPPDKeyBox->get_selected_id());
+ const PPDValue* pValue = weld::fromId<PPDValue*>(m_xPPDValueBox->get_selected_id());
+ if (pKey && pValue)
+ {
+ m_pParent->m_aJobData.m_aContext.setValue( pKey, pValue );
+ ValueBoxChanged(pKey);
+ }
+ }
+ m_pParent->SetDataModified( true );
+}
+
+IMPL_LINK_NOARG( RTSDevicePage, ComboChangedHdl, weld::ComboBox&, void )
+{
+ m_pParent->SetDataModified( true );
+}
+
+void RTSDevicePage::FillValueBox( const PPDKey* pKey )
+{
+ m_xPPDValueBox->clear();
+ m_xCustomEdit->hide();
+
+ if( ! pKey )
+ return;
+
+ const PPDValue* pValue = nullptr;
+ for( int i = 0; i < pKey->countValues(); i++ )
+ {
+ pValue = pKey->getValue( i );
+ if( m_pParent->m_aJobData.m_aContext.checkConstraints( pKey, pValue ) &&
+ m_pParent->m_aJobData.m_pParser )
+ {
+ OUString aEntry;
+ if (pValue->m_bCustomOption)
+ aEntry = VclResId(SV_PRINT_CUSTOM_TXT);
+ else
+ aEntry = m_pParent->m_aJobData.m_pParser->translateOption( pKey->getKey(), pValue->m_aOption);
+ m_xPPDValueBox->append(weld::toId(pValue), aEntry);
+ }
+ }
+ pValue = m_pParent->m_aJobData.m_aContext.getValue( pKey );
+ m_xPPDValueBox->select_id(weld::toId(pValue));
+
+ ValueBoxChanged(pKey);
+}
+
+IMPL_LINK_NOARG(RTSDevicePage, ImplHandleReselectHdl, Timer*, void)
+{
+ //in case selected entry is now not visible select it again to scroll it into view
+ m_xPPDValueBox->select(m_xPPDValueBox->get_selected_index());
+}
+
+void RTSDevicePage::ValueBoxChanged( const PPDKey* pKey )
+{
+ const PPDValue* pValue = m_pParent->m_aJobData.m_aContext.getValue(pKey);
+ if (pValue->m_bCustomOption)
+ {
+ m_pCustomValue = pValue;
+ m_pParent->m_aJobData.m_aContext.setValue(pKey, pValue);
+ // don't show the "Custom." prefix in the UI, s.a. comment in ModifyHdl
+ m_xCustomEdit->set_text(m_pCustomValue->m_aCustomOption.replaceFirst("Custom.", ""));
+ m_xCustomEdit->show();
+ m_aReselectCustomIdle.Start();
+ }
+ else
+ m_xCustomEdit->hide();
+}
+
+int SetupPrinterDriver(weld::Window* pParent, ::psp::PrinterInfo& rJobData)
+{
+ int nRet = 0;
+ RTSDialog aDialog(rJobData, pParent);
+
+ // return 0 if cancel was pressed or if the data
+ // weren't modified, 1 otherwise
+ if (aDialog.run() != RET_CANCEL)
+ {
+ rJobData = aDialog.getSetup();
+ nRet = aDialog.GetDataModified() ? 1 : 0;
+ }
+
+ return nRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/prtsetup.hxx b/vcl/unx/generic/print/prtsetup.hxx
new file mode 100644
index 000000000..bcf86670d
--- /dev/null
+++ b/vcl/unx/generic/print/prtsetup.hxx
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <vcl/idle.hxx>
+#include <vcl/weld.hxx>
+#include <ppdparser.hxx>
+#include <printerinfomanager.hxx>
+
+class RTSPaperPage;
+class RTSDevicePage;
+
+class RTSDialog : public weld::GenericDialogController
+{
+ friend class RTSPaperPage;
+ friend class RTSDevicePage;
+
+ ::psp::PrinterInfo m_aJobData;
+
+ bool m_bDataModified;
+
+ // controls
+ std::unique_ptr<weld::Notebook> m_xTabControl;
+ std::unique_ptr<weld::Button> m_xOKButton;
+ std::unique_ptr<weld::Button> m_xCancelButton;
+
+ // pages
+ std::unique_ptr<RTSPaperPage> m_xPaperPage;
+ std::unique_ptr<RTSDevicePage> m_xDevicePage;
+
+ DECL_LINK(ActivatePage, const OString&, void);
+ DECL_LINK(ClickButton, weld::Button&, void);
+
+ // helper functions
+ void insertAllPPDValues(weld::ComboBox&, const psp::PPDParser*, const psp::PPDKey*);
+
+public:
+ RTSDialog(const ::psp::PrinterInfo& rJobData, weld::Window* pParent);
+ virtual ~RTSDialog() override;
+
+ const ::psp::PrinterInfo& getSetup() const { return m_aJobData; }
+
+ void SetDataModified(bool bModified) { m_bDataModified = bModified; }
+ bool GetDataModified() const { return m_bDataModified; }
+};
+
+class RTSPaperPage
+{
+private:
+ std::unique_ptr<weld::Builder> m_xBuilder;
+
+ RTSDialog* m_pParent;
+
+ std::unique_ptr<weld::Widget> m_xContainer;
+
+ std::unique_ptr<weld::CheckButton> m_xCbFromSetup;
+
+ std::unique_ptr<weld::Label> m_xPaperText;
+ std::unique_ptr<weld::ComboBox> m_xPaperBox;
+
+ std::unique_ptr<weld::Label> m_xOrientText;
+ std::unique_ptr<weld::ComboBox> m_xOrientBox;
+
+ std::unique_ptr<weld::Label> m_xDuplexText;
+ std::unique_ptr<weld::ComboBox> m_xDuplexBox;
+
+ std::unique_ptr<weld::Label> m_xSlotText;
+ std::unique_ptr<weld::ComboBox> m_xSlotBox;
+
+ DECL_LINK(SelectHdl, weld::ComboBox&, void);
+ DECL_LINK(CheckBoxHdl, weld::Toggleable&, void);
+
+public:
+ RTSPaperPage(weld::Widget* pPage, RTSDialog* pDialog);
+ ~RTSPaperPage();
+
+ void update();
+
+ sal_Int32 getOrientation() const { return m_xOrientBox->get_active(); }
+};
+
+class RTSDevicePage
+{
+private:
+ std::unique_ptr<weld::Builder> m_xBuilder;
+
+ const psp::PPDValue* m_pCustomValue;
+ RTSDialog* m_pParent;
+
+ std::unique_ptr<weld::Widget> m_xContainer;
+ std::unique_ptr<weld::TreeView> m_xPPDKeyBox;
+ std::unique_ptr<weld::TreeView> m_xPPDValueBox;
+ std::unique_ptr<weld::Entry> m_xCustomEdit;
+
+ std::unique_ptr<weld::ComboBox> m_xLevelBox;
+ std::unique_ptr<weld::ComboBox> m_xSpaceBox;
+ std::unique_ptr<weld::ComboBox> m_xDepthBox;
+
+ void FillValueBox(const ::psp::PPDKey*);
+ void ValueBoxChanged(const ::psp::PPDKey*);
+
+ Idle m_aReselectCustomIdle;
+
+ DECL_LINK(SelectHdl, weld::TreeView&, void);
+ DECL_LINK(ModifyHdl, weld::Entry&, void);
+ DECL_LINK(ComboChangedHdl, weld::ComboBox&, void);
+ DECL_LINK(ImplHandleReselectHdl, Timer*, void);
+
+public:
+ RTSDevicePage(weld::Widget* pPage, RTSDialog* pDialog);
+ ~RTSDevicePage();
+
+ sal_uLong getLevel() const;
+ sal_uLong getPDFDevice() const;
+ sal_uLong getDepth() const;
+ sal_uLong getColorDevice() const;
+};
+
+int SetupPrinterDriver(weld::Window* pParent, ::psp::PrinterInfo& rJobData);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/psheader.ps b/vcl/unx/generic/print/psheader.ps
new file mode 100644
index 000000000..49f0f5101
--- /dev/null
+++ b/vcl/unx/generic/print/psheader.ps
@@ -0,0 +1,363 @@
+%
+% This file is part of the LibreOffice project.
+%
+% This Source Code Form is subject to the terms of the Mozilla Public
+% License, v. 2.0. If a copy of the MPL was not distributed with this
+% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%
+% This file incorporates work covered by the following license notice:
+%
+% Licensed to the Apache Software Foundation (ASF) under one or more
+% contributor license agreements. See the NOTICE file distributed
+% with this work for additional information regarding copyright
+% ownership. The ASF licenses this file to you under the Apache
+% License, Version 2.0 (the "License"); you may not use this file
+% except in compliance with the License. You may obtain a copy of
+% the License at http://www.apache.org/licenses/LICENSE-2.0 .
+%
+
+% This is an "unobsfucated version of postscript header" in printerjob.cxx. It
+% was probably kept separate for the comments, but it is not used in itself
+% and probably was not kept in sync with the actual header.
+
+%
+%
+% readpath
+%
+% The intention of readpath is to save disk space since the vcl clip region routines
+% produce a huge amount of lineto/moveto commands
+%
+% The principal idea is to maintain the current point on stack and to provide only deltas
+% in the command. These deltas are added to the current point. The new point is used for
+% the lineto and moveto command and saved on stack for the next command.
+%
+% pathdict implements binary/hex representation of lineto and moveto commands.
+% The command consists of a 1byte opcode to switch between lineto and moveto and the size
+% of the following delta-x and delta-y values. The opcode is read with /rcmd, the two
+% coordinates are read with /rhex. The whole command is executed with /xcmd
+%
+%
+
+/pathdict dup 8 dict def load
+begin
+
+ % the command is of the bit format cxxyy
+ % with c=0 meaning lineto
+ % c=1 meaning moveto
+ % xx is a 2bit value for the number of bytes for x position
+ % yy is the same for y, values are off by one: 00 means 1; 11 means 4 !
+ % the command has been added to 'A' to be always in the ascii character
+ % range. the command is followed by 2*xx + 2*yy hexchars.
+ % '~' denotes the special case of EOD
+ /rcmd {
+ {
+ currentfile 1 string readstring % s bool
+ pop % s
+ 0 get % s[0]
+ % --- check whether s[0] is CR, LF ...
+ dup 32 gt % s > ' ' ? then read on
+ { exit }
+ { pop }
+ ifelse
+ }
+ loop
+
+ dup 126 eq { pop exit } if % -- Exit loop if cmd is '~'
+ 65 sub % cmd=s[0]-'A'
+ % -- Separate yy bits
+ dup 16#3 and 1 add % cmd yy
+ % -- Separate xx bits
+ exch % yy cmd
+ dup 16#C and -2 bitshift
+ 16#3 and 1 add exch % yy xx cmd
+ % -- Separate command bit
+ 16#10 and 16#10 eq % yy xx bool
+ 3 1 roll exch % bool xx yy
+ } def
+
+ % length rhex -- reads a signed hex value of given length
+ % the left most bit of char 0 is considered as the sign (0 means '+', 1 means '-')
+ % the rest of the bits is considered to be the abs value. Please note that this
+ % does not match the C binary representation of integers
+ /rhex {
+ dup 1 sub exch % l-1 l
+ currentfile exch string readhexstring % l-1 substring[l] bool
+ pop
+ dup 0 get dup % l-1 s s[0] s[0]
+ % -- Extract the sign
+ 16#80 and 16#80 eq dup % l-1 s s[0] sign=- sign=-
+ % -- Mask out the sign bit and put value back
+ 3 1 roll % l-1 s sign=- s[0] sign=-
+ { 16#7f and } if % l-1 s sign=- +s[0]
+ 2 index 0 % l-1 s sign=- +s[0] s 0
+ 3 -1 roll put % l-1 s sign=- s 0 +s[0]
+ % -- Read loop: add to prev sum, mul with 256
+ 3 1 roll 0 % sign=- l-1 s Sum=0
+ 0 1 5 -1 roll % sign=- s Sum=0 0 1 l-1
+ { % sign=- s Sum idx
+ 2 index exch % sign=- s Sum s idx
+ get % sign=- s Sum s[idx]
+ add 256 mul % sign=- s Sum=(s[idx]+Sum)*256
+ }
+ for
+ % -- mul was once too often, weave in the sign
+ 256 div % sign=- s Sum/256
+ exch pop % sign=- Sum/256
+ exch { neg } if % (sign=- ? -Sum : Sum)
+ } def
+
+ % execute a single command, the former x and y position is already on stack
+ % only offsets are read from cmdstring
+ /xcmd { % x y
+ rcmd % x y bool wx wy
+ exch rhex % x y bool wy Dx
+ exch rhex % x y bool Dx Dy
+ exch 5 -1 roll % y bool Dy Dx x
+ add exch % y bool X Dy
+ 4 -1 roll add % bool X Y
+ 1 index 1 index % bool X Y X Y
+ 5 -1 roll % X Y X Y bool
+ { moveto }
+ { lineto }
+ ifelse % X Y
+ } def
+end
+
+/readpath
+{
+ 0 0 % push initial-x initial-y
+ pathdict begin
+ { xcmd } loop
+ end
+ pop pop % pop final-x final-y
+} def
+
+%
+%
+% if languagelevel is not in the systemdict then its level 1 interpreter:
+% provide compatibility routines
+%
+%
+
+systemdict /languagelevel known not
+{
+ % string numarray xxshow -
+ % does only work for single byte fonts
+ /xshow {
+ exch dup % a s s
+ length 0 1 % a s l(s) 1 1
+ 3 -1 roll 1 sub % a s 0 1 l(s)-1
+ { % a s idx
+ dup % a s idx idx
+ % -- extract the delta offset
+ 3 index exch get % a s idx a[idx]
+ % -- extract the character
+ exch % a s a[idx] idx
+ 2 index exch get % a s a[idx] s[idx]
+ % -- create a tmp string for show
+ 1 string dup 0 % a s a[idx] s[idx] s1 s1 0
+ 4 -1 roll % a s a[idx] s1 s1 0 s[idx]
+ put % a s a[idx] s1
+ % -- store the current point
+ currentpoint 3 -1 roll % a s a[idx] x y s1
+ % -- draw the character
+ show % a s a[idx] x y
+ % -- move to the offset
+ moveto 0 rmoveto % a s
+ }
+ for
+ pop pop % -
+ } def
+
+ % x y width height rectfill
+ % x y width height rectshow
+ % in contrast to the languagelevel 2 operator
+ % they use and change the currentpath
+ /rectangle {
+ 4 -2 roll % width height x y
+ moveto % width height
+ 1 index 0 rlineto % width height % rmoveto(width, 0)
+ 0 exch rlineto % width % rmoveto(0, height)
+ neg 0 rlineto % - % rmoveto(-width, 0)
+ closepath
+ } def
+
+ /rectfill { rectangle fill } def
+ /rectstroke { rectangle stroke } def
+}
+if
+
+% -- small test program
+% 75 75 moveto /Times-Roman findfont 12 scalefont setfont
+% <292a2b2c2d2e2f30313233343536373839>
+% [5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 5] xshow <21>[0] xshow
+% showpage
+
+%
+%
+% shortcuts for image header with compression
+%
+%
+
+/psp_lzwfilter {
+ currentfile /ASCII85Decode filter /LZWDecode filter
+} def
+/psp_ascii85filter {
+ currentfile /ASCII85Decode filter
+} def
+/psp_lzwstring {
+ psp_lzwfilter 1024 string readstring
+} def
+/psp_ascii85string {
+ psp_ascii85filter 1024 string readstring
+} def
+/psp_imagedict {
+ /psp_bitspercomponent {
+ 3 eq
+ { 1 }
+ { 8 }
+ ifelse
+ } def
+ /psp_decodearray {
+ [ [0 1 0 1 0 1] [0 255] [0 1] [0 255] ] exch get
+ } def
+
+ 7 dict dup
+ /ImageType 1 put dup
+ /Width 7 -1 roll put dup
+ /Height 5 index put dup
+ /BitsPerComponent 4 index
+ psp_bitspercomponent put dup
+ /Decode 5 -1 roll
+ psp_decodearray put dup
+ /ImageMatrix [1 0 0 1 0 0] dup
+ 5 8 -1 roll put put dup
+ /DataSource 4 -1 roll
+ 1 eq
+ { psp_lzwfilter }
+ { psp_ascii85filter }
+ ifelse put
+} def
+
+
+%
+%
+% font encoding and reencoding
+%
+%
+
+/ISO1252Encoding [
+ /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef
+ /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef
+ /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef
+ /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef
+ /space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quotesingle
+ /parenleft /parenright /asterisk /plus /comma /hyphen /period /slash
+ /zero /one /two /three /four /five /six /seven
+ /eight /nine /colon /semicolon /less /equal /greater /question
+ /at /A /B /C /D /E /F /G
+ /H /I /J /K /L /M /N /O
+ /P /Q /R /S /T /U /V /W
+ /X /Y /Z /bracketleft /backslash /bracketright /asciicircum /underscore
+ /grave /a /b /c /d /e /f /g
+ /h /i /j /k /l /m /n /o
+ /p /q /r /s /t /u /v /w
+ /x /y /z /braceleft /bar /braceright /asciitilde /unused
+ /Euro /unused /quotesinglbase /florin /quotedblbase /ellipsis /dagger /daggerdbl
+ /circumflex /perthousand /Scaron /guilsinglleft /OE /unused /Zcaron /unused
+ /unused /quoteleft /quoteright /quotedblleft /quotedblright /bullet /endash /emdash
+ /tilde /trademark /scaron /guilsinglright /oe /unused /zcaron /Ydieresis
+ /space /exclamdown /cent /sterling /currency /yen /brokenbar /section
+ /dieresis /copyright /ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron
+ /degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /periodcentered
+ /cedilla /onesuperior /ordmasculine /guillemotright /onequarter /onehalf /threequarters /questiondown
+ /Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla
+ /Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis
+ /Eth /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply
+ /Oslash /Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /germandbls
+ /agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla
+ /egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis
+ /eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide
+ /oslash /ugrave /uacute /ucircumflex /udieresis /yacute /thorn /ydieresis
+] def
+
+% /fontname /encoding psp_findfont
+/psp_findfont {
+ exch dup % encoding fontname fontname
+ findfont % encoding fontname
+ dup length dict
+ begin
+ {
+ 1 index /FID ne
+ { def }
+ { pop pop }
+ ifelse
+ } forall
+ /Encoding 3 -1 roll def
+ currentdict
+ end
+ /psp_reencodedfont exch definefont
+} def
+
+% bshow shows a text in artificial bold
+% this is achieved by first showing the text
+% then stroking its outline over it with
+% the linewidth set to the second parameter
+% usage: (string) num bshow
+
+/bshow {
+ currentlinewidth % save current linewidth
+ 3 1 roll % move it to the last stack position
+ currentpoint % save the current point
+ 3 index % copy the string to show
+ show % show it
+ moveto % move to the original coordinates again
+ setlinewidth % set the linewidth
+ false charpath % create the outline path of the shown string
+ stroke % and stroke it
+ setlinewidth % reset the stored linewidth
+} def
+
+% bxshow shows a text with a delta array in artificial bold
+% that is it does what bshow does for show
+% usage: (string) [deltaarray] num bxshow
+
+/bxshow {
+ currentlinewidth % save linewidth
+ 4 1 roll % move it to the last stack position
+ setlinewidth % set the new linewidth
+ exch % exchange string and delta array
+ dup
+ length % get length of string
+ 1 sub % prepare parameters for {} for
+ 0 1
+ 3 -1 roll
+ {
+ 1 string % create a string object length 1
+ 2 index % get the text
+ 2 index % get charpos (for index variable)
+ get % have char value at charpos
+ 1 index % prepare string for put
+ exch
+ 0
+ exch
+ put % put into string of length 1
+ dup % duplicate the it
+ currentpoint % save current position
+ 3 -1 roll % prepare show
+ show % show the character
+ moveto % move back to beginning
+ currentpoint % save current position
+ 3 -1 roll % prepare outline path of character
+ false charpath
+ stroke % stroke it
+ moveto % move back
+ % now move to next point
+ 2 index % get advance array
+ exch % get charpos
+ get % get advance element
+ 0 rmoveto % advance current position
+ } for
+ pop pop % remove string and delta array
+ setlinewidth % restore linewidth
+} def
diff --git a/vcl/unx/generic/print/psputil.cxx b/vcl/unx/generic/print/psputil.cxx
new file mode 100644
index 000000000..b4837138a
--- /dev/null
+++ b/vcl/unx/generic/print/psputil.cxx
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <string.h>
+#include "psputil.hxx"
+
+namespace psp {
+
+/*
+ * string convenience routines
+ */
+
+sal_Int32
+getHexValueOf (sal_Int32 nValue, OStringBuffer& pBuffer)
+{
+ const static char pHex [0x10] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+ pBuffer.append(pHex [(nValue & 0xF0) >> 4]);
+ pBuffer.append(pHex [(nValue & 0x0F) ]);
+
+ return 2;
+}
+
+sal_Int32
+getAlignedHexValueOf (sal_Int32 nValue, OStringBuffer& pBuffer)
+{
+ // get sign
+ bool bNegative = nValue < 0;
+ nValue = bNegative ? -nValue : nValue;
+
+ // get required buffer size, must be a multiple of two
+ sal_Int32 nPrecision;
+ if (nValue < 0x80)
+ nPrecision = 2;
+ else
+ if (nValue < 0x8000)
+ nPrecision = 4;
+ else
+ if (nValue < 0x800000)
+ nPrecision = 6;
+ else
+ nPrecision = 8;
+
+ // convert the int into its hex representation, write it into the buffer
+ sal_Int32 nRet = nPrecision;
+ auto const start = pBuffer.getLength();
+ while (nPrecision)
+ {
+ OStringBuffer scratch;
+ nPrecision -= getHexValueOf (nValue % 256, scratch );
+ pBuffer.insert(start, scratch);
+ nValue /= 256;
+ }
+
+ // set sign bit
+ if (bNegative)
+ {
+ switch (pBuffer[start])
+ {
+ case '0' : pBuffer[start] = '8'; break;
+ case '1' : pBuffer[start] = '9'; break;
+ case '2' : pBuffer[start] = 'A'; break;
+ case '3' : pBuffer[start] = 'B'; break;
+ case '4' : pBuffer[start] = 'C'; break;
+ case '5' : pBuffer[start] = 'D'; break;
+ case '6' : pBuffer[start] = 'E'; break;
+ case '7' : pBuffer[start] = 'F'; break;
+ default: OSL_FAIL("Already a signed value");
+ }
+ }
+
+ // report precision
+ return nRet;
+}
+
+sal_Int32
+getValueOf (sal_Int32 nValue, OStringBuffer& pBuffer)
+{
+ sal_Int32 nChar = 0;
+ if (nValue < 0)
+ {
+ pBuffer.append('-');
+ ++nChar;
+ nValue *= -1;
+ }
+ else
+ if (nValue == 0)
+ {
+ pBuffer.append('0');
+ ++nChar;
+ return nChar;
+ }
+
+ char pInvBuffer [32];
+ sal_Int32 nInvChar = 0;
+ while (nValue > 0)
+ {
+ pInvBuffer [nInvChar++] = '0' + nValue % 10;
+ nValue /= 10;
+ }
+ while (nInvChar > 0)
+ {
+ pBuffer.append(pInvBuffer [--nInvChar]);
+ ++nChar;
+ }
+
+ return nChar;
+}
+
+sal_Int32
+appendStr (const char* pSrc, OStringBuffer& pDst)
+{
+ sal_Int32 nBytes = strlen (pSrc);
+ pDst.append(pSrc, nBytes);
+
+ return nBytes;
+}
+
+/*
+ * copy strings to file
+ */
+
+bool
+WritePS (osl::File* pFile, const char* pString)
+{
+ sal_uInt64 nInLength = rtl_str_getLength (pString);
+ sal_uInt64 nOutLength = 0;
+
+ if (nInLength > 0 && pFile)
+ pFile->write (pString, nInLength, nOutLength);
+
+ return nInLength == nOutLength;
+}
+
+bool
+WritePS (osl::File* pFile, const char* pString, sal_uInt64 nInLength)
+{
+ sal_uInt64 nOutLength = 0;
+
+ if (nInLength > 0 && pFile)
+ pFile->write (pString, nInLength, nOutLength);
+
+ return nInLength == nOutLength;
+}
+
+bool
+WritePS (osl::File* pFile, const OString &rString)
+{
+ sal_uInt64 nInLength = rString.getLength();
+ sal_uInt64 nOutLength = 0;
+
+ if (nInLength > 0 && pFile)
+ pFile->write (rString.getStr(), nInLength, nOutLength);
+
+ return nInLength == nOutLength;
+}
+
+bool
+WritePS (osl::File* pFile, std::u16string_view rString)
+{
+ return WritePS (pFile, OUStringToOString(rString, RTL_TEXTENCODING_ASCII_US));
+}
+
+} /* namespace psp */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/psputil.hxx b/vcl/unx/generic/print/psputil.hxx
new file mode 100644
index 000000000..e5ae18071
--- /dev/null
+++ b/vcl/unx/generic/print/psputil.hxx
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <osl/file.hxx>
+
+#include <rtl/math.hxx>
+#include <rtl/ustring.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/string.hxx>
+
+namespace psp {
+
+/*
+ * string convenience routines
+ */
+sal_Int32 getHexValueOf (sal_Int32 nValue, OStringBuffer& pBuffer);
+sal_Int32 getAlignedHexValueOf (sal_Int32 nValue, OStringBuffer& pBuffer);
+sal_Int32 getValueOf (sal_Int32 nValue, OStringBuffer& pBuffer);
+sal_Int32 appendStr (const char* pSrc, OStringBuffer& pDst);
+
+inline void getValueOfDouble( OStringBuffer& pBuffer, double f, int nPrecision = 0)
+{
+ pBuffer.append(rtl::math::doubleToString( f, rtl_math_StringFormat_G, nPrecision, '.', true ));
+}
+
+bool WritePS (osl::File* pFile, const char* pString);
+bool WritePS (osl::File* pFile, const char* pString, sal_uInt64 nInLength);
+bool WritePS (osl::File* pFile, const OString &rString);
+bool WritePS (osl::File* pFile, std::u16string_view rString);
+
+} /* namespace psp */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/print/text_gfx.cxx b/vcl/unx/generic/print/text_gfx.cxx
new file mode 100644
index 000000000..d847004ed
--- /dev/null
+++ b/vcl/unx/generic/print/text_gfx.cxx
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "psputil.hxx"
+#include "glyphset.hxx"
+
+#include <unx/printergfx.hxx>
+#include <unx/fontmanager.hxx>
+
+using namespace psp ;
+
+/*
+ * implement text handling printer routines,
+ */
+
+void PrinterGfx::SetFont(
+ sal_Int32 nFontID,
+ sal_Int32 nHeight,
+ sal_Int32 nWidth,
+ Degree10 nAngle,
+ bool bVertical,
+ bool bArtItalic,
+ bool bArtBold
+ )
+{
+ // font and encoding will be set by drawText again immediately
+ // before PSShowText
+ mnFontID = nFontID;
+ maVirtualStatus.maFont.clear();
+ maVirtualStatus.maEncoding = RTL_TEXTENCODING_DONTKNOW;
+ maVirtualStatus.mnTextHeight = nHeight;
+ maVirtualStatus.mnTextWidth = nWidth;
+ maVirtualStatus.mbArtItalic = bArtItalic;
+ maVirtualStatus.mbArtBold = bArtBold;
+ mnTextAngle = nAngle;
+ mbTextVertical = bVertical;
+}
+
+void PrinterGfx::drawGlyph(const Point& rPoint,
+ sal_GlyphId aGlyphId)
+{
+
+ // draw the string
+ // search for a glyph set matching the set font
+ bool bGlyphFound = false;
+ for (auto & elem : maPS3Font)
+ if ( (elem.GetFontID() == mnFontID)
+ && (elem.IsVertical() == mbTextVertical))
+ {
+ elem.DrawGlyph (*this, rPoint, aGlyphId);
+ bGlyphFound = true;
+ break;
+ }
+
+ // not found ? create a new one
+ if (!bGlyphFound)
+ {
+ maPS3Font.emplace_back(mnFontID, mbTextVertical);
+ maPS3Font.back().DrawGlyph (*this, rPoint, aGlyphId);
+ }
+}
+
+void PrinterGfx::DrawGlyph(const Point& rPoint,
+ const GlyphItem& rGlyph)
+{
+ // move and rotate the user coordinate system
+ // avoid the gsave/grestore for the simple cases since it allows
+ // reuse of the current font if it hasn't changed
+ Degree10 nCurrentTextAngle = mnTextAngle;
+ Point aPoint( rPoint );
+
+ if (nCurrentTextAngle)
+ {
+ PSGSave ();
+ PSTranslate (rPoint);
+ PSRotate (nCurrentTextAngle);
+ mnTextAngle = 0_deg10;
+ aPoint = Point( 0, 0 );
+ }
+
+ if (mbTextVertical && rGlyph.IsVertical())
+ {
+ sal_Int32 nTextHeight = maVirtualStatus.mnTextHeight;
+ sal_Int32 nTextWidth = maVirtualStatus.mnTextWidth ? maVirtualStatus.mnTextWidth : maVirtualStatus.mnTextHeight;
+ sal_Int32 nAscend = mrFontMgr.getFontAscend( mnFontID );
+ sal_Int32 nDescend = mrFontMgr.getFontDescend( mnFontID );
+
+ nDescend = nDescend * nTextHeight / 1000;
+ nAscend = nAscend * nTextHeight / 1000;
+
+ Point aRotPoint( -nDescend*nTextWidth/nTextHeight, nAscend*nTextWidth/nTextHeight );
+
+ // transform matrix to new individual direction
+ PSGSave ();
+ GraphicsStatus aSaveStatus = maVirtualStatus;
+ // switch font aspect
+ maVirtualStatus.mnTextWidth = nTextHeight;
+ maVirtualStatus.mnTextHeight = nTextWidth;
+ if( aPoint.X() || aPoint.Y() )
+ PSTranslate( aPoint );
+ PSRotate (900_deg10);
+ // draw the rotated glyph
+ drawGlyph(aRotPoint, rGlyph.glyphId());
+
+ // restore previous state
+ maVirtualStatus = aSaveStatus;
+ PSGRestore();
+ }
+ else
+ drawGlyph(aPoint, rGlyph.glyphId());
+
+ // restore the user coordinate system
+ if (nCurrentTextAngle)
+ {
+ PSGRestore ();
+ mnTextAngle = nCurrentTextAngle;
+ }
+}
+
+/*
+ * spool the converted truetype fonts to the page header after the page body is
+ * complete
+ * for Type1 fonts spool additional reencoding vectors that are necessary to access the
+ * whole font
+ */
+
+void
+PrinterGfx::OnEndJob ()
+{
+ maPS3Font.clear();
+}
+
+void
+PrinterGfx::writeResources( osl::File* pFile, std::vector< OString >& rSuppliedFonts )
+{
+ // write glyphsets and reencodings
+ for (auto & PS3Font : maPS3Font)
+ {
+ PS3Font.PSUploadFont (*pFile, *this, mbUploadPS42Fonts, rSuppliedFonts );
+ }
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/printer/configuration/README b/vcl/unx/generic/printer/configuration/README
new file mode 100644
index 000000000..c39237a53
--- /dev/null
+++ b/vcl/unx/generic/printer/configuration/README
@@ -0,0 +1,5 @@
+Contains ppds for use by vcl when not using CUPS
+
+This is used for the print-to-file functionality. These two PPDs
+describe the range of paper sizes and postscript options necessary for
+printing to postscript without a configured printer.
diff --git a/vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS b/vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS
new file mode 100644
index 000000000..177e2c4e0
--- /dev/null
+++ b/vcl/unx/generic/printer/configuration/ppds/SGENPRT.PS
@@ -0,0 +1,582 @@
+*PPD-Adobe: "4.0"
+*%
+*% This file is part of the LibreOffice project.
+*%
+*% This Source Code Form is subject to the terms of the Mozilla Public
+*% License, v. 2.0. If a copy of the MPL was not distributed with this
+*% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*%
+*% This file incorporates work covered by the following license notice:
+*%
+*% Licensed to the Apache Software Foundation (ASF) under one or more
+*% contributor license agreements. See the NOTICE file distributed
+*% with this work for additional information regarding copyright
+*% ownership. The ASF licenses this file to you under the Apache
+*% License, Version 2.0 (the "License")*% you may not use this file
+*% except in compliance with the License. You may obtain a copy of
+*% the License at http://www.apache.org/licenses/LICENSE-2.0 .
+*%
+
+*% The user must print with a PostScript(R) emulator to non PostScript(R)
+*% printers if the system has no specific printer support. This file
+*% allows the user to print to most printers without any modification.
+*% Standard paper sizes and resolutions are defined. There are some
+*% additional definitions for screen or online documents in this file.
+*% To print to a PostScript(R) printer, use the specific PPD file.
+
+*% ===== General =====
+
+*FormatVersion: "4.0"
+*FileVersion: "1.0"
+*LanguageEncoding: ISOLatin1
+*LanguageVersion: English
+*PSVersion: "(1) 1"
+*Product: "(Generic Printer)"
+*ModelName: "Generic Printer"
+*NickName: "Generic Printer"
+*PCFileName: "SGENPRT.PPD"
+
+
+*% ===== Basic Capabilities and Defaults =====
+
+*ColorDevice: True
+*DefaultColorSpace: RGB
+*LanguageLevel: "2"
+*TTRasterizer: Type42
+
+*% --- For None Color or old PostScript(R) printers use following lines ---
+*% *ColorDevice: False
+*% *DefaultColorSpace: Gray
+*% *LanguageLevel: "1"
+
+*FreeVM: "8388608"
+*VariablePaperSize: True
+*FileSystem: False
+*Throughput: "8"
+*Password: "0"
+*ExitServer: "
+ count 0 eq % is the password on the stack?
+ { true }
+ { dup % potential password
+ statusdict /checkpassword get exec not
+ } ifelse
+ { % if no password or not valid
+ (WARNING : Cannot perform the exitserver command.) =
+ (Password supplied is not valid.) =
+ (Please contact the author of this software.) = flush
+ quit
+ } if
+ serverdict /exitserver get exec
+"
+*End
+*Reset: "
+ count 0 eq % is the password on the stack?
+ { true }
+ { dup % potential password
+ statusdict /checkpassword get exec not
+ } ifelse
+ { % if no password or not valid
+ (WARNING : Cannot reset printer.) =
+ (Password supplied is not valid.) =
+ (Please contact the author of this software.) = flush
+ quit
+ } if
+ serverdict /exitserver get exec
+ systemdict /quit get exec
+ (WARNING : Printer Reset Failed.) = flush
+"
+*End
+
+
+*DefaultResolution: 300dpi
+
+*ResScreenFreq 72dpi: "60.0"
+*ResScreenFreq 144dpi: "60.0"
+*ResScreenFreq 300dpi: "60.0"
+*ResScreenFreq 360dpi: "60.0"
+*ResScreenFreq 600dpi: "60.0"
+*ResScreenFreq 720dpi: "60.0"
+*ResScreenFreq 1200dpi: "60.0"
+*ResScreenFreq 1440dpi: "60.0"
+*ResScreenFreq 2400dpi: "60.0"
+*ResScreenAngle 72dpi: "45.0"
+*ResScreenAngle 144dpi: "45.0"
+*ResScreenAngle 300dpi: "45.0"
+*ResScreenAngle 360dpi: "45.0"
+*ResScreenAngle 600dpi: "45.0"
+*ResScreenAngle 720dpi: "45.0"
+*ResScreenAngle 1200dpi: "45.0"
+*ResScreenAngle 1440dpi: "45.0"
+*ResScreenAngle 2400dpi: "45.0"
+
+
+*% ===== Halftone =====
+
+*ContoneOnly: False
+*DefaultHalftoneType: 1
+*ScreenFreq: "60.0"
+*ScreenAngle: "45.0"
+*DefaultScreenProc: Dot
+*ScreenProc Dot: "
+ { abs exch abs 2 copy add 1 gt {1 sub dup mul exch 1 sub
+ dup mul add 1 sub } { dup mul exch dup mul add 1 exch sub }
+ ifelse } bind
+"
+*End
+*ScreenProc Line: "{ exch pop abs neg } bind"
+*ScreenProc Ellipse: "
+ { abs exch abs 2 copy mul exch 4 mul add 3 sub dup 0
+ lt { pop dup mul exch .75 div dup mul add 4 div 1 exch sub }
+ { dup 1 gt { pop 1 exch sub dup mul exch 1 exch sub .75 div
+ dup mul add 4 div 1 sub }
+ { .5 exch sub exch pop exch pop } ifelse } ifelse } bind
+"
+*End
+*ScreenProc Cross: "{ abs exch abs 2 copy gt { exch } if pop neg } bind"
+
+*DefaultTransfer: Null
+*Transfer Null: "{ } bind"
+*Transfer Null.Inverse: "{ 1 exch sub } bind"
+
+
+*% ===== Paper =====
+
+*OpenUI *PageSize: PickOne
+*OrderDependency: 30 AnySetup *PageSize
+*DefaultPageSize: Letter
+*PageSize A0: "<</PageSize [2384 3370] /ImagingBBox null>> setpagedevice"
+*PageSize A1: "<</PageSize [1684 2384] /ImagingBBox null>> setpagedevice"
+*PageSize A2: "<</PageSize [1191 1684] /ImagingBBox null>> setpagedevice"
+*PageSize A3: "<</PageSize [842 1191] /ImagingBBox null>> setpagedevice"
+*PageSize A4: "<</PageSize [595 842] /ImagingBBox null>> setpagedevice"
+*PageSize A5: "<</PageSize [420 595] /ImagingBBox null>> setpagedevice"
+*PageSize A6: "<</PageSize [297 420] /ImagingBBox null>> setpagedevice"
+*PageSize B4: "<</PageSize [728 1032] /ImagingBBox null>> setpagedevice"
+*PageSize B5: "<</PageSize [516 729] /ImagingBBox null>> setpagedevice"
+*PageSize B6: "<</PageSize [363 516] /ImagingBBox null>> setpagedevice"
+*PageSize Legal/US Legal: "<</PageSize [612 1008] /ImagingBBox null>> setpagedevice"
+*PageSize Letter/US Letter: "<</PageSize [612 792] /ImagingBBox null>> setpagedevice"
+*PageSize Executive: "<</PageSize [522 756] /ImagingBBox null>> setpagedevice"
+*PageSize Statement: "<</PageSize [396 612] /ImagingBBox null>> setpagedevice"
+*PageSize Tabloid/US Tabloid: "<</PageSize [792 1224] /ImagingBBox null>> setpagedevice"
+*PageSize Ledger/Ledger Landscape: "<</PageSize [1224 792] /ImagingBBox null>> setpagedevice"
+*PageSize AnsiC/US C: "<</PageSize [1224 1584] /ImagingBBox null>> setpagedevice"
+*PageSize AnsiD/US D: "<</PageSize [1584 2448] /ImagingBBox null>> setpagedevice"
+*PageSize AnsiE/US E: "<</PageSize [2448 3168] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHA/ARCH A: "<</PageSize [648 864] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHB/ARCH B: "<</PageSize [864 1296] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHC/ARCH C: "<</PageSize [1296 1728] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHD/ARCH D: "<</PageSize [1728 2592] /ImagingBBox null>> setpagedevice"
+*PageSize ARCHE/ARCH E: "<</PageSize [2592 3456] /ImagingBBox null>> setpagedevice"
+*PageSize EnvMonarch/Monarch Envelope: "<</PageSize [279 540] /ImagingBBox null>> setpagedevice"
+*PageSize EnvDL/DL Envelope: "<</PageSize [312 624] /ImagingBBox null>> setpagedevice"
+*PageSize EnvC4/C4 Envelope: "<</PageSize [649 918] /ImagingBBox null>> setpagedevice"
+*PageSize EnvC5/C5 Envelope: "<</PageSize [459 649] /ImagingBBox null>> setpagedevice"
+*PageSize EnvC6/C6 Envelope: "<</PageSize [323 459] /ImagingBBox null>> setpagedevice"
+*PageSize Env10/C10 Envelope: "<</PageSize [297 684] /ImagingBBox null>> setpagedevice"
+*PageSize EnvC65/C65 Envelope: "<</PageSize [324 648] /ImagingBBox null>> setpagedevice"
+*PageSize Folio: "<</PageSize [595 935] /ImagingBBox null>> setpagedevice"
+*?PageSize: "
+ save
+ currentpagedevice /PageSize get aload pop
+ 2 copy gt {exch} if
+ (Unknown)
+ 32 dict
+ dup [2384 3370] (A0) put
+ dup [1684 2384] (A1) put
+ dup [1191 1684] (A2) put
+ dup [842 1191] (A3) put
+ dup [595 842] (A4) put
+ dup [420 595] (A5) put
+ dup [297 420] (A6) put
+ dup [728 1032] (B4) put
+ dup [516 729] (B5) put
+ dup [363 516] (B6) put
+ dup [612 1008] (Legal) put
+ dup [612 792] (Letter) put
+ dup [522 756] (Executive) put
+ dup [396 612] (Statement) put
+ dup [792 1224] (Tabloid) put
+ dup [1224 792] (Ledger) put
+ dup [1224 1584] (AnsiC) put
+ dup [1584 2448] (AnsiD) put
+ dup [2448 3168] (AnsiE) put
+ dup [648 864] (ARCHA) put
+ dup [864 1296] (ARCHB) put
+ dup [1296 1728] (ARCHC) put
+ dup [1728 2592] (ARCHD) put
+ dup [2592 3456] (ARCHE) put
+ dup [279 540] (EnvMonarch) put
+ dup [312 624] (EnvDL) put
+ dup [649 918] (EnvC4) put
+ dup [459 649] (EnvC5) put
+ dup [323 459] (EnvC6) put
+ dup [297 684] (Env10) put
+ dup [324 648] (EnvC65) put
+ dup [595 935] (Folio) put
+ { exch aload pop 4 index sub abs 5 le exch
+ 5 index sub abs 5 le and
+ { exch pop exit } { pop } ifelse
+ } bind forall
+ = flush pop pop
+ restore
+"
+*End
+*CloseUI: *PageSize
+
+*OpenUI *PageRegion: PickOne
+*OrderDependency: 40 AnySetup *PageRegion
+*DefaultPageRegion: Letter
+*PageRegion A0: "<</PageSize [2384 3370] /ImagingBBox null>> setpagedevice"
+*PageRegion A1: "<</PageSize [1684 2384] /ImagingBBox null>> setpagedevice"
+*PageRegion A2: "<</PageSize [1191 1684] /ImagingBBox null>> setpagedevice"
+*PageRegion A3: "<</PageSize [842 1191] /ImagingBBox null>> setpagedevice"
+*PageRegion A4: "<</PageSize [595 842] /ImagingBBox null>> setpagedevice"
+*PageRegion A5: "<</PageSize [420 595] /ImagingBBox null>> setpagedevice"
+*PageRegion A6: "<</PageSize [297 420] /ImagingBBox null>> setpagedevice"
+*PageRegion B4: "<</PageSize [728 1032] /ImagingBBox null>> setpagedevice"
+*PageRegion B5: "<</PageSize [516 729] /ImagingBBox null>> setpagedevice"
+*PageRegion B6: "<</PageSize [363 516] /ImagingBBox null>> setpagedevice"
+*PageRegion Legal/US Legal: "<</PageSize [612 1008] /ImagingBBox null>> setpagedevice"
+*PageRegion Letter/US Letter: "<</PageSize [612 792] /ImagingBBox null>> setpagedevice"
+*PageRegion Executive: "<</PageSize [522 756] /ImagingBBox null>> setpagedevice"
+*PageRegion Statement: "<</PageSize [396 612] /ImagingBBox null>> setpagedevice"
+*PageRegion Tabloid/US Tabloid: "<</PageSize [792 1224] /ImagingBBox null>> setpagedevice"
+*PageRegion Ledger/Ledger Landscape: "<</PageSize [1224 792] /ImagingBBox null>> setpagedevice"
+*PageRegion AnsiC/US C: "<</PageSize [1224 1584] /ImagingBBox null>> setpagedevice"
+*PageRegion AnsiD/US D: "<</PageSize [1584 2448] /ImagingBBox null>> setpagedevice"
+*PageRegion AnsiE/US E: "<</PageSize [2448 3168] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHA/ARCH A: "<</PageSize [648 864] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHB/ARCH B: "<</PageSize [864 1296] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHC/ARCH C: "<</PageSize [1296 1728] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHD/ARCH D: "<</PageSize [1728 2592] /ImagingBBox null>> setpagedevice"
+*PageRegion ARCHE/ARCH E: "<</PageSize [2592 3456] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvMonarch/Monarch Envelope: "<</PageSize [279 540] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvDL/DL Envelope: "<</PageSize [312 624] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvC4/C4 Envelope: "<</PageSize [649 918] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvC5/C5 Envelope: "<</PageSize [459 649] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvC6/C6 Envelope: "<</PageSize [323 459] /ImagingBBox null>> setpagedevice"
+*PageRegion Env10/C10 Envelope: "<</PageSize [297 684] /ImagingBBox null>> setpagedevice"
+*PageRegion EnvC65/C65 Envelope: "<</PageSize [324 648] /ImagingBBox null>> setpagedevice"
+*PageRegion Folio: "<</PageSize [595 935] /ImagingBBox null>> setpagedevice"
+*CloseUI: *PageRegion
+
+*DefaultImageableArea: Letter
+*ImageableArea A0: "0 0 2384 3370"
+*ImageableArea A1: "0 0 1684 2384"
+*ImageableArea A2: "0 0 1191 1684"
+*ImageableArea A3: "18 18 824 1173"
+*ImageableArea A4: "18 18 577 824"
+*ImageableArea A5: "18 18 402 577"
+*ImageableArea A6: "18 18 279 402"
+*ImageableArea B4: "18 18 710 1014"
+*ImageableArea B5: "18 18 498 711"
+*ImageableArea B6: "18 18 345 498"
+*ImageableArea Legal: "18 18 594 990"
+*ImageableArea Letter: "18 18 594 774"
+*ImageableArea Executive: "18 18 504 738"
+*ImageableArea Statement: "18 18 378 594"
+*ImageableArea Tabloid: "18 18 774 1206"
+*ImageableArea Ledger: "18 18 1206 774"
+*ImageableArea AnsiC: "0 0 1224 1584"
+*ImageableArea AnsiD: "0 0 1584 2448"
+*ImageableArea AnsiE: "0 0 2448 3168"
+*ImageableArea ARCHA: "0 0 648 864"
+*ImageableArea ARCHB: "0 0 864 1296"
+*ImageableArea ARCHC: "0 0 1296 1728"
+*ImageableArea ARCHD: "0 0 1728 2592"
+*ImageableArea ARCHE: "0 0 2592 3456"
+*ImageableArea EnvMonarch: "0 0 279 540"
+*ImageableArea EnvDL: "0 0 312 624"
+*ImageableArea EnvC4: "0 0 649 918"
+*ImageableArea EnvC5: "0 0 459 649"
+*ImageableArea EnvC6: "0 0 323 459"
+*ImageableArea Env10: "0 0 297 684"
+*ImageableArea EnvC65: "0 0 324 648"
+*ImageableArea Folio: "0 0 595 935"
+
+*DefaultPaperDimension: Letter
+*PaperDimension A0: "2384 3370"
+*PaperDimension A1: "1684 2384"
+*PaperDimension A2: "1191 1684"
+*PaperDimension A3: "842 1191"
+*PaperDimension A4: "595 842"
+*PaperDimension A5: "420 595"
+*PaperDimension A6: "297 420"
+*PaperDimension B4: "728 1032"
+*PaperDimension B5: "516 729"
+*PaperDimension B6: "363 516"
+*PaperDimension Legal: "612 1008"
+*PaperDimension Letter: "612 792"
+*PaperDimension Executive: "522 756"
+*PaperDimension Statement: "396 612"
+*PaperDimension Tabloid: "792 1224"
+*PaperDimension Ledger: "1224 792"
+*PaperDimension AnsiC: "1224 1584"
+*PaperDimension AnsiD: "1584 2448"
+*PaperDimension AnsiE: "2448 3168"
+*PaperDimension ARCHA: "648 864"
+*PaperDimension ARCHB: "864 1296"
+*PaperDimension ARCHC: "1296 1728"
+*PaperDimension ARCHD: "1728 2592"
+*PaperDimension ARCHE: "2592 3456"
+*PaperDimension EnvMonarch: "279 540"
+*PaperDimension EnvDL: "312 624"
+*PaperDimension EnvC4: "649 918"
+*PaperDimension EnvC5: "459 649"
+*PaperDimension EnvC6: "323 459"
+*PaperDimension Env10: "297 684"
+*PaperDimension EnvC65: "324 648"
+*PaperDimension Folio: "595 935"
+
+*% ===== Duplex =====
+*OpenUI *Duplex/Duplex: PickOne
+*OrderDependency: 30 AnySetup *Duplex
+*DefaultDuplex: Simplex
+*Duplex Simplex: ""
+*Duplex None/Off: "
+<</Duplex false /Tumble false
+ /Policies << /Duplex 1 /Tumble 1 >>
+>> setpagedevice"
+*Duplex DuplexNoTumble/Long edge:"
+<</Duplex true /Tumble false
+ /Policies << /Duplex 1 /Tumble 1 >>
+>> setpagedevice"
+*Duplex DuplexTumble/Short edge:"
+<</Duplex true /Tumble true
+ /Policies << /Duplex 1 /Tumble 1 >>
+>> setpagedevice"
+*End
+*CloseUI: *Duplex
+
+*% ===== ManualFeed ===
+*OpenUI *ManualFeed/Manual Feed: Boolean
+*OrderDependency: 15 AnySetup *ManualFeed
+*DefaultManualFeed: False
+*ManualFeed False: "
+<< /ManualFeed false /Policies << /ManualFeed 1 >> >> setpagedevice"
+*ManualFeed True: "
+<< /ManualFeed true /Policies << /ManualFeed 1 >> >> setpagedevice"
+*End
+*CloseUI: *ManualFeed
+
+*% ===== Fonts =====
+
+*DefaultFont: Courier
+*Font AvantGarde-Book: Standard "(001.002)" Standard ROM
+*Font AvantGarde-BookOblique: Standard "(001.000)" Standard ROM
+*Font AvantGarde-Demi: Standard "(001.000)" Standard ROM
+*Font AvantGarde-DemiOblique: Standard "(001.000)" Standard ROM
+*Font Bookman-Demi: Standard "(001.000)" Standard ROM
+*Font Bookman-DemiItalic: Standard "(001.000)" Standard ROM
+*Font Bookman-Light: Standard "(001.000)" Standard ROM
+*Font Bookman-LightItalic: Standard "(001.000)" Standard ROM
+*Font Courier: Standard "(001.000)" Standard ROM
+*Font Courier-Bold: Standard "(001.000)" Standard ROM
+*Font Courier-BoldOblique: Standard "(001.000)" Standard ROM
+*Font Courier-Oblique: Standard "(001.000)" Standard ROM
+*Font Helvetica: Standard "(001.000)" Standard ROM
+*Font Helvetica-Bold: Standard "(001.000)" Standard ROM
+*Font Helvetica-BoldOblique: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow-Bold: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow-BoldOblique: Standard "(001.000)" Standard ROM
+*Font Helvetica-Narrow-Oblique: Standard "(001.000)" Standard ROM
+*Font Helvetica-Oblique: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-Bold: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-BoldItalic: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-Italic: Standard "(001.000)" Standard ROM
+*Font NewCenturySchlbk-Roman: Standard "(001.000)" Standard ROM
+*Font Palatino-Bold: Standard "(001.000)" Standard ROM
+*Font Palatino-BoldItalic: Standard "(001.000)" Standard ROM
+*Font Palatino-Italic: Standard "(001.000)" Standard ROM
+*Font Palatino-Roman: Standard "(001.000)" Standard ROM
+*Font Symbol: Special "(001.001)" Special ROM
+*Font Times-Bold: Standard "(001.000)" Standard ROM
+*Font Times-BoldItalic: Standard "(001.000)" Standard ROM
+*Font Times-Italic: Standard "(001.000)" Standard ROM
+*Font Times-Roman: Standard "(001.000)" Standard ROM
+*Font ZapfChancery-MediumItalic: Standard "(001.000)" Standard ROM
+*Font ZapfDingbats: Special "(001.000)" Special ROM
+*?FontQuery: "
+ save
+ {
+ count 1 gt
+ {
+ exch dup 127 string cvs (/) print print (:) print
+ /Font resourcestatus {pop pop (Yes)} {(No)} ifelse =
+ }
+ { exit } ifelse
+ } bind loop
+ (*) = flush
+ restore
+"
+*End
+
+*?FontList: "
+ save
+ (*) {cvn ==} 128 string /Font resourceforall
+ (*) = flush
+ restore
+"
+*End
+
+
+*% === Printer Messages ===
+
+*Message: "%%[ exitserver: permanent state may be changed ]%%"
+*Message: "%%[ Flushing: rest of job (to end-of-file) will be ignored ]%%"
+*Message: "\FontName\ not found, using Courier"
+
+*% Status (format: %%[ status: <one of these> %%] )
+*Status: "idle"
+*Status: "busy"
+*Status: "waiting"
+*Status: "printing"
+*Status: "PrinterError: timeout, clearing printer"
+*Status: "PrinterError: paper entry misfeed"
+*Status: "PrinterError: warming up"
+*Status: "PrinterError: service call"
+*Status: "PrinterError: no toner cartridge"
+*Status: "PrinterError: no paper tray"
+*Status: "PrinterError: cover open"
+*Status: "PrinterError: resetting printer"
+*Status: "PrinterError: out of paper"
+*Status: "PrinterError: timeout"
+*Status: "PrinterError: manual feed timeout"
+
+*% Input Sources (format: %%[ status: <stat>; source: <one of these>]%% )
+
+*% Printer Error (format: %%[ PrinterError: <one of these>]%%)
+*PrinterError: "timeout, clearing printer"
+*PrinterError: "paper entry misfeed"
+*PrinterError: "warming up"
+*PrinterError: "service call"
+*PrinterError: "no toner cartridge"
+*PrinterError: "no paper tray"
+*PrinterError: "cover open"
+*PrinterError: "resetting printer"
+*PrinterError: "out of paper"
+*PrinterError: "timeout"
+*PrinterError: "manual feed timeout"
+
+
+*% ===== Color Separation =====
+
+*DefaultColorSep: ProcessBlack.60lpi.300dpi/60 lpi / 300 dpi
+*InkName: ProcessBlack/Process Black
+*InkName: CustomColor/Custom Color
+*InkName: ProcessCyan/Process Cyan
+*InkName: ProcessMagenta/Process Magenta
+*InkName: ProcessYellow/Process Yellow
+
+*% --- For 60 lpi / 72 dpi ---
+*ColorSepScreenAngle ProcessBlack.60lpi.72dpi/60 lpi / 72 dpi: "45"
+*ColorSepScreenAngle CustomColor.60lpi.72dpi/60 lpi / 72 dpi: "45"
+*ColorSepScreenAngle ProcessCyan.60lpi.72dpi/60 lpi / 72 dpi: "15"
+*ColorSepScreenAngle ProcessMagenta.60lpi.72dpi/60 lpi / 72 dpi: "75"
+*ColorSepScreenAngle ProcessYellow.60lpi.72dpi/60 lpi / 72 dpi: "0"
+*ColorSepScreenFreq ProcessBlack.60lpi.72dpi/60 lpi / 72 dpi: "60"
+*ColorSepScreenFreq CustomColor.60lpi.72dpi/60 lpi / 72 dpi: "60"
+*ColorSepScreenFreq ProcessCyan.60lpi.72dpi/60 lpi / 72 dpi: "60"
+*ColorSepScreenFreq ProcessMagenta.60lpi.72dpi/60 lpi / 72 dpi: "60"
+*ColorSepScreenFreq ProcessYellow.60lpi.72dpi/60 lpi / 72 dpi: "60"
+
+*% --- For 60 lpi / 144 dpi ---
+*ColorSepScreenAngle ProcessBlack.60lpi.144dpi/60 lpi / 144 dpi: "45"
+*ColorSepScreenAngle CustomColor.60lpi.144dpi/60 lpi / 144 dpi: "45"
+*ColorSepScreenAngle ProcessCyan.60lpi.144dpi/60 lpi / 144 dpi: "15"
+*ColorSepScreenAngle ProcessMagenta.60lpi.144dpi/60 lpi / 144 dpi: "75"
+*ColorSepScreenAngle ProcessYellow.60lpi.144dpi/60 lpi / 144 dpi: "0"
+*ColorSepScreenFreq ProcessBlack.60lpi.144dpi/60 lpi / 144 dpi: "60"
+*ColorSepScreenFreq CustomColor.60lpi.144dpi/60 lpi / 144 dpi: "60"
+*ColorSepScreenFreq ProcessCyan.60lpi.144dpi/60 lpi / 144 dpi: "60"
+*ColorSepScreenFreq ProcessMagenta.60lpi.144dpi/60 lpi / 144 dpi: "60"
+*ColorSepScreenFreq ProcessYellow.60lpi.144dpi/60 lpi / 144 dpi: "60"
+
+*% --- For 60 lpi / 300 dpi ---
+*ColorSepScreenAngle ProcessBlack.60lpi.300dpi/60 lpi / 300 dpi: "45"
+*ColorSepScreenAngle CustomColor.60lpi.300dpi/60 lpi / 300 dpi: "45"
+*ColorSepScreenAngle ProcessCyan.60lpi.300dpi/60 lpi / 300 dpi: "15"
+*ColorSepScreenAngle ProcessMagenta.60lpi.300dpi/60 lpi / 300 dpi: "75"
+*ColorSepScreenAngle ProcessYellow.60lpi.300dpi/60 lpi / 300 dpi: "0"
+*ColorSepScreenFreq ProcessBlack.60lpi.300dpi/60 lpi / 300 dpi: "60"
+*ColorSepScreenFreq CustomColor.60lpi.300dpi/60 lpi / 300 dpi: "60"
+*ColorSepScreenFreq ProcessCyan.60lpi.300dpi/60 lpi / 300 dpi: "60"
+*ColorSepScreenFreq ProcessMagenta.60lpi.300dpi/60 lpi / 300 dpi: "60"
+*ColorSepScreenFreq ProcessYellow.60lpi.300dpi/60 lpi / 300 dpi: "60"
+
+*% --- For 60 lpi / 360 dpi ---
+*ColorSepScreenAngle ProcessBlack.60lpi.360dpi/60 lpi / 360 dpi: "45"
+*ColorSepScreenAngle CustomColor.60lpi.360dpi/60 lpi / 360 dpi: "45"
+*ColorSepScreenAngle ProcessCyan.60lpi.360dpi/60 lpi / 360 dpi: "15"
+*ColorSepScreenAngle ProcessMagenta.60lpi.360dpi/60 lpi / 360 dpi: "75"
+*ColorSepScreenAngle ProcessYellow.60lpi.360dpi/60 lpi / 360 dpi: "0"
+*ColorSepScreenFreq ProcessBlack.60lpi.360dpi/60 lpi / 360 dpi: "60"
+*ColorSepScreenFreq CustomColor.60lpi.360dpi/60 lpi / 360 dpi: "60"
+*ColorSepScreenFreq ProcessCyan.60lpi.360dpi/60 lpi / 360 dpi: "60"
+*ColorSepScreenFreq ProcessMagenta.60lpi.360dpi/60 lpi / 360 dpi: "60"
+*ColorSepScreenFreq ProcessYellow.60lpi.360dpi/60 lpi / 360 dpi: "60"
+
+*% --- For 71 lpi / 600 dpi ---
+*ColorSepScreenAngle ProcessBlack.71lpi.600dpi/71 lpi / 600 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.71lpi.600dpi/71 lpi / 600 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.71lpi.600dpi/71 lpi / 600 dpi: "71.5651"
+*ColorSepScreenAngle ProcessMagenta.71lpi.600dpi/71 lpi / 600 dpi: "18.4349"
+*ColorSepScreenAngle ProcessYellow.71lpi.600dpi/71 lpi / 600 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.71lpi.600dpi/71 lpi / 600 dpi: "70.7107"
+*ColorSepScreenFreq CustomColor.71lpi.600dpi/71 lpi / 600 dpi: "70.7107"
+*ColorSepScreenFreq ProcessCyan.71lpi.600dpi/71 lpi / 600 dpi: "63.2456"
+*ColorSepScreenFreq ProcessMagenta.71lpi.600dpi/71 lpi / 600 dpi: "63.2456"
+*ColorSepScreenFreq ProcessYellow.71lpi.600dpi/71 lpi / 600 dpi: "66.6667"
+
+*% --- For 71 lpi / 720 dpi ---
+*ColorSepScreenAngle ProcessBlack.71lpi.720dpi/71 lpi / 720 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.71lpi.720dpi/71 lpi / 720 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.71lpi.720dpi/71 lpi / 720 dpi: "71.5651"
+*ColorSepScreenAngle ProcessMagenta.71lpi.720dpi/71 lpi / 720 dpi: "18.4349"
+*ColorSepScreenAngle ProcessYellow.71lpi.720dpi/71 lpi / 720 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.71lpi.720dpi/71 lpi / 720 dpi: "70.7107"
+*ColorSepScreenFreq CustomColor.71lpi.720dpi/71 lpi / 720 dpi: "70.7107"
+*ColorSepScreenFreq ProcessCyan.71lpi.720dpi/71 lpi / 720 dpi: "63.2456"
+*ColorSepScreenFreq ProcessMagenta.71lpi.720dpi/71 lpi / 720 dpi: "63.2456"
+*ColorSepScreenFreq ProcessYellow.71lpi.720dpi/71 lpi / 720 dpi: "66.6667"
+
+*% --- For 100 lpi / 1200 dpi ---
+*ColorSepScreenAngle ProcessBlack.100lpi.1200dpi/100 lpi / 1200 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.100lpi.1200dpi/100 lpi / 1200 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.100lpi.1200dpi/100 lpi / 1200 dpi: "15.0"
+*ColorSepScreenAngle ProcessMagenta.100lpi.1200dpi/100 lpi / 1200 dpi: "75.0"
+*ColorSepScreenAngle ProcessYellow.100lpi.1200dpi/100 lpi / 1200 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+*ColorSepScreenFreq CustomColor.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+*ColorSepScreenFreq ProcessCyan.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+*ColorSepScreenFreq ProcessMagenta.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+*ColorSepScreenFreq ProcessYellow.100lpi.1200dpi/100 lpi / 1200 dpi: "100.0"
+
+*% --- For 100 lpi / 1440 dpi ---
+*ColorSepScreenAngle ProcessBlack.100lpi.1440dpi/100 lpi / 1440 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.100lpi.1440dpi/100 lpi / 1440 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.100lpi.1440dpi/100 lpi / 1440 dpi: "15.0"
+*ColorSepScreenAngle ProcessMagenta.100lpi.1440dpi/100 lpi / 1440 dpi: "75.0"
+*ColorSepScreenAngle ProcessYellow.100lpi.1440dpi/100 lpi / 1440 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+*ColorSepScreenFreq CustomColor.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+*ColorSepScreenFreq ProcessCyan.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+*ColorSepScreenFreq ProcessMagenta.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+*ColorSepScreenFreq ProcessYellow.100lpi.1440dpi/100 lpi / 1440 dpi: "100.0"
+
+*% --- For 175 lpi / 2400 dpi ---
+*ColorSepScreenAngle ProcessBlack.175lpi.2400dpi/175 lpi / 2400 dpi: "45.0"
+*ColorSepScreenAngle CustomColor.175lpi.2400dpi/175 lpi / 2400 dpi: "45.0"
+*ColorSepScreenAngle ProcessCyan.175lpi.2400dpi/175 lpi / 2400 dpi: "15.0"
+*ColorSepScreenAngle ProcessMagenta.175lpi.2400dpi/175 lpi / 2400 dpi: "75.0"
+*ColorSepScreenAngle ProcessYellow.175lpi.2400dpi/175 lpi / 2400 dpi: "0.0"
+*ColorSepScreenFreq ProcessBlack.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+*ColorSepScreenFreq CustomColor.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+*ColorSepScreenFreq ProcessCyan.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+*ColorSepScreenFreq ProcessMagenta.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+*ColorSepScreenFreq ProcessYellow.175lpi.2400dpi/175 lpi / 2400 dpi: "175.0"
+
+*% Last Edit Date: March 24 2000
+*% end of PPD file
diff --git a/vcl/unx/generic/printer/configuration/psprint.conf b/vcl/unx/generic/printer/configuration/psprint.conf
new file mode 100644
index 000000000..1b56e084e
--- /dev/null
+++ b/vcl/unx/generic/printer/configuration/psprint.conf
@@ -0,0 +1,99 @@
+;
+; This file is part of the LibreOffice project.
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;
+; This file incorporates work covered by the following license notice:
+;
+; Licensed to the Apache Software Foundation (ASF) under one or more
+; contributor license agreements. See the NOTICE file distributed
+; with this work for additional information regarding copyright
+; ownership. The ASF licenses this file to you under the Apache
+; License, Version 2.0 (the "License"); you may not use this file
+; except in compliance with the License. You may obtain a copy of
+; the License at http://www.apache.org/licenses/LICENSE-2.0 .
+;
+[__Global_Printer_Defaults__]
+; Copies: the default number of copies produced
+; if key is absent the default is 1
+; Copies=1
+
+; Orientation: the default orientation of pages
+; possible Values: Portrait, Landscape
+; if key is absent the default is Portrait
+; Orientation=Portrait
+
+; Scale: the default scaling of output in percent
+; if key is absent the default is 100
+; Scale=100
+
+; MarginAdjust: the default adjustment to driver margins in 1/100 mm
+; MarginAdjust contains corrections for the driver defined margins
+; the values are comma separated
+; the order is: left,right,top,bottom
+; if key is absent the default is 0,0,0,0
+; MarginAdjust=0,0,0,0
+
+; ColorDepth: the default colordepth of the device in bits
+; possible values: 1, 8, 24
+; if key is absent the default is 24
+; ColorDepth=24
+
+; ColorDevice: the default setting whether the device is color capable
+; possible values: 0: driver setting, -1: grey scale, 1: color
+; if key is absent the default is 0
+; ColorDepth=0
+
+; PSLevel: the default setting of the PostScript level of the output
+; possible values: 0: driver setting, 1: level 1, 2: level2
+; if key is absent the default is 0
+; PSLevel=0
+
+; PPD_PageSize: the default page size to use. If a specific printer does
+; not support this page size its default is used instead.
+; possible values: A0, A1, A2, A3, A4, A5, A6, B4, B5, B6,
+; Legal, Letter, Executive, Statement, Tabloid,
+; Ledger, AnsiC, AnsiD, ARCHA, ARCHB, ARCHC,
+; ARCHD, ARCHE, EnvMonarch, EnvC4, EnvC5, EnvC6,
+; Env10, EnvC65, Folio
+; if key is absent the default value is driver specific
+; PPD_PageSize=A4
+
+
+[Generic Printer]
+; for every printer a group with at least the keys
+; "Printer" and "Command" is required
+
+; Printer: contains the base name of the PPD and the Printer name separated by /
+Printer=SGENPRT/Generic Printer
+
+; DefaultPrinter: marks the default printer
+DefaultPrinter=1
+
+; Location: a user readable string that will be shown in the print dialog
+Location=
+
+; Comment: a user readable string that will be shown in the print dialog
+Comment=
+
+; Command: a command line that accepts PostScript as standard input (pipe)
+; note: a shell will be started for the command
+Command=
+
+; QuickCommand: a command line that accepts PostScript as standard input (pipe)
+; this command line will be used instead of the command line given in the
+; "Command" key, if the user presses the direct print button. In this case
+; no print dialog should be shown, neither from the printing application nor
+; from the command line (example "kprinter --nodialog --stdin")
+; note: a shell will be started for the command
+;QuickCommand=
+
+; Features: a string containing additional comma separated properties of a printer
+; currently valid properties:
+; fax for a Fax printer queue
+; pdf=<dir> for a PDF printer where <dir> is the base directory for output files
+; external_dialog to notify that the print command of a printer will show a dialog
+; and therefore the application should not show its own dialog.
+;Features=
diff --git a/vcl/unx/generic/printer/cpdmgr.cxx b/vcl/unx/generic/printer/cpdmgr.cxx
new file mode 100644
index 000000000..e8b22111d
--- /dev/null
+++ b/vcl/unx/generic/printer/cpdmgr.cxx
@@ -0,0 +1,757 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <cstddef>
+#include <unistd.h>
+
+#include <unx/cpdmgr.hxx>
+
+#include <osl/file.h>
+#include <osl/thread.h>
+
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+
+#include <config_dbus.h>
+#include <config_gio.h>
+
+using namespace psp;
+using namespace osl;
+
+#if ENABLE_DBUS && ENABLE_GIO
+// Function to execute when name is acquired on the bus
+void CPDManager::onNameAcquired (GDBusConnection *connection,
+ const gchar *,
+ gpointer user_data)
+{
+ gchar* contents;
+ // Get Interface for introspection
+ if (!g_file_get_contents (FRONTEND_INTERFACE, &contents, nullptr, nullptr))
+ return;
+
+ GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr);
+
+ g_dbus_connection_register_object (connection,
+ "/org/libreoffice/PrintDialog",
+ introspection_data->interfaces[0],
+ nullptr,
+ nullptr, /* user_data */
+ nullptr, /* user_data_free_func */
+ nullptr); /* GError** */
+ g_free(contents);
+ g_dbus_node_info_unref(introspection_data);
+
+ CPDManager* current = static_cast<CPDManager*>(user_data);
+ std::vector<std::pair<std::string, gchar*>> backends = current->getTempBackends();
+ for (auto const& backend : backends)
+ {
+ // Get Interface for introspection
+ if (g_file_get_contents(BACKEND_INTERFACE, &contents, nullptr, nullptr))
+ {
+ introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr);
+ GDBusProxy *proxy = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ introspection_data->interfaces[0],
+ backend.first.c_str(),
+ backend.second,
+ "org.openprinting.PrintBackend",
+ nullptr,
+ nullptr);
+ g_assert (proxy != nullptr);
+ g_dbus_proxy_call(proxy, "ActivateBackend",
+ nullptr,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, nullptr, nullptr, nullptr);
+
+ g_free(contents);
+ g_object_unref(proxy);
+ g_dbus_node_info_unref(introspection_data);
+ }
+ g_free(backend.second);
+ }
+}
+
+void CPDManager::onNameLost (GDBusConnection *,
+ const gchar *name,
+ gpointer)
+{
+ g_message("Name Lost: %s", name);
+}
+
+void CPDManager::printerAdded (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ CPDManager* current = static_cast<CPDManager*>(user_data);
+ GDBusProxy *proxy;
+ proxy = current->getProxy(sender_name);
+ if (proxy == nullptr) {
+ gchar* contents;
+
+ // Get Interface for introspection
+ if (g_file_get_contents ("/usr/share/dbus-1/interfaces/org.openprinting.Backend.xml", &contents, nullptr, nullptr)) {
+ GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml (contents, nullptr);
+ proxy = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+ introspection_data->interfaces[0],
+ sender_name,
+ object_path,
+ interface_name,
+ nullptr,
+ nullptr);
+
+ g_free(contents);
+ g_dbus_node_info_unref(introspection_data);
+ std::pair<std::string, GDBusProxy *> new_backend (sender_name, proxy);
+ current->addBackend(std::move(new_backend));
+ }
+ }
+ CPDPrinter *pDest = static_cast<CPDPrinter *>(malloc(sizeof(CPDPrinter)));
+ pDest->backend = proxy;
+ g_variant_get (parameters, "(sssssbss)", &(pDest->id), &(pDest->name), &(pDest->info), &(pDest->location), &(pDest->make_and_model), &(pDest->is_accepting_jobs), &(pDest->printer_state), &(pDest->backend_name));
+ std::stringstream printerName;
+ printerName << pDest->name << ", " << pDest->backend_name;
+ std::stringstream uniqueName;
+ uniqueName << pDest->id << ", " << pDest->backend_name;
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OUString aPrinterName = OStringToOUString( printerName.str().c_str(), aEncoding );
+ OUString aUniqueName = OStringToOUString( uniqueName.str().c_str(), aEncoding );
+ current->addNewPrinter(aPrinterName, aUniqueName, pDest);
+}
+
+void CPDManager::printerRemoved (GDBusConnection *,
+ const gchar *,
+ const gchar *,
+ const gchar *,
+ const gchar *,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ // TODO: Remove every data linked to this particular printer.
+ CPDManager* pManager = static_cast<CPDManager*>(user_data);
+ char* id;
+ char* backend_name;
+ g_variant_get (parameters, "(ss)", &id, &backend_name);
+ std::stringstream uniqueName;
+ uniqueName << id << ", " << backend_name;
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OUString aUniqueName = OStringToOUString( uniqueName.str().c_str(), aEncoding );
+ std::unordered_map<OUString, CPDPrinter *>::iterator it = pManager->m_aCPDDestMap.find( aUniqueName );
+ if (it == pManager->m_aCPDDestMap.end()) {
+ SAL_WARN("vcl.unx.print", "CPD trying to remove non-existent printer from list");
+ return;
+ }
+ pManager->m_aCPDDestMap.erase(it);
+ std::unordered_map<OUString, Printer>::iterator printersIt = pManager->m_aPrinters.find( aUniqueName );
+ if (printersIt == pManager->m_aPrinters.end()) {
+ SAL_WARN("vcl.unx.print", "CPD trying to remove non-existent printer from m_aPrinters");
+ return;
+ }
+ pManager->m_aPrinters.erase(printersIt);
+}
+
+GDBusProxy* CPDManager::getProxy(const std::string& target)
+{
+ std::unordered_map<std::string, GDBusProxy *>::const_iterator it = m_pBackends.find(target);
+ if (it == m_pBackends.end()) {
+ return nullptr;
+ }
+ return it->second;
+}
+
+void CPDManager::addBackend(std::pair<std::string, GDBusProxy *> pair) {
+ m_pBackends.insert(pair);
+}
+
+void CPDManager::addTempBackend(const std::pair<std::string, gchar*>& pair)
+{
+ m_tBackends.push_back(pair);
+}
+
+std::vector<std::pair<std::string, gchar*>> const & CPDManager::getTempBackends() const {
+ return m_tBackends;
+}
+
+void CPDManager::addNewPrinter(const OUString& aPrinterName, const OUString& aUniqueName, CPDPrinter *pDest) {
+ m_aCPDDestMap[aUniqueName] = pDest;
+ bool bSetToGlobalDefaults = m_aPrinters.find( aUniqueName ) == m_aPrinters.end();
+ Printer aPrinter = m_aPrinters[ aUniqueName ];
+ if( bSetToGlobalDefaults )
+ aPrinter.m_aInfo = m_aGlobalDefaults;
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+
+ // TODO: I don't know how this should work when we have multiple
+ // sources with multiple possible defaults for each
+ // if( pDest->is_default )
+ // m_aDefaultPrinter = aPrinterName;
+
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ aPrinter.m_aInfo.m_aComment = OStringToOUString(pDest->info, aEncoding);
+ aPrinter.m_aInfo.m_aLocation = OStringToOUString(pDest->location, aEncoding);
+ // note: the parser that goes with the PrinterInfo
+ // is created implicitly by the JobData::operator=()
+ // when it detects the NULL ptr m_pParser.
+ // if we wanted to fill in the parser here this
+ // would mean we'd have to send a dbus message for each and
+ // every printer - which would be really bad runtime
+ // behaviour
+ aPrinter.m_aInfo.m_pParser = nullptr;
+ aPrinter.m_aInfo.m_aContext.setParser( nullptr );
+ std::unordered_map< OUString, PPDContext >::const_iterator c_it = m_aDefaultContexts.find( aUniqueName );
+ if( c_it != m_aDefaultContexts.end() )
+ {
+ aPrinter.m_aInfo.m_pParser = c_it->second.getParser();
+ aPrinter.m_aInfo.m_aContext = c_it->second;
+ }
+ aPrinter.m_aInfo.setDefaultBackend(true);
+ aPrinter.m_aInfo.m_aDriverName = "CPD:" + aUniqueName;
+ m_aPrinters[ aUniqueName ] = aPrinter;
+}
+#endif
+
+/*
+ * CPDManager class
+ */
+
+CPDManager* CPDManager::tryLoadCPD()
+{
+ CPDManager* pManager = nullptr;
+#if ENABLE_DBUS && ENABLE_GIO
+ static const char* pEnv = getenv("SAL_DISABLE_CPD");
+
+ if (!pEnv || !*pEnv) {
+ // interface description XML files are needed in 'onNameAcquired()'
+ if (!g_file_test(FRONTEND_INTERFACE, G_FILE_TEST_IS_REGULAR) ||
+ !g_file_test(BACKEND_INTERFACE, G_FILE_TEST_IS_REGULAR)) {
+ return nullptr;
+ }
+
+ GDir *dir;
+ const gchar *filename;
+ dir = g_dir_open(BACKEND_DIR, 0, nullptr);
+ if (dir != nullptr) {
+ while ((filename = g_dir_read_name(dir))) {
+ if (pManager == nullptr) {
+ pManager = new CPDManager();
+ }
+ gchar* contents;
+ std::stringstream filepath;
+ filepath << BACKEND_DIR << '/' << filename;
+ if (g_file_get_contents(filepath.str().c_str(), &contents, nullptr, nullptr))
+ {
+ std::pair<std::string, gchar*> new_tbackend (filename, contents);
+ pManager->addTempBackend(new_tbackend);
+ }
+ }
+ g_dir_close(dir);
+ }
+ }
+#endif
+ return pManager;
+}
+
+CPDManager::CPDManager() :
+ PrinterInfoManager( PrinterInfoManager::Type::CPD )
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ // Get Destinations number and pointers
+ GError *error = nullptr;
+ m_pConnection = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, &error);
+ g_assert_no_error (error);
+#endif
+}
+
+CPDManager::~CPDManager()
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ g_dbus_connection_emit_signal (m_pConnection,
+ nullptr,
+ "/org/libreoffice/PrintDialog",
+ "org.openprinting.PrintFrontend",
+ "StopListing",
+ nullptr,
+ nullptr);
+ g_dbus_connection_flush_sync (m_pConnection,
+ nullptr,
+ nullptr);
+ g_dbus_connection_close_sync (m_pConnection,
+ nullptr,
+ nullptr);
+ for (auto const& backend : m_pBackends)
+ {
+ g_object_unref(backend.second);
+ }
+ for (auto const& backend : m_aCPDDestMap)
+ {
+ free(backend.second);
+ }
+#endif
+}
+
+
+const PPDParser* CPDManager::createCPDParser( const OUString& rPrinter )
+{
+ const PPDParser* pNewParser = nullptr;
+#if ENABLE_DBUS && ENABLE_GIO
+ OUString aPrinter;
+
+ if( rPrinter.startsWith("CPD:") )
+ aPrinter = rPrinter.copy( 4 );
+ else
+ aPrinter = rPrinter;
+
+ std::unordered_map< OUString, CPDPrinter * >::iterator dest_it =
+ m_aCPDDestMap.find( aPrinter );
+
+ if( dest_it != m_aCPDDestMap.end() )
+ {
+ CPDPrinter* pDest = dest_it->second;
+ GVariant* ret = nullptr;
+ GError* error = nullptr;
+ ret = g_dbus_proxy_call_sync (pDest->backend, "GetAllOptions",
+ g_variant_new("(s)", (pDest->id)),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, nullptr, &error);
+ if (ret != nullptr && error == nullptr)
+ {
+ // TODO: These keys need to be redefined to preserve usage across libreoffice
+ // InputSlot - media-col.media-source?
+ // Font - not needed now as it is required only for ps and we are using pdf
+ // Dial? - for FAX (need to look up PWG spec)
+
+ int num_attribute;
+ GVariantIter *iter_attr, *iter_supported_values;
+ g_variant_get (ret, "(ia(ssia(s)))", &num_attribute, &iter_attr);
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ PPDKey *pKey = nullptr;
+ OUString aValueName;
+ PPDValue* pValue;
+ std::vector<PPDKey*> keys;
+ std::vector<OUString> default_values;
+ for (int i = 0; i < num_attribute; i++) {
+ char *name, *default_value;
+ int num_supported_values;
+ g_variant_iter_loop(iter_attr, "(ssia(s))",
+ &name, &default_value,
+ &num_supported_values, &iter_supported_values);
+ OUString aOptionName = OStringToOUString( name, aEncoding );
+ OUString aDefaultValue = OStringToOUString( default_value, aEncoding );
+ if (aOptionName == "sides") {
+ // Duplex key is used throughout for checking Duplex Support
+ aOptionName = OUString("Duplex");
+ } else if (aOptionName == "printer-resolution") {
+ // Resolution key is used in places
+ aOptionName = OUString("Resolution");
+ } else if (aOptionName == "media") {
+ // PageSize key is used in many places
+ aOptionName = OUString("PageSize");
+ }
+ default_values.push_back(aDefaultValue);
+ pKey = new PPDKey( aOptionName );
+
+ // If number of values are 0, this is not settable via UI
+ if (num_supported_values > 0 && aDefaultValue != "NA")
+ pKey->m_bUIOption = true;
+
+ bool bDefaultFound = false;
+
+ for (int j = 0; j < num_supported_values; j++) {
+ char* value;
+ g_variant_iter_loop(iter_supported_values, "(s)", &value);
+ aValueName = OStringToOUString( value, aEncoding );
+ if (aOptionName == "Duplex") {
+ // Duplex key matches against very specific Values
+ if (aValueName == "one-sided") {
+ aValueName = OUString("None");
+ } else if (aValueName == "two-sided-long-edge") {
+ aValueName = OUString("DuplexNoTumble");
+ } else if (aValueName == "two-sided-short-edge") {
+ aValueName = OUString("DuplexTumble");
+ }
+ }
+
+ pValue = pKey->insertValue( aValueName, eQuoted );
+ if( ! pValue )
+ continue;
+ pValue->m_aValue = aValueName;
+
+ if (aValueName.equals(aDefaultValue)) {
+ pKey->m_pDefaultValue = pValue;
+ bDefaultFound = true;
+ }
+
+ }
+ // This could be done to ensure default values also appear as options:
+ if (!bDefaultFound && pKey->m_bUIOption) {
+ // pValue = pKey->insertValue( aDefaultValue, eQuoted );
+ // if( pValue )
+ // pValue->m_aValue = aDefaultValue;
+ }
+ keys.emplace_back(pKey);
+ }
+
+ pKey = new PPDKey("ModelName");
+ aValueName = OStringToOUString( "", aEncoding );
+ pValue = pKey->insertValue( aValueName, eQuoted );
+ if( pValue )
+ pValue->m_aValue = aValueName;
+ pKey->m_pDefaultValue = pValue;
+ keys.emplace_back(pKey);
+
+ pKey = new PPDKey("NickName");
+ aValueName = OStringToOUString( pDest->name, aEncoding );
+ pValue = pKey->insertValue( aValueName, eQuoted );
+ if( pValue )
+ pValue->m_aValue = aValueName;
+ pKey->m_pDefaultValue = pValue;
+ keys.emplace_back(pKey);
+
+ pNewParser = new PPDParser(aPrinter, keys);
+ PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
+ PPDContext& rContext = m_aDefaultContexts[ aPrinter ];
+ rContext.setParser( pNewParser );
+ setDefaultPaper( rContext );
+ std::vector<OUString>::iterator defit = default_values.begin();
+ for (auto const& key : keys)
+ {
+ const PPDValue* p1Value = key->getValue( *defit );
+ if( p1Value )
+ {
+ if( p1Value != key->getDefaultValue() )
+ {
+ rContext.setValue( key, p1Value, true );
+ SAL_INFO("vcl.unx.print", "key " << pKey->getKey() << " is set to " << *defit);
+ }
+ else
+ SAL_INFO("vcl.unx.print", "key " << pKey->getKey() << " is defaulted to " << *defit);
+ }
+ ++defit;
+ }
+
+ rInfo.m_pParser = pNewParser;
+ rInfo.m_aContext = rContext;
+ g_variant_unref(ret);
+ }
+ else
+ {
+ g_clear_error(&error);
+ SAL_INFO("vcl.unx.print", "CPD GetAllOptions failed, falling back to generic driver");
+ }
+ }
+ else
+ SAL_INFO("vcl.unx.print", "no dest found for printer " << aPrinter);
+
+ if( ! pNewParser )
+ {
+ // get the default PPD
+ pNewParser = PPDParser::getParser( "SGENPRT" );
+ SAL_WARN("vcl.unx.print", "Parsing default SGENPRT PPD" );
+
+ PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
+
+ rInfo.m_pParser = pNewParser;
+ rInfo.m_aContext.setParser( pNewParser );
+ }
+#else
+ (void)rPrinter;
+#endif
+ return pNewParser;
+}
+
+
+void CPDManager::initialize()
+{
+ // get normal printers, clear printer list
+ PrinterInfoManager::initialize();
+#if ENABLE_DBUS && ENABLE_GIO
+ g_bus_own_name_on_connection (m_pConnection,
+ "org.libreoffice.print-dialog",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ onNameAcquired,
+ onNameLost,
+ this,
+ nullptr);
+
+ g_dbus_connection_signal_subscribe (m_pConnection, // DBus Connection
+ nullptr, // Sender Name
+ "org.openprinting.PrintBackend", // Sender Interface
+ "PrinterAdded", // Signal Name
+ nullptr, // Object Path
+ nullptr, // arg0 behaviour
+ G_DBUS_SIGNAL_FLAGS_NONE, // Signal Flags
+ printerAdded, // Callback Function
+ this,
+ nullptr);
+ g_dbus_connection_signal_subscribe (m_pConnection, // DBus Connection
+ nullptr, // Sender Name
+ "org.openprinting.PrintBackend", // Sender Interface
+ "PrinterRemoved", // Signal Name
+ nullptr, // Object Path
+ nullptr, // arg0 behaviour
+ G_DBUS_SIGNAL_FLAGS_NONE, // Signal Flags
+ printerRemoved, // Callback Function
+ this,
+ nullptr);
+
+ // remove everything that is not a CUPS printer and not
+ // a special purpose printer (PDF, Fax)
+ std::unordered_map< OUString, Printer >::iterator it = m_aPrinters.begin();
+ while (it != m_aPrinters.end())
+ {
+ if( m_aCPDDestMap.find( it->first ) != m_aCPDDestMap.end() )
+ {
+ ++it;
+ continue;
+ }
+
+ if( !it->second.m_aInfo.m_aFeatures.isEmpty() )
+ {
+ ++it;
+ continue;
+ }
+ it = m_aPrinters.erase(it);
+ }
+#endif
+}
+
+void CPDManager::setupJobContextData( JobData& rData )
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ std::unordered_map<OUString, CPDPrinter *>::iterator dest_it =
+ m_aCPDDestMap.find( rData.m_aPrinterName );
+
+ if( dest_it == m_aCPDDestMap.end() )
+ return PrinterInfoManager::setupJobContextData( rData );
+
+ std::unordered_map< OUString, Printer >::iterator p_it =
+ m_aPrinters.find( rData.m_aPrinterName );
+ if( p_it == m_aPrinters.end() ) // huh ?
+ {
+ SAL_WARN("vcl.unx.print", "CPD printer list in disorder, "
+ "no dest for printer " << rData.m_aPrinterName);
+ return;
+ }
+
+ if( p_it->second.m_aInfo.m_pParser == nullptr )
+ {
+ // in turn calls createCPDParser
+ // which updates the printer info
+ p_it->second.m_aInfo.m_pParser = PPDParser::getParser( p_it->second.m_aInfo.m_aDriverName );
+ }
+ if( p_it->second.m_aInfo.m_aContext.getParser() == nullptr )
+ {
+ OUString aPrinter;
+ if( p_it->second.m_aInfo.m_aDriverName.startsWith("CPD:") )
+ aPrinter = p_it->second.m_aInfo.m_aDriverName.copy( 4 );
+ else
+ aPrinter = p_it->second.m_aInfo.m_aDriverName;
+
+ p_it->second.m_aInfo.m_aContext = m_aDefaultContexts[ aPrinter ];
+ }
+
+ rData.m_pParser = p_it->second.m_aInfo.m_pParser;
+ rData.m_aContext = p_it->second.m_aInfo.m_aContext;
+#else
+ (void)rData;
+#endif
+}
+
+FILE* CPDManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ SAL_INFO( "vcl.unx.print", "startSpool: " << rPrintername << " " << (bQuickCommand ? "true" : "false") );
+ if( m_aCPDDestMap.find( rPrintername ) == m_aCPDDestMap.end() )
+ {
+ SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::startSpool" );
+ return PrinterInfoManager::startSpool( rPrintername, bQuickCommand );
+ }
+ OUString aTmpURL, aTmpFile;
+ osl_createTempFile( nullptr, nullptr, &aTmpURL.pData );
+ osl_getSystemPathFromFileURL( aTmpURL.pData, &aTmpFile.pData );
+ OString aSysFile = OUStringToOString( aTmpFile, osl_getThreadTextEncoding() );
+ FILE* fp = fopen( aSysFile.getStr(), "w" );
+ if( fp )
+ m_aSpoolFiles[fp] = aSysFile;
+
+ return fp;
+#else
+ (void)rPrintername;
+ (void)bQuickCommand;
+ return nullptr;
+#endif
+}
+
+#if ENABLE_DBUS && ENABLE_GIO
+void CPDManager::getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, const OString& rJobName, int& rNumOptions, GVariant **arr )
+{
+ GVariantBuilder *builder;
+ builder = g_variant_builder_new(G_VARIANT_TYPE("a(ss)"));
+ g_variant_builder_add(builder, "(ss)", "job-name", rJobName.getStr());
+ if( rJob.m_pParser == rJob.m_aContext.getParser() && rJob.m_pParser ) {
+ std::size_t i;
+ std::size_t nKeys = rJob.m_aContext.countValuesModified();
+ ::std::vector< const PPDKey* > aKeys( nKeys );
+ for( i = 0; i < nKeys; i++ )
+ aKeys[i] = rJob.m_aContext.getModifiedKey( i );
+ for( i = 0; i < nKeys; i++ ) {
+ const PPDKey* pKey = aKeys[i];
+ const PPDValue* pValue = rJob.m_aContext.getValue( pKey );
+ OUString sPayLoad;
+ if (pValue) {
+ sPayLoad = pValue->m_bCustomOption ? pValue->m_aCustomOption : pValue->m_aOption;
+ }
+ if (!sPayLoad.isEmpty()) {
+ OString aKey = OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US );
+ OString aValue = OUStringToOString( sPayLoad, RTL_TEXTENCODING_ASCII_US );
+ if (aKey.equals("Duplex")) {
+ aKey = OString("sides");
+ } else if (aKey.equals("Resolution")) {
+ aKey = OString("printer-resolution");
+ } else if (aKey.equals("PageSize")) {
+ aKey = OString("media");
+ }
+ if (aKey.equals("sides")) {
+ if (aValue.equals("None")) {
+ aValue = OString("one-sided");
+ } else if (aValue.equals("DuplexNoTumble")) {
+ aValue = OString("two-sided-long-edge");
+ } else if (aValue.equals("DuplexTumble")) {
+ aValue = OString("two-sided-short-edge");
+ }
+ }
+ g_variant_builder_add(builder, "(ss)", aKey.getStr(), aValue.getStr());
+ }
+ }
+ }
+ if( rJob.m_nPDFDevice > 0 && rJob.m_nCopies > 1 )
+ {
+ OString aVal( OString::number( rJob.m_nCopies ) );
+ g_variant_builder_add(builder, "(ss)", "copies", aVal.getStr());
+ rNumOptions++;
+ // TODO: something for collate
+ // Maybe this is the equivalent ipp attribute:
+ if (rJob.m_bCollate) {
+ g_variant_builder_add(builder, "(ss)", "multiple-document-handling", "separate-documents-collated-copies");
+ } else {
+ g_variant_builder_add(builder, "(ss)", "multiple-document-handling", "separate-documents-uncollated-copies");
+ }
+ rNumOptions++;
+ }
+ if( ! bBanner )
+ {
+ g_variant_builder_add(builder, "(ss)", "job-sheets", "none");
+ rNumOptions++;
+ }
+ if (rJob.m_eOrientation == orientation::Portrait) {
+ g_variant_builder_add(builder, "(ss)", "orientation-requested", "portrait");
+ rNumOptions++;
+ } else if (rJob.m_eOrientation == orientation::Landscape) {
+ g_variant_builder_add(builder, "(ss)", "orientation-requested", "landscape");
+ rNumOptions++;
+ }
+ (*arr) = g_variant_new("a(ss)", builder);
+ g_variant_builder_unref(builder);
+}
+#endif
+
+bool CPDManager::endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber )
+{
+ bool success = false;
+#if ENABLE_DBUS && ENABLE_GIO
+ SAL_INFO( "vcl.unx.print", "endSpool: " << rPrintername << "," << rJobTitle << " copy count = " << rDocumentJobData.m_nCopies );
+ std::unordered_map< OUString, CPDPrinter * >::iterator dest_it =
+ m_aCPDDestMap.find( rPrintername );
+ if( dest_it == m_aCPDDestMap.end() )
+ {
+ SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::endSpool" );
+ return PrinterInfoManager::endSpool( rPrintername, rJobTitle, pFile, rDocumentJobData, bBanner, rFaxNumber );
+ }
+
+ std::unordered_map< FILE*, OString, FPtrHash >::const_iterator it = m_aSpoolFiles.find( pFile );
+ if( it != m_aSpoolFiles.end() )
+ {
+ fclose( pFile );
+ rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
+ OString sJobName(OUStringToOString(rJobTitle, aEnc));
+ if (!rFaxNumber.isEmpty())
+ {
+ sJobName = OUStringToOString(rFaxNumber, aEnc);
+ }
+ OString aSysFile = it->second;
+ CPDPrinter* pDest = dest_it->second;
+ GVariant* ret;
+ gint job_id;
+ int nNumOptions = 0;
+ GVariant *pArr = nullptr;
+ getOptionsFromDocumentSetup( rDocumentJobData, bBanner, sJobName, nNumOptions, &pArr );
+ ret = g_dbus_proxy_call_sync (pDest->backend, "printFile",
+ g_variant_new(
+ "(ssi@a(ss))",
+ (pDest->id),
+ aSysFile.getStr(),
+ nNumOptions,
+ pArr
+ ),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, nullptr, nullptr);
+ g_variant_get (ret, "(i)", &job_id);
+ if (job_id != -1) {
+ success = true;
+ }
+ g_variant_unref(ret);
+ unlink( it->second.getStr() );
+ m_aSpoolFiles.erase(it);
+ }
+#else
+ (void)rPrintername;
+ (void)rJobTitle;
+ (void)pFile;
+ (void)rDocumentJobData;
+ (void)bBanner;
+ (void)rFaxNumber;
+#endif
+ return success;
+}
+
+bool CPDManager::checkPrintersChanged( bool )
+{
+#if ENABLE_DBUS && ENABLE_GIO
+ bool bChanged = m_aPrintersChanged;
+ m_aPrintersChanged = false;
+ g_dbus_connection_emit_signal (m_pConnection,
+ nullptr,
+ "/org/libreoffice/PrintDialog",
+ "org.openprinting.PrintFrontend",
+ "RefreshBackend",
+ nullptr,
+ nullptr);
+ return bChanged;
+#else
+ return false;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+
diff --git a/vcl/unx/generic/printer/cupsmgr.cxx b/vcl/unx/generic/printer/cupsmgr.cxx
new file mode 100644
index 000000000..6acfe1db6
--- /dev/null
+++ b/vcl/unx/generic/printer/cupsmgr.cxx
@@ -0,0 +1,964 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cups/cups.h>
+#include <cups/http.h>
+#include <cups/ipp.h>
+#include <cups/ppd.h>
+
+#include <unistd.h>
+
+#include <unx/cupsmgr.hxx>
+
+#include <o3tl/string_view.hxx>
+#include <osl/thread.h>
+#include <osl/file.h>
+#include <osl/conditn.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+
+#include <officecfg/Office/Common.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/window.hxx>
+
+#include <algorithm>
+#include <cstddef>
+#include <string_view>
+
+using namespace psp;
+using namespace osl;
+
+namespace {
+
+struct GetPPDAttribs
+{
+ osl::Condition m_aCondition;
+ OString m_aParameter;
+ OString m_aResult;
+ int m_nRefs;
+ bool* m_pResetRunning;
+ osl::Mutex* m_pSyncMutex;
+
+ GetPPDAttribs( const char * m_pParameter,
+ bool* pResetRunning, osl::Mutex* pSyncMutex )
+ : m_aParameter( m_pParameter ),
+ m_pResetRunning( pResetRunning ),
+ m_pSyncMutex( pSyncMutex )
+ {
+ m_nRefs = 2;
+ m_aCondition.reset();
+ }
+
+ ~GetPPDAttribs()
+ {
+ if( !m_aResult.isEmpty() )
+ unlink( m_aResult.getStr() );
+ }
+
+ void unref()
+ {
+ if( --m_nRefs == 0 )
+ {
+ *m_pResetRunning = false;
+ delete this;
+ }
+ }
+
+ void executeCall()
+ {
+ // This CUPS method is not at all thread-safe we need
+ // to dup the pointer to a static buffer it returns ASAP
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ const char* pResult = cupsGetPPD(m_aParameter.getStr());
+ OString aResult = pResult ? OString(pResult) : OString();
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+ MutexGuard aGuard( *m_pSyncMutex );
+ m_aResult = aResult;
+ m_aCondition.set();
+ unref();
+ }
+
+ OString waitResult( TimeValue const *pDelay )
+ {
+ m_pSyncMutex->release();
+
+ if (m_aCondition.wait( pDelay ) != Condition::result_ok
+ )
+ {
+ SAL_WARN("vcl.unx.print",
+ "cupsGetPPD " << m_aParameter << " timed out");
+ }
+ m_pSyncMutex->acquire();
+
+ OString aRetval = m_aResult;
+ m_aResult.clear();
+ unref();
+
+ return aRetval;
+ }
+};
+
+}
+
+extern "C" {
+ static void getPPDWorker(void* pData)
+ {
+ osl_setThreadName("CUPSManager getPPDWorker");
+ GetPPDAttribs* pAttribs = static_cast<GetPPDAttribs*>(pData);
+ pAttribs->executeCall();
+ }
+}
+
+OString CUPSManager::threadedCupsGetPPD( const char* pPrinter )
+{
+ OString aResult;
+
+ m_aGetPPDMutex.acquire();
+ // if one thread hangs in cupsGetPPD already, don't start another
+ if( ! m_bPPDThreadRunning )
+ {
+ m_bPPDThreadRunning = true;
+ GetPPDAttribs* pAttribs = new GetPPDAttribs( pPrinter,
+ &m_bPPDThreadRunning,
+ &m_aGetPPDMutex );
+
+ oslThread aThread = osl_createThread( getPPDWorker, pAttribs );
+
+ TimeValue aValue;
+ aValue.Seconds = 5;
+ aValue.Nanosec = 0;
+
+ // NOTE: waitResult release and acquires the GetPPD mutex
+ aResult = pAttribs->waitResult( &aValue );
+ osl_destroyThread( aThread );
+ }
+ m_aGetPPDMutex.release();
+
+ return aResult;
+}
+
+static const char* setPasswordCallback( const char* /*pIn*/ )
+{
+ const char* pRet = nullptr;
+
+ PrinterInfoManager& rMgr = PrinterInfoManager::get();
+ if( rMgr.getType() == PrinterInfoManager::Type::CUPS ) // sanity check
+ pRet = static_cast<CUPSManager&>(rMgr).authenticateUser();
+ return pRet;
+}
+
+/*
+ * CUPSManager class
+ */
+
+CUPSManager* CUPSManager::tryLoadCUPS()
+{
+ CUPSManager* pManager = nullptr;
+ static const char* pEnv = getenv("SAL_DISABLE_CUPS");
+
+ if (!pEnv || !*pEnv)
+ pManager = new CUPSManager();
+ return pManager;
+}
+
+extern "C"
+{
+static void run_dest_thread_stub( void* pThis )
+{
+ osl_setThreadName("CUPSManager cupsGetDests");
+ CUPSManager::runDestThread( pThis );
+}
+}
+
+CUPSManager::CUPSManager() :
+ PrinterInfoManager( PrinterInfoManager::Type::CUPS ),
+ m_nDests( 0 ),
+ m_pDests( nullptr ),
+ m_bNewDests( false ),
+ m_bPPDThreadRunning( false )
+{
+ m_aDestThread = osl_createThread( run_dest_thread_stub, this );
+}
+
+CUPSManager::~CUPSManager()
+{
+ if( m_aDestThread )
+ {
+ osl_joinWithThread( m_aDestThread );
+ osl_destroyThread( m_aDestThread );
+ }
+
+ if (m_nDests && m_pDests)
+ cupsFreeDests( m_nDests, static_cast<cups_dest_t*>(m_pDests) );
+}
+
+void CUPSManager::runDestThread( void* pThis )
+{
+ static_cast<CUPSManager*>(pThis)->runDests();
+}
+
+void CUPSManager::runDests()
+{
+ SAL_INFO("vcl.unx.print", "starting cupsGetDests");
+ cups_dest_t* pDests = nullptr;
+
+ // n#722902 - do a fast-failing check for cups working *at all* first
+ http_t* p_http;
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ if( (p_http=httpConnectEncrypt(
+ cupsServer(),
+ ippPort(),
+ cupsEncryption())) == nullptr )
+ return;
+
+ int nDests = cupsGetDests2(p_http, &pDests);
+ SAL_INFO("vcl.unx.print", "came out of cupsGetDests");
+
+ osl::MutexGuard aGuard( m_aCUPSMutex );
+ m_nDests = nDests;
+ m_pDests = pDests;
+ m_bNewDests = true;
+ SAL_INFO("vcl.unx.print", "finished cupsGetDests");
+
+ httpClose(p_http);
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+}
+
+void CUPSManager::initialize()
+{
+ // get normal printers, clear printer list
+ PrinterInfoManager::initialize();
+
+ // check whether thread has completed
+ // if not behave like old printing system
+ osl::MutexGuard aGuard( m_aCUPSMutex );
+
+ if( ! m_bNewDests )
+ return;
+
+ // dest thread has run, clean up
+ if( m_aDestThread )
+ {
+ osl_joinWithThread( m_aDestThread );
+ osl_destroyThread( m_aDestThread );
+ m_aDestThread = nullptr;
+ }
+ m_bNewDests = false;
+
+ // clear old stuff
+ m_aCUPSDestMap.clear();
+
+ if( ! (m_nDests && m_pDests ) )
+ return;
+
+ // check for CUPS server(?) > 1.2
+ // since there is no API to query, check for options that were
+ // introduced in dests with 1.2
+ // this is needed to check for %%IncludeFeature support
+ // (#i65684#, #i65491#)
+ bool bUsePDF = false;
+ cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests);
+ const char* pOpt = cupsGetOption( "printer-info",
+ pDest->num_options,
+ pDest->options );
+ if( pOpt )
+ {
+ m_bUseIncludeFeature = true;
+ bUsePDF = officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::get();
+ }
+
+ m_aGlobalDefaults.setDefaultBackend(bUsePDF);
+
+ // do not send include JobPatch; CUPS will insert that itself
+ // TODO: currently unknown which versions of CUPS insert JobPatches
+ // so currently it is assumed CUPS = don't insert JobPatch files
+ m_bUseJobPatch = false;
+
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ int nPrinter = m_nDests;
+
+ // reset global default PPD options; these are queried on demand from CUPS
+ m_aGlobalDefaults.m_pParser = nullptr;
+ m_aGlobalDefaults.m_aContext = PPDContext();
+
+ // add CUPS printers, should there be a printer
+ // with the same name as a CUPS printer, overwrite it
+ while( nPrinter-- )
+ {
+ pDest = static_cast<cups_dest_t*>(m_pDests)+nPrinter;
+ OUString aPrinterName = OStringToOUString( pDest->name, aEncoding );
+ if( pDest->instance && *pDest->instance )
+ {
+ aPrinterName += "/" +
+ OStringToOUString( pDest->instance, aEncoding );
+ }
+
+ // initialize printer with possible configuration from psprint.conf
+ bool bSetToGlobalDefaults = m_aPrinters.find( aPrinterName ) == m_aPrinters.end();
+ Printer aPrinter = m_aPrinters[ aPrinterName ];
+ if( bSetToGlobalDefaults )
+ aPrinter.m_aInfo = m_aGlobalDefaults;
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+ if( pDest->is_default )
+ m_aDefaultPrinter = aPrinterName;
+
+ for( int k = 0; k < pDest->num_options; k++ )
+ {
+ if(!strcmp(pDest->options[k].name, "printer-info"))
+ aPrinter.m_aInfo.m_aComment=OStringToOUString(pDest->options[k].value, aEncoding);
+ if(!strcmp(pDest->options[k].name, "printer-location"))
+ aPrinter.m_aInfo.m_aLocation=OStringToOUString(pDest->options[k].value, aEncoding);
+ if(!strcmp(pDest->options[k].name, "auth-info-required"))
+ aPrinter.m_aInfo.m_aAuthInfoRequired=OStringToOUString(pDest->options[k].value, aEncoding);
+ }
+
+ // note: the parser that goes with the PrinterInfo
+ // is created implicitly by the JobData::operator=()
+ // when it detects the NULL ptr m_pParser.
+ // if we wanted to fill in the parser here this
+ // would mean we'd have to download PPDs for each and
+ // every printer - which would be really bad runtime
+ // behaviour
+ aPrinter.m_aInfo.m_pParser = nullptr;
+ aPrinter.m_aInfo.m_aContext.setParser( nullptr );
+ std::unordered_map< OUString, PPDContext >::const_iterator c_it = m_aDefaultContexts.find( aPrinterName );
+ if( c_it != m_aDefaultContexts.end() )
+ {
+ aPrinter.m_aInfo.m_pParser = c_it->second.getParser();
+ aPrinter.m_aInfo.m_aContext = c_it->second;
+ }
+ aPrinter.m_aInfo.setDefaultBackend(bUsePDF);
+ aPrinter.m_aInfo.m_aDriverName = "CUPS:" + aPrinterName;
+
+ m_aPrinters[ aPrinter.m_aInfo.m_aPrinterName ] = aPrinter;
+ m_aCUPSDestMap[ aPrinter.m_aInfo.m_aPrinterName ] = nPrinter;
+ }
+
+ // remove everything that is not a CUPS printer and not
+ // a special purpose printer (PDF, Fax)
+ std::unordered_map< OUString, Printer >::iterator it = m_aPrinters.begin();
+ while(it != m_aPrinters.end())
+ {
+ if( m_aCUPSDestMap.find( it->first ) != m_aCUPSDestMap.end() )
+ {
+ ++it;
+ continue;
+ }
+
+ if( !it->second.m_aInfo.m_aFeatures.isEmpty() )
+ {
+ ++it;
+ continue;
+ }
+ it = m_aPrinters.erase(it);
+ }
+
+ cupsSetPasswordCB( setPasswordCallback );
+}
+
+static void updatePrinterContextInfo( ppd_group_t* pPPDGroup, PPDContext& rContext )
+{
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ for( int i = 0; i < pPPDGroup->num_options; i++ )
+ {
+ ppd_option_t* pOption = pPPDGroup->options + i;
+ for( int n = 0; n < pOption->num_choices; n++ )
+ {
+ ppd_choice_t* pChoice = pOption->choices + n;
+ if( pChoice->marked )
+ {
+ const PPDKey* pKey = rContext.getParser()->getKey( OStringToOUString( pOption->keyword, aEncoding ) );
+ if( pKey )
+ {
+ const PPDValue* pValue = pKey->getValue( OStringToOUString( pChoice->choice, aEncoding ) );
+ if( pValue )
+ {
+ if( pValue != pKey->getDefaultValue() )
+ {
+ rContext.setValue( pKey, pValue, true );
+ SAL_INFO("vcl.unx.print", "key " << pOption->keyword << " is set to " << pChoice->choice);
+
+ }
+ else
+ SAL_INFO("vcl.unx.print", "key " << pOption->keyword << " is defaulted to " << pChoice->choice);
+ }
+ else
+ SAL_INFO("vcl.unx.print", "caution: value " << pChoice->choice << " not found in key " << pOption->keyword);
+ }
+ else
+ SAL_INFO("vcl.unx.print", "caution: key " << pOption->keyword << " not found in parser");
+ }
+ }
+ }
+
+ // recurse through subgroups
+ for( int g = 0; g < pPPDGroup->num_subgroups; g++ )
+ {
+ updatePrinterContextInfo( pPPDGroup->subgroups + g, rContext );
+ }
+}
+
+const PPDParser* CUPSManager::createCUPSParser( const OUString& rPrinter )
+{
+ const PPDParser* pNewParser = nullptr;
+ OUString aPrinter;
+
+ if( rPrinter.startsWith("CUPS:") )
+ aPrinter = rPrinter.copy( 5 );
+ else
+ aPrinter = rPrinter;
+
+ if( m_aCUPSMutex.tryToAcquire() )
+ {
+ if (m_nDests && m_pDests)
+ {
+ std::unordered_map< OUString, int >::iterator dest_it =
+ m_aCUPSDestMap.find( aPrinter );
+ if( dest_it != m_aCUPSDestMap.end() )
+ {
+ cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests) + dest_it->second;
+ OString aPPDFile = threadedCupsGetPPD( pDest->name );
+ SAL_INFO("vcl.unx.print",
+ "PPD for " << aPrinter << " is " << aPPDFile);
+ if( !aPPDFile.isEmpty() )
+ {
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OUString aFileName( OStringToOUString( aPPDFile, aEncoding ) );
+ // update the printer info with context information
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ ppd_file_t* pPPD = ppdOpenFile( aPPDFile.getStr() );
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+ if( pPPD )
+ {
+ // create the new parser
+ PPDParser* pCUPSParser = new PPDParser( aFileName );
+ pCUPSParser->m_aFile = rPrinter;
+ pNewParser = pCUPSParser;
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ /*int nConflicts =*/ cupsMarkOptions( pPPD, pDest->num_options, pDest->options );
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+ SAL_INFO("vcl.unx.print", "processing the following options for printer " << pDest->name << " (instance " << (pDest->instance == nullptr ? "null" : pDest->instance) << "):");
+ for( int k = 0; k < pDest->num_options; k++ )
+ SAL_INFO("vcl.unx.print",
+ " \"" << pDest->options[k].name <<
+ "\" = \"" << pDest->options[k].value << "\"");
+ PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
+
+ // remember the default context for later use
+ PPDContext& rContext = m_aDefaultContexts[ aPrinter ];
+ rContext.setParser( pNewParser );
+ // set system default paper; printer CUPS PPD options
+ // may overwrite it
+ setDefaultPaper( rContext );
+ for( int i = 0; i < pPPD->num_groups; i++ )
+ updatePrinterContextInfo( pPPD->groups + i, rContext );
+
+ rInfo.m_pParser = pNewParser;
+ rInfo.m_aContext = rContext;
+
+ // clean up the mess
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ ppdClose( pPPD );
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+ }
+ else
+ SAL_INFO("vcl.unx.print", "ppdOpenFile failed, falling back to generic driver");
+
+ // remove temporary PPD file
+ if (!getenv("SAL_CUPS_PPD_RETAIN_TMP"))
+ unlink( aPPDFile.getStr() );
+ }
+ else
+ SAL_INFO("vcl.unx.print", "cupsGetPPD failed, falling back to generic driver");
+ }
+ else
+ SAL_INFO("vcl.unx.print", "no dest found for printer " << aPrinter);
+ }
+ m_aCUPSMutex.release();
+ }
+ else
+ SAL_WARN("vcl.unx.print", "could not acquire CUPS mutex !!!" );
+
+ if( ! pNewParser )
+ {
+ // get the default PPD
+ pNewParser = PPDParser::getParser( "SGENPRT" );
+ SAL_INFO("vcl.unx.print", "Parsing default SGENPRT PPD" );
+
+ PrinterInfo& rInfo = m_aPrinters[ aPrinter ].m_aInfo;
+
+ rInfo.m_pParser = pNewParser;
+ rInfo.m_aContext.setParser( pNewParser );
+ }
+
+ return pNewParser;
+}
+
+void CUPSManager::setupJobContextData( JobData& rData )
+{
+ std::unordered_map< OUString, int >::iterator dest_it =
+ m_aCUPSDestMap.find( rData.m_aPrinterName );
+
+ if( dest_it == m_aCUPSDestMap.end() )
+ return PrinterInfoManager::setupJobContextData( rData );
+
+ std::unordered_map< OUString, Printer >::iterator p_it =
+ m_aPrinters.find( rData.m_aPrinterName );
+ if( p_it == m_aPrinters.end() ) // huh ?
+ {
+ SAL_WARN("vcl.unx.print", "CUPS printer list in disorder, "
+ "no dest for printer " << rData.m_aPrinterName);
+ return;
+ }
+
+ if( p_it->second.m_aInfo.m_pParser == nullptr )
+ {
+ // in turn calls createCUPSParser
+ // which updates the printer info
+ p_it->second.m_aInfo.m_pParser = PPDParser::getParser( p_it->second.m_aInfo.m_aDriverName );
+ }
+ if( p_it->second.m_aInfo.m_aContext.getParser() == nullptr )
+ {
+ OUString aPrinter;
+ if( p_it->second.m_aInfo.m_aDriverName.startsWith("CUPS:") )
+ aPrinter = p_it->second.m_aInfo.m_aDriverName.copy( 5 );
+ else
+ aPrinter = p_it->second.m_aInfo.m_aDriverName;
+
+ p_it->second.m_aInfo.m_aContext = m_aDefaultContexts[ aPrinter ];
+ }
+
+ rData.m_pParser = p_it->second.m_aInfo.m_pParser;
+ rData.m_aContext = p_it->second.m_aInfo.m_aContext;
+}
+
+FILE* CUPSManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
+{
+ SAL_INFO( "vcl.unx.print", "startSpool: " << rPrintername << " " << (bQuickCommand ? "true" : "false") );
+
+ if( m_aCUPSDestMap.find( rPrintername ) == m_aCUPSDestMap.end() )
+ {
+ SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::startSpool" );
+ return PrinterInfoManager::startSpool( rPrintername, bQuickCommand );
+ }
+
+ OUString aTmpURL, aTmpFile;
+ osl_createTempFile( nullptr, nullptr, &aTmpURL.pData );
+ osl_getSystemPathFromFileURL( aTmpURL.pData, &aTmpFile.pData );
+ OString aSysFile = OUStringToOString( aTmpFile, osl_getThreadTextEncoding() );
+ FILE* fp = fopen( aSysFile.getStr(), "w" );
+ if( fp )
+ m_aSpoolFiles[fp] = aSysFile;
+
+ return fp;
+}
+
+namespace {
+
+struct less_ppd_key
+{
+ bool operator()(const PPDKey* left, const PPDKey* right)
+ { return left->getOrderDependency() < right->getOrderDependency(); }
+};
+
+}
+
+void CUPSManager::getOptionsFromDocumentSetup( const JobData& rJob, bool bBanner, int& rNumOptions, void** rOptions )
+{
+ rNumOptions = 0;
+ *rOptions = nullptr;
+
+ // emit features ordered to OrderDependency
+ // ignore features that are set to default
+
+ // sanity check
+ if( rJob.m_pParser == rJob.m_aContext.getParser() && rJob.m_pParser )
+ {
+ std::size_t i;
+ std::size_t nKeys = rJob.m_aContext.countValuesModified();
+ ::std::vector< const PPDKey* > aKeys( nKeys );
+ for( i = 0; i < nKeys; i++ )
+ aKeys[i] = rJob.m_aContext.getModifiedKey( i );
+ ::std::sort( aKeys.begin(), aKeys.end(), less_ppd_key() );
+
+ for( i = 0; i < nKeys; i++ )
+ {
+ const PPDKey* pKey = aKeys[i];
+ const PPDValue* pValue = rJob.m_aContext.getValue( pKey );
+ OUString sPayLoad;
+ if (pValue && pValue->m_eType == eInvocation)
+ {
+ sPayLoad = pValue->m_bCustomOption ? pValue->m_aCustomOption : pValue->m_aOption;
+ }
+
+ if (!sPayLoad.isEmpty())
+ {
+ OString aKey = OUStringToOString( pKey->getKey(), RTL_TEXTENCODING_ASCII_US );
+ OString aValue = OUStringToOString( sPayLoad, RTL_TEXTENCODING_ASCII_US );
+ rNumOptions = cupsAddOption( aKey.getStr(), aValue.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
+ }
+ }
+ }
+
+ if( rJob.m_nPDFDevice > 0 && rJob.m_nCopies > 1 )
+ {
+ OString aVal( OString::number( rJob.m_nCopies ) );
+ rNumOptions = cupsAddOption( "copies", aVal.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
+ aVal = OString::boolean(rJob.m_bCollate);
+ rNumOptions = cupsAddOption( "collate", aVal.getStr(), rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
+ }
+ if( ! bBanner )
+ {
+ rNumOptions = cupsAddOption( "job-sheets", "none", rNumOptions, reinterpret_cast<cups_option_t**>(rOptions) );
+ }
+}
+
+namespace
+{
+ class RTSPWDialog : public weld::GenericDialogController
+ {
+ std::unique_ptr<weld::Label> m_xText;
+ std::unique_ptr<weld::Label> m_xDomainLabel;
+ std::unique_ptr<weld::Entry> m_xDomainEdit;
+ std::unique_ptr<weld::Label> m_xUserLabel;
+ std::unique_ptr<weld::Entry> m_xUserEdit;
+ std::unique_ptr<weld::Label> m_xPassLabel;
+ std::unique_ptr<weld::Entry> m_xPassEdit;
+
+ public:
+ RTSPWDialog(weld::Window* pParent, std::string_view rServer, std::string_view rUserName);
+
+ OString getDomain() const
+ {
+ return OUStringToOString( m_xDomainEdit->get_text(), osl_getThreadTextEncoding() );
+ }
+
+ OString getUserName() const
+ {
+ return OUStringToOString( m_xUserEdit->get_text(), osl_getThreadTextEncoding() );
+ }
+
+ OString getPassword() const
+ {
+ return OUStringToOString( m_xPassEdit->get_text(), osl_getThreadTextEncoding() );
+ }
+
+ void SetDomainVisible(bool bShow)
+ {
+ m_xDomainLabel->set_visible(bShow);
+ m_xDomainEdit->set_visible(bShow);
+ }
+
+ void SetUserVisible(bool bShow)
+ {
+ m_xUserLabel->set_visible(bShow);
+ m_xUserEdit->set_visible(bShow);
+ }
+
+ void SetPassVisible(bool bShow)
+ {
+ m_xPassLabel->set_visible(bShow);
+ m_xPassEdit->set_visible(bShow);
+ }
+ };
+
+ RTSPWDialog::RTSPWDialog(weld::Window* pParent, std::string_view rServer, std::string_view rUserName)
+ : GenericDialogController(pParent, "vcl/ui/cupspassworddialog.ui", "CUPSPasswordDialog")
+ , m_xText(m_xBuilder->weld_label("text"))
+ , m_xDomainLabel(m_xBuilder->weld_label("label3"))
+ , m_xDomainEdit(m_xBuilder->weld_entry("domain"))
+ , m_xUserLabel(m_xBuilder->weld_label("label1"))
+ , m_xUserEdit(m_xBuilder->weld_entry("user"))
+ , m_xPassLabel(m_xBuilder->weld_label("label2"))
+ , m_xPassEdit(m_xBuilder->weld_entry("pass"))
+ {
+ OUString aText(m_xText->get_label());
+ aText = aText.replaceFirst("%s", OStringToOUString(rServer, osl_getThreadTextEncoding()));
+ m_xText->set_label(aText);
+ m_xDomainEdit->set_text("WORKGROUP");
+ if (rUserName.empty())
+ m_xUserEdit->grab_focus();
+ else
+ {
+ m_xUserEdit->set_text(OStringToOUString(rUserName, osl_getThreadTextEncoding()));
+ m_xPassEdit->grab_focus();
+ }
+ }
+
+ bool AuthenticateQuery(std::string_view rServer, OString& rUserName, OString& rPassword)
+ {
+ bool bRet = false;
+
+ RTSPWDialog aDialog(Application::GetDefDialogParent(), rServer, rUserName);
+ if (aDialog.run() == RET_OK)
+ {
+ rUserName = aDialog.getUserName();
+ rPassword = aDialog.getPassword();
+ bRet = true;
+ }
+
+ return bRet;
+ }
+}
+
+namespace
+{
+ OString EscapeCupsOption(const OString& rIn)
+ {
+ OStringBuffer sRet;
+ sal_Int32 nLen = rIn.getLength();
+ for (sal_Int32 i = 0; i < nLen; ++i)
+ {
+ switch(rIn[i])
+ {
+ case '\\':
+ case '\'':
+ case '\"':
+ case ',':
+ case ' ':
+ case '\f':
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\v':
+ sRet.append('\\');
+ break;
+ }
+ sRet.append(rIn[i]);
+ }
+ return sRet.makeStringAndClear();
+ }
+}
+
+bool CUPSManager::endSpool( const OUString& rPrintername, const OUString& rJobTitle, FILE* pFile, const JobData& rDocumentJobData, bool bBanner, const OUString& rFaxNumber )
+{
+ SAL_INFO( "vcl.unx.print", "endSpool: " << rPrintername << "," << rJobTitle << " copy count = " << rDocumentJobData.m_nCopies );
+
+ int nJobID = 0;
+
+ osl::MutexGuard aGuard( m_aCUPSMutex );
+
+ std::unordered_map< OUString, int >::iterator dest_it =
+ m_aCUPSDestMap.find( rPrintername );
+ if( dest_it == m_aCUPSDestMap.end() )
+ {
+ SAL_INFO( "vcl.unx.print", "defer to PrinterInfoManager::endSpool" );
+ return PrinterInfoManager::endSpool( rPrintername, rJobTitle, pFile, rDocumentJobData, bBanner, rFaxNumber );
+ }
+
+ std::unordered_map< FILE*, OString, FPtrHash >::const_iterator it = m_aSpoolFiles.find( pFile );
+ if( it != m_aSpoolFiles.end() )
+ {
+ fclose( pFile );
+ rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
+
+ // setup cups options
+ int nNumOptions = 0;
+ cups_option_t* pOptions = nullptr;
+ auto ppOptions = reinterpret_cast<void**>(&pOptions);
+ getOptionsFromDocumentSetup( rDocumentJobData, bBanner, nNumOptions, ppOptions );
+
+ PrinterInfo aInfo(getPrinterInfo(rPrintername));
+ if (!aInfo.m_aAuthInfoRequired.isEmpty())
+ {
+ bool bDomain(false), bUser(false), bPass(false);
+ sal_Int32 nIndex = 0;
+ do
+ {
+ std::u16string_view aToken = o3tl::getToken(aInfo.m_aAuthInfoRequired, 0, ',', nIndex);
+ if (aToken == u"domain")
+ bDomain = true;
+ else if (aToken == u"username")
+ bUser = true;
+ else if (aToken == u"password")
+ bPass = true;
+ }
+ while (nIndex >= 0);
+
+ if (bDomain || bUser || bPass)
+ {
+ OString sPrinterName(OUStringToOString(rPrintername, RTL_TEXTENCODING_UTF8));
+ OString sUser = cupsUser();
+ RTSPWDialog aDialog(Application::GetDefDialogParent(), sPrinterName, sUser);
+ aDialog.SetDomainVisible(bDomain);
+ aDialog.SetUserVisible(bUser);
+ aDialog.SetPassVisible(bPass);
+
+ if (aDialog.run() == RET_OK)
+ {
+ OString sAuth;
+ if (bDomain)
+ sAuth = EscapeCupsOption(aDialog.getDomain());
+ if (bUser)
+ {
+ if (bDomain)
+ sAuth += ",";
+ sAuth += EscapeCupsOption(aDialog.getUserName());
+ }
+ if (bPass)
+ {
+ if (bUser || bDomain)
+ sAuth += ",";
+ sAuth += EscapeCupsOption(aDialog.getPassword());
+ }
+ nNumOptions = cupsAddOption("auth-info", sAuth.getStr(), nNumOptions, &pOptions);
+ }
+ }
+ }
+
+ OString sJobName(OUStringToOString(rJobTitle, aEnc));
+
+ //fax4CUPS, "the job name will be dialled for you"
+ //so override the jobname with the desired number
+ if (!rFaxNumber.isEmpty())
+ {
+ sJobName = OUStringToOString(rFaxNumber, aEnc);
+ }
+
+ cups_dest_t* pDest = static_cast<cups_dest_t*>(m_pDests) + dest_it->second;
+ nJobID = cupsPrintFile(pDest->name,
+ it->second.getStr(),
+ sJobName.getStr(),
+ nNumOptions, pOptions);
+ SAL_INFO("vcl.unx.print", "cupsPrintFile( " << pDest->name << ", "
+ << it->second << ", " << rJobTitle << ", " << nNumOptions
+ << ", " << pOptions << " ) returns " << nJobID);
+ for( int n = 0; n < nNumOptions; n++ )
+ SAL_INFO("vcl.unx.print",
+ " option " << pOptions[n].name << "=" << pOptions[n].value);
+#if OSL_DEBUG_LEVEL > 1
+ OString aCmd( "cp " );
+ aCmd += it->second.getStr();
+ aCmd += OString( " $HOME/cupsprint.ps" );
+ system( aCmd.getStr() );
+#endif
+
+ unlink( it->second.getStr() );
+ m_aSpoolFiles.erase(it);
+ if( pOptions )
+ cupsFreeOptions( nNumOptions, pOptions );
+ }
+
+ return nJobID != 0;
+}
+
+bool CUPSManager::checkPrintersChanged( bool bWait )
+{
+ bool bChanged = false;
+ if( bWait )
+ {
+ if( m_aDestThread )
+ {
+ // initial asynchronous detection still running
+ SAL_INFO("vcl.unx.print", "syncing cups discovery thread");
+ osl_joinWithThread( m_aDestThread );
+ osl_destroyThread( m_aDestThread );
+ m_aDestThread = nullptr;
+ SAL_INFO("vcl.unx.print", "done: syncing cups discovery thread");
+ }
+ else
+ {
+ // #i82321# check for cups printer updates
+ // with this change the whole asynchronous detection in a thread is
+ // almost useless. The only relevance left is for some stalled systems
+ // where the user can set SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION
+ // (see vcl/unx/source/gdi/salprnpsp.cxx)
+ // so that checkPrintersChanged( true ) will never be called
+
+ // there is no way to query CUPS whether the printer list has changed
+ // so get the dest list anew
+ if( m_nDests && m_pDests )
+ cupsFreeDests( m_nDests, static_cast<cups_dest_t*>(m_pDests) );
+ m_nDests = 0;
+ m_pDests = nullptr;
+ runDests();
+ }
+ }
+ if( m_aCUPSMutex.tryToAcquire() )
+ {
+ bChanged = m_bNewDests;
+ m_aCUPSMutex.release();
+ }
+
+ if( ! bChanged )
+ {
+ bChanged = PrinterInfoManager::checkPrintersChanged( bWait );
+ // #i54375# ensure new merging with CUPS list in :initialize
+ if( bChanged )
+ m_bNewDests = true;
+ }
+
+ if( bChanged )
+ initialize();
+
+ return bChanged;
+}
+
+const char* CUPSManager::authenticateUser()
+{
+ const char* pRet = nullptr;
+
+ osl::MutexGuard aGuard( m_aCUPSMutex );
+
+ OString aUser = cupsUser();
+ OString aServer = cupsServer();
+ OString aPassword;
+ if (AuthenticateQuery(aServer, aUser, aPassword))
+ {
+ m_aPassword = aPassword;
+ m_aUser = aUser;
+ cupsSetUser( m_aUser.getStr() );
+ pRet = m_aPassword.getStr();
+ }
+
+ return pRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/printer/jobdata.cxx b/vcl/unx/generic/printer/jobdata.cxx
new file mode 100644
index 000000000..9707a8109
--- /dev/null
+++ b/vcl/unx/generic/printer/jobdata.cxx
@@ -0,0 +1,316 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <officecfg/Office/Common.hxx>
+#include <jobdata.hxx>
+#include <printerinfomanager.hxx>
+#include <tools/stream.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <memory>
+
+using namespace psp;
+
+JobData& JobData::operator=(const JobData& rRight)
+{
+ if(this == &rRight)
+ return *this;
+
+ m_nCopies = rRight.m_nCopies;
+ m_bCollate = rRight.m_bCollate;
+ m_nLeftMarginAdjust = rRight.m_nLeftMarginAdjust;
+ m_nRightMarginAdjust = rRight.m_nRightMarginAdjust;
+ m_nTopMarginAdjust = rRight.m_nTopMarginAdjust;
+ m_nBottomMarginAdjust = rRight.m_nBottomMarginAdjust;
+ m_nColorDepth = rRight.m_nColorDepth;
+ m_eOrientation = rRight.m_eOrientation;
+ m_aPrinterName = rRight.m_aPrinterName;
+ m_bPapersizeFromSetup = rRight.m_bPapersizeFromSetup;
+ m_pParser = rRight.m_pParser;
+ m_aContext = rRight.m_aContext;
+ m_nPSLevel = rRight.m_nPSLevel;
+ m_nPDFDevice = rRight.m_nPDFDevice;
+ m_nColorDevice = rRight.m_nColorDevice;
+
+ if( !m_pParser && !m_aPrinterName.isEmpty() )
+ {
+ PrinterInfoManager& rMgr = PrinterInfoManager::get();
+ rMgr.setupJobContextData( *this );
+ }
+ return *this;
+}
+
+void JobData::setCollate( bool bCollate )
+{
+ if (m_nPDFDevice > 0)
+ {
+ m_bCollate = bCollate;
+ return;
+ }
+ const PPDParser* pParser = m_aContext.getParser();
+ if( !pParser )
+ return;
+
+ const PPDKey* pKey = pParser->getKey( "Collate" );
+ if( !pKey )
+ return;
+
+ const PPDValue* pVal = nullptr;
+ if( bCollate )
+ pVal = pKey->getValue( "True" );
+ else
+ {
+ pVal = pKey->getValue( "False" );
+ if( ! pVal )
+ pVal = pKey->getValue( "None" );
+ }
+ m_aContext.setValue( pKey, pVal );
+}
+
+void JobData::setPaper( int i_nWidth, int i_nHeight )
+{
+ if( m_pParser )
+ {
+ OUString aPaper( m_pParser->matchPaper( i_nWidth, i_nHeight ) );
+
+ const PPDKey* pKey = m_pParser->getKey( "PageSize" );
+ const PPDValue* pValue = pKey ? pKey->getValueCaseInsensitive( aPaper ) : nullptr;
+
+ if (pKey && pValue)
+ m_aContext.setValue( pKey, pValue );
+ }
+}
+
+void JobData::setPaperBin( int i_nPaperBin )
+{
+ if( m_pParser )
+ {
+ const PPDKey* pKey = m_pParser->getKey( "InputSlot" );
+ const PPDValue* pValue = pKey ? pKey->getValue( i_nPaperBin ) : nullptr;
+
+ if (pKey && pValue)
+ m_aContext.setValue( pKey, pValue );
+ }
+}
+
+bool JobData::getStreamBuffer( void*& pData, sal_uInt32& bytes )
+{
+ // consistency checks
+ if( ! m_pParser )
+ m_pParser = m_aContext.getParser();
+ if( m_pParser != m_aContext.getParser() ||
+ ! m_pParser )
+ return false;
+
+ SvMemoryStream aStream;
+
+ // write header job data
+ aStream.WriteLine("JobData 1");
+
+ OStringBuffer aLine;
+
+ aLine.append("printer=");
+ aLine.append(OUStringToOString(m_aPrinterName, RTL_TEXTENCODING_UTF8));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("orientation=");
+ if (m_eOrientation == orientation::Landscape)
+ aLine.append("Landscape");
+ else
+ aLine.append("Portrait");
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("copies=");
+ aLine.append(static_cast<sal_Int32>(m_nCopies));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ if (m_nPDFDevice > 0)
+ {
+ aLine.append("collate=");
+ aLine.append(OString::boolean(m_bCollate));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+ }
+
+ aLine.append("marginadjustment=");
+ aLine.append(static_cast<sal_Int32>(m_nLeftMarginAdjust));
+ aLine.append(',');
+ aLine.append(static_cast<sal_Int32>(m_nRightMarginAdjust));
+ aLine.append(',');
+ aLine.append(static_cast<sal_Int32>(m_nTopMarginAdjust));
+ aLine.append(',');
+ aLine.append(static_cast<sal_Int32>(m_nBottomMarginAdjust));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("colordepth=");
+ aLine.append(static_cast<sal_Int32>(m_nColorDepth));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("pslevel=");
+ aLine.append(static_cast<sal_Int32>(m_nPSLevel));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("pdfdevice=");
+ aLine.append(static_cast<sal_Int32>(m_nPDFDevice));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ aLine.append("colordevice=");
+ aLine.append(static_cast<sal_Int32>(m_nColorDevice));
+ aStream.WriteLine(aLine);
+ aLine.setLength(0);
+
+ // now append the PPDContext stream buffer
+ aStream.WriteLine( "PPDContextData" );
+ sal_uLong nBytes;
+ std::unique_ptr<char[]> pContextBuffer(m_aContext.getStreamableBuffer( nBytes ));
+ if( nBytes )
+ aStream.WriteBytes( pContextBuffer.get(), nBytes );
+ pContextBuffer.reset();
+
+ // success
+ bytes = static_cast<sal_uInt32>(aStream.Tell());
+ pData = std::malloc( bytes );
+ memcpy( pData, aStream.GetData(), bytes );
+ return true;
+}
+
+bool JobData::constructFromStreamBuffer( const void* pData, sal_uInt32 bytes, JobData& rJobData )
+{
+ SvMemoryStream aStream( const_cast<void*>(pData), bytes, StreamMode::READ );
+ OString aLine;
+ bool bVersion = false;
+ bool bPrinter = false;
+ bool bOrientation = false;
+ bool bCopies = false;
+ bool bContext = false;
+ bool bMargin = false;
+ bool bColorDepth = false;
+ bool bColorDevice = false;
+ bool bPSLevel = false;
+ bool bPDFDevice = false;
+
+ const char printerEquals[] = "printer=";
+ const char orientatationEquals[] = "orientation=";
+ const char copiesEquals[] = "copies=";
+ const char collateEquals[] = "collate=";
+ const char marginadjustmentEquals[] = "marginadjustment=";
+ const char colordepthEquals[] = "colordepth=";
+ const char colordeviceEquals[] = "colordevice=";
+ const char pslevelEquals[] = "pslevel=";
+ const char pdfdeviceEquals[] = "pdfdevice=";
+
+ while( ! aStream.eof() )
+ {
+ aStream.ReadLine( aLine );
+ if (aLine.startsWith("JobData"))
+ bVersion = true;
+ else if (aLine.startsWith(printerEquals))
+ {
+ bPrinter = true;
+ rJobData.m_aPrinterName = OStringToOUString(aLine.subView(RTL_CONSTASCII_LENGTH(printerEquals)), RTL_TEXTENCODING_UTF8);
+ }
+ else if (aLine.startsWith(orientatationEquals))
+ {
+ bOrientation = true;
+ rJobData.m_eOrientation = o3tl::equalsIgnoreAsciiCase(aLine.subView(RTL_CONSTASCII_LENGTH(orientatationEquals)), "landscape") ? orientation::Landscape : orientation::Portrait;
+ }
+ else if (aLine.startsWith(copiesEquals))
+ {
+ bCopies = true;
+ rJobData.m_nCopies = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(copiesEquals)));
+ }
+ else if (aLine.startsWith(collateEquals))
+ {
+ rJobData.m_bCollate = aLine.copy(RTL_CONSTASCII_LENGTH(collateEquals)).toBoolean();
+ }
+ else if (aLine.startsWith(marginadjustmentEquals))
+ {
+ bMargin = true;
+ sal_Int32 nIdx {RTL_CONSTASCII_LENGTH(marginadjustmentEquals)};
+ rJobData.m_nLeftMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx));
+ rJobData.m_nRightMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx));
+ rJobData.m_nTopMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx));
+ rJobData.m_nBottomMarginAdjust = o3tl::toInt32(o3tl::getToken(aLine, 0, ',', nIdx));
+ }
+ else if (aLine.startsWith(colordepthEquals))
+ {
+ bColorDepth = true;
+ rJobData.m_nColorDepth = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(colordepthEquals)));
+ }
+ else if (aLine.startsWith(colordeviceEquals))
+ {
+ bColorDevice = true;
+ rJobData.m_nColorDevice = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(colordeviceEquals)));
+ }
+ else if (aLine.startsWith(pslevelEquals))
+ {
+ bPSLevel = true;
+ rJobData.m_nPSLevel = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(pslevelEquals)));
+ }
+ else if (aLine.startsWith(pdfdeviceEquals))
+ {
+ bPDFDevice = true;
+ rJobData.m_nPDFDevice = o3tl::toInt32(aLine.subView(RTL_CONSTASCII_LENGTH(pdfdeviceEquals)));
+ }
+ else if (aLine == "PPDContextData" && bPrinter)
+ {
+ PrinterInfoManager& rManager = PrinterInfoManager::get();
+ const PrinterInfo& rInfo = rManager.getPrinterInfo( rJobData.m_aPrinterName );
+ rJobData.m_pParser = PPDParser::getParser( rInfo.m_aDriverName );
+ if( rJobData.m_pParser )
+ {
+ rJobData.m_aContext.setParser( rJobData.m_pParser );
+ sal_uInt64 nBytes = bytes - aStream.Tell();
+ std::vector<char> aRemain(nBytes+1);
+ nBytes = aStream.ReadBytes(aRemain.data(), nBytes);
+ if (nBytes)
+ {
+ aRemain.resize(nBytes+1);
+ aRemain[nBytes] = 0;
+ rJobData.m_aContext.rebuildFromStreamBuffer(aRemain);
+ bContext = true;
+ }
+ }
+ }
+ }
+
+ return bVersion && bPrinter && bOrientation && bCopies && bContext && bMargin && bPSLevel && bPDFDevice && bColorDevice && bColorDepth;
+}
+
+void JobData::resolveDefaultBackend()
+{
+ if (m_nPSLevel == 0 && m_nPDFDevice == 0)
+ setDefaultBackend(officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::get());
+}
+
+void JobData::setDefaultBackend(bool bUsePDF)
+{
+ if (bUsePDF && m_nPSLevel == 0 && m_nPDFDevice == 0)
+ m_nPDFDevice = 1;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/printer/ppdparser.cxx b/vcl/unx/generic/printer/ppdparser.cxx
new file mode 100644
index 000000000..c16d257e2
--- /dev/null
+++ b/vcl/unx/generic/printer/ppdparser.cxx
@@ -0,0 +1,1991 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <stdlib.h>
+
+#include <comphelper/string.hxx>
+#include <o3tl/string_view.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <ppdparser.hxx>
+#include <strhelper.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+
+#include <unx/helper.hxx>
+#include <unx/cupsmgr.hxx>
+#include <unx/cpdmgr.hxx>
+
+#include <tools/urlobj.hxx>
+#include <tools/stream.hxx>
+#include <tools/zcodec.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <osl/thread.h>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <salhelper/linkhelper.hxx>
+
+#include <com/sun/star/lang/Locale.hpp>
+
+#include <mutex>
+#include <unordered_map>
+
+#ifdef ENABLE_CUPS
+#include <cups/cups.h>
+#endif
+
+#include <config_dbus.h>
+#include <config_gio.h>
+#include <o3tl/hash_combine.hxx>
+
+namespace psp
+{
+ class PPDTranslator
+ {
+ struct LocaleEqual
+ {
+ bool operator()(const css::lang::Locale& i_rLeft,
+ const css::lang::Locale& i_rRight) const
+ {
+ return i_rLeft.Language == i_rRight.Language &&
+ i_rLeft.Country == i_rRight.Country &&
+ i_rLeft.Variant == i_rRight.Variant;
+ }
+ };
+
+ struct LocaleHash
+ {
+ size_t operator()(const css::lang::Locale& rLocale) const
+ {
+ std::size_t seed = 0;
+ o3tl::hash_combine(seed, rLocale.Language.hashCode());
+ o3tl::hash_combine(seed, rLocale.Country.hashCode());
+ o3tl::hash_combine(seed, rLocale.Variant.hashCode());
+ return seed;
+ }
+ };
+
+ typedef std::unordered_map< css::lang::Locale, OUString, LocaleHash, LocaleEqual > translation_map;
+ typedef std::unordered_map< OUString, translation_map > key_translation_map;
+
+ key_translation_map m_aTranslations;
+ public:
+ PPDTranslator() {}
+
+ void insertValue(
+ const OUString& i_rKey,
+ const OUString& i_rOption,
+ const OUString& i_rValue,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale
+ );
+
+ void insertOption( const OUString& i_rKey,
+ const OUString& i_rOption,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale )
+ {
+ insertValue( i_rKey, i_rOption, OUString(), i_rTranslation, i_rLocale );
+ }
+
+ void insertKey( const OUString& i_rKey,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale = css::lang::Locale() )
+ {
+ insertValue( i_rKey, OUString(), OUString(), i_rTranslation, i_rLocale );
+ }
+
+ OUString translateValue(
+ const OUString& i_rKey,
+ const OUString& i_rOption
+ ) const;
+
+ OUString translateOption( const OUString& i_rKey,
+ const OUString& i_rOption ) const
+ {
+ return translateValue( i_rKey, i_rOption );
+ }
+
+ OUString translateKey( const OUString& i_rKey ) const
+ {
+ return translateValue( i_rKey, OUString() );
+ }
+ };
+
+ static css::lang::Locale normalizeInputLocale(
+ const css::lang::Locale& i_rLocale
+ )
+ {
+ css::lang::Locale aLoc( i_rLocale );
+ if( aLoc.Language.isEmpty() )
+ {
+ // empty locale requested, fill in application UI locale
+ aLoc = Application::GetSettings().GetUILanguageTag().getLocale();
+
+ #if OSL_DEBUG_LEVEL > 1
+ static const char* pEnvLocale = getenv( "SAL_PPDPARSER_LOCALE" );
+ if( pEnvLocale && *pEnvLocale )
+ {
+ OString aStr( pEnvLocale );
+ sal_Int32 nLen = aStr.getLength();
+ aLoc.Language = OStringToOUString( aStr.copy( 0, std::min(nLen, 2) ), RTL_TEXTENCODING_MS_1252 );
+ if( nLen >=5 && aStr[2] == '_' )
+ aLoc.Country = OStringToOUString( aStr.copy( 3, 2 ), RTL_TEXTENCODING_MS_1252 );
+ else
+ aLoc.Country.clear();
+ aLoc.Variant.clear();
+ }
+ #endif
+ }
+ /* FIXME-BCP47: using Variant, uppercase? */
+ aLoc.Language = aLoc.Language.toAsciiLowerCase();
+ aLoc.Country = aLoc.Country.toAsciiUpperCase();
+ aLoc.Variant = aLoc.Variant.toAsciiUpperCase();
+
+ return aLoc;
+ }
+
+ void PPDTranslator::insertValue(
+ const OUString& i_rKey,
+ const OUString& i_rOption,
+ const OUString& i_rValue,
+ const OUString& i_rTranslation,
+ const css::lang::Locale& i_rLocale
+ )
+ {
+ OUStringBuffer aKey( i_rKey.getLength() + i_rOption.getLength() + i_rValue.getLength() + 2 );
+ aKey.append( i_rKey );
+ if( !i_rOption.isEmpty() || !i_rValue.isEmpty() )
+ {
+ aKey.append( ':' );
+ aKey.append( i_rOption );
+ }
+ if( !i_rValue.isEmpty() )
+ {
+ aKey.append( ':' );
+ aKey.append( i_rValue );
+ }
+ if( !aKey.isEmpty() && !i_rTranslation.isEmpty() )
+ {
+ OUString aK( aKey.makeStringAndClear() );
+ css::lang::Locale aLoc;
+ /* FIXME-BCP47: using Variant, uppercase? */
+ aLoc.Language = i_rLocale.Language.toAsciiLowerCase();
+ aLoc.Country = i_rLocale.Country.toAsciiUpperCase();
+ aLoc.Variant = i_rLocale.Variant.toAsciiUpperCase();
+ m_aTranslations[ aK ][ aLoc ] = i_rTranslation;
+ }
+ }
+
+ OUString PPDTranslator::translateValue(
+ const OUString& i_rKey,
+ const OUString& i_rOption
+ ) const
+ {
+ OUString aResult;
+
+ OUStringBuffer aKey( i_rKey.getLength() + i_rOption.getLength() + 2 );
+ aKey.append( i_rKey );
+ if( !i_rOption.isEmpty() )
+ {
+ aKey.append( ':' );
+ aKey.append( i_rOption );
+ }
+ if( !aKey.isEmpty() )
+ {
+ OUString aK( aKey.makeStringAndClear() );
+ key_translation_map::const_iterator it = m_aTranslations.find( aK );
+ if( it != m_aTranslations.end() )
+ {
+ const translation_map& rMap( it->second );
+
+ css::lang::Locale aLoc( normalizeInputLocale( css::lang::Locale() ) );
+ /* FIXME-BCP47: use LanguageTag::getFallbackStrings()? */
+ for( int nTry = 0; nTry < 4; nTry++ )
+ {
+ translation_map::const_iterator tr = rMap.find( aLoc );
+ if( tr != rMap.end() )
+ {
+ aResult = tr->second;
+ break;
+ }
+ switch( nTry )
+ {
+ case 0: aLoc.Variant.clear();break;
+ case 1: aLoc.Country.clear();break;
+ case 2: aLoc.Language.clear();break;
+ }
+ }
+ }
+ }
+ return aResult;
+ }
+
+ class PPDCache
+ {
+ public:
+ std::vector< std::unique_ptr<PPDParser> > aAllParsers;
+ std::optional<std::unordered_map< OUString, OUString >> xAllPPDFiles;
+ };
+}
+
+using namespace psp;
+
+namespace
+{
+ PPDCache& getPPDCache()
+ {
+ static PPDCache thePPDCache;
+ return thePPDCache;
+ }
+
+class PPDDecompressStream
+{
+private:
+ PPDDecompressStream(const PPDDecompressStream&) = delete;
+ PPDDecompressStream& operator=(const PPDDecompressStream&) = delete;
+
+ std::unique_ptr<SvFileStream> mpFileStream;
+ std::unique_ptr<SvMemoryStream> mpMemStream;
+ OUString maFileName;
+
+public:
+ explicit PPDDecompressStream( const OUString& rFile );
+ ~PPDDecompressStream();
+
+ bool IsOpen() const;
+ bool eof() const;
+ OString ReadLine();
+ void Open( const OUString& i_rFile );
+ void Close();
+ const OUString& GetFileName() const { return maFileName; }
+};
+
+}
+
+PPDDecompressStream::PPDDecompressStream( const OUString& i_rFile )
+{
+ Open( i_rFile );
+}
+
+PPDDecompressStream::~PPDDecompressStream()
+{
+ Close();
+}
+
+void PPDDecompressStream::Open( const OUString& i_rFile )
+{
+ Close();
+
+ mpFileStream.reset( new SvFileStream( i_rFile, StreamMode::READ ) );
+ maFileName = mpFileStream->GetFileName();
+
+ if( ! mpFileStream->IsOpen() )
+ {
+ Close();
+ return;
+ }
+
+ OString aLine;
+ mpFileStream->ReadLine( aLine );
+ mpFileStream->Seek( 0 );
+
+ // check for compress'ed or gzip'ed file
+ if( aLine.getLength() <= 1 ||
+ static_cast<unsigned char>(aLine[0]) != 0x1f ||
+ static_cast<unsigned char>(aLine[1]) != 0x8b /* check for gzip */ )
+ return;
+
+ // so let's try to decompress the stream
+ mpMemStream.reset( new SvMemoryStream( 4096, 4096 ) );
+ ZCodec aCodec;
+ aCodec.BeginCompression( ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true );
+ tools::Long nComp = aCodec.Decompress( *mpFileStream, *mpMemStream );
+ aCodec.EndCompression();
+ if( nComp < 0 )
+ {
+ // decompression failed, must be an uncompressed stream after all
+ mpMemStream.reset();
+ mpFileStream->Seek( 0 );
+ }
+ else
+ {
+ // compression successful, can get rid of file stream
+ mpFileStream.reset();
+ mpMemStream->Seek( 0 );
+ }
+}
+
+void PPDDecompressStream::Close()
+{
+ mpMemStream.reset();
+ mpFileStream.reset();
+}
+
+bool PPDDecompressStream::IsOpen() const
+{
+ return (mpMemStream || (mpFileStream && mpFileStream->IsOpen()));
+}
+
+bool PPDDecompressStream::eof() const
+{
+ return ( mpMemStream ? mpMemStream->eof() : ( mpFileStream == nullptr || mpFileStream->eof() ) );
+}
+
+OString PPDDecompressStream::ReadLine()
+{
+ OString o_rLine;
+ if( mpMemStream )
+ mpMemStream->ReadLine( o_rLine );
+ else if( mpFileStream )
+ mpFileStream->ReadLine( o_rLine );
+ return o_rLine;
+}
+
+static osl::FileBase::RC resolveLink( const OUString& i_rURL, OUString& o_rResolvedURL, OUString& o_rBaseName, osl::FileStatus::Type& o_rType)
+{
+ salhelper::LinkResolver aResolver(osl_FileStatus_Mask_FileName |
+ osl_FileStatus_Mask_Type |
+ osl_FileStatus_Mask_FileURL);
+
+ osl::FileBase::RC aRet = aResolver.fetchFileStatus(i_rURL, 10/*nLinkLevel*/);
+
+ if (aRet == osl::FileBase::E_None)
+ {
+ o_rResolvedURL = aResolver.m_aStatus.getFileURL();
+ o_rBaseName = aResolver.m_aStatus.getFileName();
+ o_rType = aResolver.m_aStatus.getFileType();
+ }
+
+ return aRet;
+}
+
+void PPDParser::scanPPDDir( const OUString& rDir )
+{
+ static struct suffix_t
+ {
+ const char* pSuffix;
+ const sal_Int32 nSuffixLen;
+ } const pSuffixes[] =
+ { { ".PS", 3 }, { ".PPD", 4 }, { ".PS.GZ", 6 }, { ".PPD.GZ", 7 } };
+
+ PPDCache &rPPDCache = getPPDCache();
+
+ osl::Directory aDir( rDir );
+ if ( aDir.open() != osl::FileBase::E_None )
+ return;
+
+ osl::DirectoryItem aItem;
+
+ INetURLObject aPPDDir(rDir);
+ while( aDir.getNextItem( aItem ) == osl::FileBase::E_None )
+ {
+ osl::FileStatus aStatus( osl_FileStatus_Mask_FileName );
+ if( aItem.getFileStatus( aStatus ) == osl::FileBase::E_None )
+ {
+ OUString aFileURL, aFileName;
+ osl::FileStatus::Type eType = osl::FileStatus::Unknown;
+ OUString aURL = rDir + "/" + aStatus.getFileName();
+
+ if(resolveLink( aURL, aFileURL, aFileName, eType ) == osl::FileBase::E_None)
+ {
+ if( eType == osl::FileStatus::Regular )
+ {
+ INetURLObject aPPDFile = aPPDDir;
+ aPPDFile.Append( aFileName );
+
+ // match extension
+ for(const suffix_t & rSuffix : pSuffixes)
+ {
+ if( aFileName.getLength() > rSuffix.nSuffixLen )
+ {
+ if( aFileName.endsWithIgnoreAsciiCaseAsciiL( rSuffix.pSuffix, rSuffix.nSuffixLen ) )
+ {
+ (*rPPDCache.xAllPPDFiles)[ aFileName.copy( 0, aFileName.getLength() - rSuffix.nSuffixLen ) ] = aPPDFile.PathToFileName();
+ break;
+ }
+ }
+ }
+ }
+ else if( eType == osl::FileStatus::Directory )
+ {
+ scanPPDDir( aFileURL );
+ }
+ }
+ }
+ }
+ aDir.close();
+}
+
+void PPDParser::initPPDFiles(PPDCache &rPPDCache)
+{
+ if( rPPDCache.xAllPPDFiles )
+ return;
+
+ rPPDCache.xAllPPDFiles.emplace();
+
+ // check installation directories
+ std::vector< OUString > aPathList;
+ psp::getPrinterPathList( aPathList, PRINTER_PPDDIR );
+ for (auto const& path : aPathList)
+ {
+ INetURLObject aPPDDir( path, INetProtocol::File, INetURLObject::EncodeMechanism::All );
+ scanPPDDir( aPPDDir.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+ }
+ if( rPPDCache.xAllPPDFiles->find( OUString( "SGENPRT" ) ) != rPPDCache.xAllPPDFiles->end() )
+ return;
+
+ // last try: search in directory of executable (mainly for setup)
+ OUString aExe;
+ if( osl_getExecutableFile( &aExe.pData ) == osl_Process_E_None )
+ {
+ INetURLObject aDir( aExe );
+ aDir.removeSegment();
+ SAL_INFO("vcl.unx.print", "scanning last chance dir: "
+ << aDir.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ scanPPDDir( aDir.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+ SAL_INFO("vcl.unx.print", "SGENPRT "
+ << (rPPDCache.xAllPPDFiles->find("SGENPRT") ==
+ rPPDCache.xAllPPDFiles->end() ? "not found" : "found"));
+ }
+}
+
+OUString PPDParser::getPPDFile( const OUString& rFile )
+{
+ INetURLObject aPPD( rFile, INetProtocol::File, INetURLObject::EncodeMechanism::All );
+ // someone might enter a full qualified name here
+ PPDDecompressStream aStream( aPPD.PathToFileName() );
+ if( ! aStream.IsOpen() )
+ {
+ std::unordered_map< OUString, OUString >::const_iterator it;
+ PPDCache &rPPDCache = getPPDCache();
+
+ bool bRetry = true;
+ do
+ {
+ initPPDFiles(rPPDCache);
+ // some PPD files contain dots beside the extension, so try name first
+ // and cut of points after that
+ OUString aBase( rFile );
+ sal_Int32 nLastIndex = aBase.lastIndexOf( '/' );
+ if( nLastIndex >= 0 )
+ aBase = aBase.copy( nLastIndex+1 );
+ do
+ {
+ it = rPPDCache.xAllPPDFiles->find( aBase );
+ nLastIndex = aBase.lastIndexOf( '.' );
+ if( nLastIndex > 0 )
+ aBase = aBase.copy( 0, nLastIndex );
+ } while( it == rPPDCache.xAllPPDFiles->end() && nLastIndex > 0 );
+
+ if( it == rPPDCache.xAllPPDFiles->end() && bRetry )
+ {
+ // a new file ? rehash
+ rPPDCache.xAllPPDFiles.reset();
+ bRetry = false;
+ // note this is optimized for office start where
+ // no new files occur and initPPDFiles is called only once
+ }
+ } while( ! rPPDCache.xAllPPDFiles );
+
+ if( it != rPPDCache.xAllPPDFiles->end() )
+ aStream.Open( it->second );
+ }
+
+ OUString aRet;
+ if( aStream.IsOpen() )
+ {
+ OString aLine = aStream.ReadLine();
+ if (aLine.startsWith("*PPD-Adobe"))
+ aRet = aStream.GetFileName();
+ else
+ {
+ // our *Include hack does usually not begin
+ // with *PPD-Adobe, so try some lines for *Include
+ int nLines = 10;
+ while (aLine.indexOf("*Include") != 0 && --nLines)
+ aLine = aStream.ReadLine();
+ if( nLines )
+ aRet = aStream.GetFileName();
+ }
+ }
+
+ return aRet;
+}
+
+const PPDParser* PPDParser::getParser( const OUString& rFile )
+{
+ // Recursive because we can get re-entered via CUPSManager::createCUPSParser
+ static std::recursive_mutex aMutex;
+ std::scoped_lock aGuard( aMutex );
+
+ OUString aFile = rFile;
+ if( !rFile.startsWith( "CUPS:" ) && !rFile.startsWith( "CPD:" ) )
+ aFile = getPPDFile( rFile );
+ if( aFile.isEmpty() )
+ {
+ SAL_INFO("vcl.unx.print", "Could not get printer PPD file \""
+ << rFile << "\" !");
+ return nullptr;
+ }
+ else
+ SAL_INFO("vcl.unx.print", "Parsing printer info from \""
+ << rFile << "\" !");
+
+
+ PPDCache &rPPDCache = getPPDCache();
+ for( auto const & i : rPPDCache.aAllParsers )
+ if( i->m_aFile == aFile )
+ return i.get();
+
+ PPDParser* pNewParser = nullptr;
+ if( !aFile.startsWith( "CUPS:" ) && !aFile.startsWith( "CPD:" ) )
+ pNewParser = new PPDParser( aFile );
+ else
+ {
+ PrinterInfoManager& rMgr = PrinterInfoManager::get();
+ if( rMgr.getType() == PrinterInfoManager::Type::CUPS )
+ {
+#ifdef ENABLE_CUPS
+ pNewParser = const_cast<PPDParser*>(static_cast<CUPSManager&>(rMgr).createCUPSParser( aFile ));
+#endif
+ } else if ( rMgr.getType() == PrinterInfoManager::Type::CPD )
+ {
+#if ENABLE_DBUS && ENABLE_GIO
+ pNewParser = const_cast<PPDParser*>(static_cast<CPDManager&>(rMgr).createCPDParser( aFile ));
+#endif
+ }
+ }
+ if( pNewParser )
+ {
+ // this may actually be the SGENPRT parser,
+ // so ensure uniqueness here (but don't remove last we delete us!)
+ if (std::none_of(
+ rPPDCache.aAllParsers.begin(),
+ rPPDCache.aAllParsers.end(),
+ [pNewParser] (std::unique_ptr<PPDParser> const & x) { return x.get() == pNewParser; } ))
+ {
+ // insert new parser to vector
+ rPPDCache.aAllParsers.emplace_back(pNewParser);
+ }
+ }
+ return pNewParser;
+}
+
+PPDParser::PPDParser(const OUString& rFile, const std::vector<PPDKey*>& keys)
+ : m_aFile(rFile)
+ , m_bColorDevice(false)
+ , m_bType42Capable(false)
+ , m_nLanguageLevel(0)
+ , m_aFileEncoding(RTL_TEXTENCODING_MS_1252)
+ , m_pImageableAreas(nullptr)
+ , m_pDefaultPaperDimension(nullptr)
+ , m_pPaperDimensions(nullptr)
+ , m_pDefaultInputSlot(nullptr)
+ , m_pDefaultResolution(nullptr)
+ , m_pTranslator(new PPDTranslator())
+{
+ for (auto & key: keys)
+ {
+ insertKey( std::unique_ptr<PPDKey>(key) );
+ }
+
+ // fill in shortcuts
+ const PPDKey* pKey;
+
+ pKey = getKey( "PageSize" );
+
+ if ( pKey ) {
+ std::unique_ptr<PPDKey> pImageableAreas(new PPDKey("ImageableArea"));
+ std::unique_ptr<PPDKey> pPaperDimensions(new PPDKey("PaperDimension"));
+#if defined(CUPS_VERSION_MAJOR)
+#if (CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR >= 7) || CUPS_VERSION_MAJOR > 1
+ for (int i = 0; i < pKey->countValues(); i++) {
+ const PPDValue* pValue = pKey -> getValue(i);
+ OUString aValueName = pValue -> m_aOption;
+ PPDValue* pImageableAreaValue = pImageableAreas -> insertValue( aValueName, eQuoted );
+ PPDValue* pPaperDimensionValue = pPaperDimensions -> insertValue( aValueName, eQuoted );
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ OString o = OUStringToOString( aValueName, aEncoding );
+ pwg_media_t *pPWGMedia = pwgMediaForPWG(o.pData->buffer);
+ if (pPWGMedia != nullptr) {
+ OUStringBuffer aBuf( 256 );
+ aBuf = "0 0 " +
+ OUString::number(PWG_TO_POINTS(pPWGMedia -> width)) +
+ " " +
+ OUString::number(PWG_TO_POINTS(pPWGMedia -> length));
+ if ( pImageableAreaValue )
+ pImageableAreaValue->m_aValue = aBuf.makeStringAndClear();
+ aBuf.append( PWG_TO_POINTS(pPWGMedia -> width) );
+ aBuf.append( " " );
+ aBuf.append( PWG_TO_POINTS(pPWGMedia -> length) );
+ if ( pPaperDimensionValue )
+ pPaperDimensionValue->m_aValue = aBuf.makeStringAndClear();
+ if (aValueName.equals(pKey -> getDefaultValue() -> m_aOption)) {
+ pImageableAreas -> m_pDefaultValue = pImageableAreaValue;
+ pPaperDimensions -> m_pDefaultValue = pPaperDimensionValue;
+ }
+ }
+ }
+#endif // HAVE_CUPS_API_1_7
+#endif
+ insertKey(std::move(pImageableAreas));
+ insertKey(std::move(pPaperDimensions));
+ }
+
+ m_pImageableAreas = getKey( "ImageableArea" );
+ const PPDValue* pDefaultImageableArea = nullptr;
+ if( m_pImageableAreas )
+ pDefaultImageableArea = m_pImageableAreas->getDefaultValue();
+ if (m_pImageableAreas == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no ImageableArea in " << m_aFile);
+ }
+ if (pDefaultImageableArea == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no DefaultImageableArea in " << m_aFile);
+ }
+
+ m_pPaperDimensions = getKey( "PaperDimension" );
+ if( m_pPaperDimensions )
+ m_pDefaultPaperDimension = m_pPaperDimensions->getDefaultValue();
+ if (m_pPaperDimensions == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no PaperDimensions in " << m_aFile);
+ }
+ if (m_pDefaultPaperDimension == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no DefaultPaperDimensions in " << m_aFile);
+ }
+
+ auto pResolutions = getKey( "Resolution" );
+ if( pResolutions )
+ m_pDefaultResolution = pResolutions->getDefaultValue();
+ if (pResolutions == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no Resolution in " << m_aFile);
+ }
+ SAL_INFO_IF(!m_pDefaultResolution, "vcl.unx.print", "no DefaultResolution in " + m_aFile);
+
+ auto pInputSlots = getKey( "InputSlot" );
+ if( pInputSlots )
+ m_pDefaultInputSlot = pInputSlots->getDefaultValue();
+ SAL_INFO_IF(!pInputSlots, "vcl.unx.print", "no InputSlot in " << m_aFile);
+ SAL_INFO_IF(!m_pDefaultInputSlot, "vcl.unx.print", "no DefaultInputSlot in " << m_aFile);
+
+ auto pFontList = getKey( "Font" );
+ if (pFontList == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no Font in " << m_aFile);
+ }
+
+ // fill in direct values
+ if( (pKey = getKey( "print-color-mode" )) )
+ m_bColorDevice = pKey->countValues() > 1;
+}
+
+PPDParser::PPDParser( const OUString& rFile ) :
+ m_aFile( rFile ),
+ m_bColorDevice( false ),
+ m_bType42Capable( false ),
+ m_nLanguageLevel( 0 ),
+ m_aFileEncoding( RTL_TEXTENCODING_MS_1252 ),
+ m_pImageableAreas( nullptr ),
+ m_pDefaultPaperDimension( nullptr ),
+ m_pPaperDimensions( nullptr ),
+ m_pDefaultInputSlot( nullptr ),
+ m_pDefaultResolution( nullptr ),
+ m_pTranslator( new PPDTranslator() )
+{
+ // read in the file
+ std::vector< OString > aLines;
+ PPDDecompressStream aStream( m_aFile );
+ if( aStream.IsOpen() )
+ {
+ bool bLanguageEncoding = false;
+ while( ! aStream.eof() )
+ {
+ OString aCurLine = aStream.ReadLine();
+ if( aCurLine.startsWith("*") )
+ {
+ if (aCurLine.matchIgnoreAsciiCase("*include:"))
+ {
+ aCurLine = aCurLine.copy(9);
+ aCurLine = comphelper::string::strip(aCurLine, ' ');
+ aCurLine = comphelper::string::strip(aCurLine, '\t');
+ aCurLine = comphelper::string::stripEnd(aCurLine, '\r');
+ aCurLine = comphelper::string::stripEnd(aCurLine, '\n');
+ aCurLine = comphelper::string::strip(aCurLine, '"');
+ aStream.Close();
+ aStream.Open(getPPDFile(OStringToOUString(aCurLine, m_aFileEncoding)));
+ continue;
+ }
+ else if( ! bLanguageEncoding &&
+ aCurLine.matchIgnoreAsciiCase("*languageencoding") )
+ {
+ bLanguageEncoding = true; // generally only the first one counts
+ OString aLower = aCurLine.toAsciiLowerCase();
+ if( aLower.indexOf("isolatin1", 17 ) != -1 ||
+ aLower.indexOf("windowsansi", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_MS_1252;
+ else if( aLower.indexOf("isolatin2", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_ISO_8859_2;
+ else if( aLower.indexOf("isolatin5", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_ISO_8859_5;
+ else if( aLower.indexOf("jis83-rksj", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_SHIFT_JIS;
+ else if( aLower.indexOf("macstandard", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_APPLE_ROMAN;
+ else if( aLower.indexOf("utf-8", 17 ) != -1 )
+ m_aFileEncoding = RTL_TEXTENCODING_UTF8;
+ }
+ }
+ aLines.push_back( aCurLine );
+ }
+ }
+ aStream.Close();
+
+ // now get the Values
+ parse( aLines );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "acquired " << m_aKeys.size()
+ << " Keys from PPD " << m_aFile << ":");
+ for (auto const& key : m_aKeys)
+ {
+ const PPDKey* pKey = key.second.get();
+ char const* pSetupType = "<unknown>";
+ switch( pKey->m_eSetupType )
+ {
+ case PPDKey::SetupType::ExitServer: pSetupType = "ExitServer";break;
+ case PPDKey::SetupType::Prolog: pSetupType = "Prolog";break;
+ case PPDKey::SetupType::DocumentSetup: pSetupType = "DocumentSetup";break;
+ case PPDKey::SetupType::PageSetup: pSetupType = "PageSetup";break;
+ case PPDKey::SetupType::JCLSetup: pSetupType = "JCLSetup";break;
+ case PPDKey::SetupType::AnySetup: pSetupType = "AnySetup";break;
+ default: break;
+ }
+ SAL_INFO("vcl.unx.print", "\t\"" << pKey->getKey() << "\" ("
+ << pKey->countValues() << "values) OrderDependency: "
+ << pKey->m_nOrderDependency << pSetupType );
+ for( int j = 0; j < pKey->countValues(); j++ )
+ {
+ const PPDValue* pValue = pKey->getValue( j );
+ char const* pVType = "<unknown>";
+ switch( pValue->m_eType )
+ {
+ case eInvocation: pVType = "invocation";break;
+ case eQuoted: pVType = "quoted";break;
+ case eString: pVType = "string";break;
+ case eSymbol: pVType = "symbol";break;
+ case eNo: pVType = "no";break;
+ default: break;
+ }
+ SAL_INFO("vcl.unx.print", "\t\t"
+ << (pValue == pKey->m_pDefaultValue ? "(Default:) " : "")
+ << "option: \"" << pValue->m_aOption
+ << "\", value: type " << pVType << " \""
+ << pValue->m_aValue << "\"");
+ }
+ }
+ SAL_INFO("vcl.unx.print",
+ "constraints: (" << m_aConstraints.size() << " found)");
+ for (auto const& constraint : m_aConstraints)
+ {
+ SAL_INFO("vcl.unx.print", "*\"" << constraint.m_pKey1->getKey() << "\" \""
+ << (constraint.m_pOption1 ? constraint.m_pOption1->m_aOption : "<nil>")
+ << "\" *\"" << constraint.m_pKey2->getKey() << "\" \""
+ << (constraint.m_pOption2 ? constraint.m_pOption2->m_aOption : "<nil>")
+ << "\"");
+ }
+#endif
+
+ // fill in shortcuts
+ const PPDKey* pKey;
+
+ m_pImageableAreas = getKey( "ImageableArea" );
+ const PPDValue * pDefaultImageableArea = nullptr;
+ if( m_pImageableAreas )
+ pDefaultImageableArea = m_pImageableAreas->getDefaultValue();
+ if (m_pImageableAreas == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no ImageableArea in " << m_aFile);
+ }
+ if (pDefaultImageableArea == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no DefaultImageableArea in " << m_aFile);
+ }
+
+ m_pPaperDimensions = getKey( "PaperDimension" );
+ if( m_pPaperDimensions )
+ m_pDefaultPaperDimension = m_pPaperDimensions->getDefaultValue();
+ if (m_pPaperDimensions == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no PaperDimensions in " << m_aFile);
+ }
+ if (m_pDefaultPaperDimension == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no DefaultPaperDimensions in " << m_aFile);
+ }
+
+ auto pResolutions = getKey( "Resolution" );
+ if( pResolutions )
+ m_pDefaultResolution = pResolutions->getDefaultValue();
+ if (pResolutions == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no Resolution in " << m_aFile);
+ }
+ SAL_INFO_IF(!m_pDefaultResolution, "vcl.unx.print", "no DefaultResolution in " + m_aFile);
+
+ auto pInputSlots = getKey( "InputSlot" );
+ if( pInputSlots )
+ m_pDefaultInputSlot = pInputSlots->getDefaultValue();
+ SAL_INFO_IF(!pInputSlots, "vcl.unx.print", "no InputSlot in " << m_aFile);
+ SAL_INFO_IF(!m_pDefaultInputSlot, "vcl.unx.print", "no DefaultInputSlot in " << m_aFile);
+
+ auto pFontList = getKey( "Font" );
+ if (pFontList == nullptr) {
+ SAL_WARN( "vcl.unx.print", "no Font in " << m_aFile);
+ }
+
+ // fill in direct values
+ if ((pKey = getKey("ColorDevice")))
+ {
+ if (const PPDValue* pValue = pKey->getValue(0))
+ m_bColorDevice = pValue->m_aValue.startsWithIgnoreAsciiCase("true");
+ }
+
+ if ((pKey = getKey("LanguageLevel")))
+ {
+ if (const PPDValue* pValue = pKey->getValue(0))
+ m_nLanguageLevel = pValue->m_aValue.toInt32();
+ }
+ if ((pKey = getKey("TTRasterizer")))
+ {
+ if (const PPDValue* pValue = pKey->getValue(0))
+ m_bType42Capable = pValue->m_aValue.equalsIgnoreAsciiCase( "Type42" );
+ }
+}
+
+PPDParser::~PPDParser()
+{
+ m_pTranslator.reset();
+}
+
+void PPDParser::insertKey( std::unique_ptr<PPDKey> pKey )
+{
+ m_aOrderedKeys.push_back( pKey.get() );
+ m_aKeys[ pKey->getKey() ] = std::move(pKey);
+}
+
+const PPDKey* PPDParser::getKey( int n ) const
+{
+ return (n >= 0 && o3tl::make_unsigned(n) < m_aOrderedKeys.size()) ? m_aOrderedKeys[n] : nullptr;
+}
+
+const PPDKey* PPDParser::getKey( const OUString& rKey ) const
+{
+ PPDParser::hash_type::const_iterator it = m_aKeys.find( rKey );
+ return it != m_aKeys.end() ? it->second.get() : nullptr;
+}
+
+bool PPDParser::hasKey( const PPDKey* pKey ) const
+{
+ return pKey && ( m_aKeys.find( pKey->getKey() ) != m_aKeys.end() );
+}
+
+static sal_uInt8 getNibble( char cChar )
+{
+ sal_uInt8 nRet = 0;
+ if( cChar >= '0' && cChar <= '9' )
+ nRet = sal_uInt8( cChar - '0' );
+ else if( cChar >= 'A' && cChar <= 'F' )
+ nRet = 10 + sal_uInt8( cChar - 'A' );
+ else if( cChar >= 'a' && cChar <= 'f' )
+ nRet = 10 + sal_uInt8( cChar - 'a' );
+ return nRet;
+}
+
+OUString PPDParser::handleTranslation(const OString& i_rString, bool bIsGlobalized)
+{
+ sal_Int32 nOrigLen = i_rString.getLength();
+ OStringBuffer aTrans( nOrigLen );
+ const char* pStr = i_rString.getStr();
+ const char* pEnd = pStr + nOrigLen;
+ while( pStr < pEnd )
+ {
+ if( *pStr == '<' )
+ {
+ pStr++;
+ char cChar;
+ while( *pStr != '>' && pStr < pEnd-1 )
+ {
+ cChar = getNibble( *pStr++ ) << 4;
+ cChar |= getNibble( *pStr++ );
+ aTrans.append( cChar );
+ }
+ pStr++;
+ }
+ else
+ aTrans.append( *pStr++ );
+ }
+ return OStringToOUString( aTrans, bIsGlobalized ? RTL_TEXTENCODING_UTF8 : m_aFileEncoding );
+}
+
+namespace
+{
+ bool oddDoubleQuoteCount(OStringBuffer &rBuffer)
+ {
+ bool bHasOddCount = false;
+ for (sal_Int32 i = 0; i < rBuffer.getLength(); ++i)
+ {
+ if (rBuffer[i] == '"')
+ bHasOddCount = !bHasOddCount;
+ }
+ return bHasOddCount;
+ }
+}
+
+void PPDParser::parse( ::std::vector< OString >& rLines )
+{
+ // Name for PPD group into which all options are put for which the PPD
+ // does not explicitly define a group.
+ // This is similar to how CUPS handles it,
+ // s. Sweet, Michael R. (2001): Common UNIX Printing System, p. 251:
+ // "Each option in turn is associated with a group stored in the
+ // ppd_group_t structure. Groups can be specified in the PPD file; if an
+ // option is not associated with a group, it is put in a "General" or
+ // "Extra" group depending on the option.
+ static constexpr OStringLiteral aDefaultPPDGroupName("General");
+
+ std::vector< OString >::iterator line = rLines.begin();
+ PPDParser::hash_type::const_iterator keyit;
+
+ // name of the PPD group that is currently being processed
+ OString aCurrentGroup = aDefaultPPDGroupName;
+
+ while( line != rLines.end() )
+ {
+ OString aCurrentLine( *line );
+ ++line;
+
+ SAL_INFO("vcl.unx.print", "Parse line '" << aCurrentLine << "'");
+
+ if (aCurrentLine.getLength() < 2 || aCurrentLine[0] != '*')
+ continue;
+ if( aCurrentLine[1] == '%' )
+ continue;
+
+ OString aKey = GetCommandLineToken( 0, aCurrentLine.getToken(0, ':') );
+ sal_Int32 nPos = aKey.indexOf('/');
+ if (nPos != -1)
+ aKey = aKey.copy(0, nPos);
+ if(!aKey.isEmpty())
+ {
+ aKey = aKey.copy(1); // remove the '*'
+ }
+ if(aKey.isEmpty())
+ {
+ continue;
+ }
+
+ if (aKey == "CloseGroup")
+ {
+ aCurrentGroup = aDefaultPPDGroupName;
+ continue;
+ }
+ if (aKey == "OpenGroup")
+ {
+ OString aGroupName = aCurrentLine;
+ sal_Int32 nPosition = aGroupName.indexOf('/');
+ if (nPosition != -1)
+ {
+ aGroupName = aGroupName.copy(0, nPosition);
+ }
+
+ aCurrentGroup = GetCommandLineToken(1, aGroupName);
+ continue;
+ }
+ if ((aKey == "CloseUI") ||
+ (aKey == "JCLCloseUI") ||
+ (aKey == "End") ||
+ (aKey == "JCLEnd") ||
+ (aKey == "OpenSubGroup") ||
+ (aKey == "CloseSubGroup"))
+ {
+ continue;
+ }
+
+ if ((aKey == "OpenUI") || (aKey == "JCLOpenUI"))
+ {
+ parseOpenUI( aCurrentLine, aCurrentGroup);
+ continue;
+ }
+ else if (aKey == "OrderDependency")
+ {
+ parseOrderDependency( aCurrentLine );
+ continue;
+ }
+ else if (aKey == "UIConstraints" ||
+ aKey == "NonUIConstraints")
+ {
+ continue; // parsed in pass 2
+ }
+ else if( aKey == "CustomPageSize" ) // currently not handled
+ continue;
+ else if (aKey.startsWith("Custom", &aKey) )
+ {
+ //fdo#43049 very basic support for Custom entries, we ignore the
+ //validation params and types
+ OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
+ keyit = m_aKeys.find( aUniKey );
+ if(keyit != m_aKeys.end())
+ {
+ PPDKey* pKey = keyit->second.get();
+ pKey->insertValue("Custom", eInvocation, true);
+ }
+ continue;
+ }
+
+ // default values are parsed in pass 2
+ if (aKey.startsWith("Default"))
+ continue;
+
+ bool bQuery = false;
+ if (aKey[0] == '?')
+ {
+ aKey = aKey.copy(1);
+ bQuery = true;
+ }
+
+ OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
+ // handle CUPS extension for globalized PPDs
+ /* FIXME-BCP47: really only ISO 639-1 two character language codes?
+ * goodnight... */
+ bool bIsGlobalizedLine = false;
+ css::lang::Locale aTransLocale;
+ if( ( aUniKey.getLength() > 3 && aUniKey[ 2 ] == '.' ) ||
+ ( aUniKey.getLength() > 5 && aUniKey[ 2 ] == '_' && aUniKey[ 5 ] == '.' ) )
+ {
+ if( aUniKey[ 2 ] == '.' )
+ {
+ aTransLocale.Language = aUniKey.copy( 0, 2 );
+ aUniKey = aUniKey.copy( 3 );
+ }
+ else
+ {
+ aTransLocale.Language = aUniKey.copy( 0, 2 );
+ aTransLocale.Country = aUniKey.copy( 3, 2 );
+ aUniKey = aUniKey.copy( 6 );
+ }
+ bIsGlobalizedLine = true;
+ }
+
+ OUString aOption;
+ nPos = aCurrentLine.indexOf(':');
+ if( nPos != -1 )
+ {
+ aOption = OStringToOUString(
+ aCurrentLine.subView( 1, nPos-1 ), RTL_TEXTENCODING_MS_1252 );
+ aOption = GetCommandLineToken( 1, aOption );
+ sal_Int32 nTransPos = aOption.indexOf( '/' );
+ if( nTransPos != -1 )
+ aOption = aOption.copy(0, nTransPos);
+ }
+
+ PPDValueType eType = eNo;
+ OUString aValue;
+ OUString aOptionTranslation;
+ OUString aValueTranslation;
+ if( nPos != -1 )
+ {
+ // found a colon, there may be an option
+ OString aLine = aCurrentLine.copy( 1, nPos-1 );
+ aLine = WhitespaceToSpace( aLine );
+ sal_Int32 nTransPos = aLine.indexOf('/');
+ if (nTransPos != -1)
+ aOptionTranslation = handleTranslation( aLine.copy(nTransPos+1), bIsGlobalizedLine );
+
+ // read in more lines if necessary for multiline values
+ aLine = aCurrentLine.copy( nPos+1 );
+ if (!aLine.isEmpty())
+ {
+ OStringBuffer aBuffer(aLine);
+ while (line != rLines.end() && oddDoubleQuoteCount(aBuffer))
+ {
+ // copy the newlines also
+ aBuffer.append('\n');
+ aBuffer.append(*line);
+ ++line;
+ }
+ aLine = aBuffer.makeStringAndClear();
+ }
+ aLine = WhitespaceToSpace( aLine );
+
+ // #i100644# handle a missing value (actually a broken PPD)
+ if( aLine.isEmpty() )
+ {
+ if( !aOption.isEmpty() &&
+ !aUniKey.startsWith( "JCL" ) )
+ eType = eInvocation;
+ else
+ eType = eQuoted;
+ }
+ // check for invocation or quoted value
+ else if(aLine[0] == '"')
+ {
+ aLine = aLine.copy(1);
+ nTransPos = aLine.indexOf('"');
+ if (nTransPos == -1)
+ nTransPos = aLine.getLength();
+ aValue = OStringToOUString(aLine.subView(0, nTransPos), RTL_TEXTENCODING_MS_1252);
+ // after the second doublequote can follow a / and a translation
+ if (nTransPos < aLine.getLength() - 2)
+ {
+ aValueTranslation = handleTranslation( aLine.copy( nTransPos+2 ), bIsGlobalizedLine );
+ }
+ // check for quoted value
+ if( !aOption.isEmpty() &&
+ !aUniKey.startsWith( "JCL" ) )
+ eType = eInvocation;
+ else
+ eType = eQuoted;
+ }
+ // check for symbol value
+ else if(aLine[0] == '^')
+ {
+ aLine = aLine.copy(1);
+ aValue = OStringToOUString(aLine, RTL_TEXTENCODING_MS_1252);
+ eType = eSymbol;
+ }
+ else
+ {
+ // must be a string value then
+ // strictly this is false because string values
+ // can contain any whitespace which is reduced
+ // to one space by now
+ // who cares ...
+ nTransPos = aLine.indexOf('/');
+ if (nTransPos == -1)
+ nTransPos = aLine.getLength();
+ aValue = OStringToOUString(aLine.subView(0, nTransPos), RTL_TEXTENCODING_MS_1252);
+ if (nTransPos+1 < aLine.getLength())
+ aValueTranslation = handleTranslation( aLine.copy( nTransPos+1 ), bIsGlobalizedLine );
+ eType = eString;
+ }
+ }
+
+ // handle globalized PPD entries
+ if( bIsGlobalizedLine )
+ {
+ // handle main key translations of form:
+ // *ll_CC.Translation MainKeyword/translated text: ""
+ if( aUniKey == "Translation" )
+ {
+ m_pTranslator->insertKey( aOption, aOptionTranslation, aTransLocale );
+ }
+ // handle options translations of for:
+ // *ll_CC.MainKeyword OptionKeyword/translated text: ""
+ else
+ {
+ m_pTranslator->insertOption( aUniKey, aOption, aOptionTranslation, aTransLocale );
+ }
+ continue;
+ }
+
+ PPDKey* pKey = nullptr;
+ keyit = m_aKeys.find( aUniKey );
+ if( keyit == m_aKeys.end() )
+ {
+ pKey = new PPDKey( aUniKey );
+ insertKey( std::unique_ptr<PPDKey>(pKey) );
+ }
+ else
+ pKey = keyit->second.get();
+
+ if( eType == eNo && bQuery )
+ continue;
+
+ PPDValue* pValue = pKey->insertValue( aOption, eType );
+ if( ! pValue )
+ continue;
+ pValue->m_aValue = aValue;
+
+ if( !aOptionTranslation.isEmpty() )
+ m_pTranslator->insertOption( aUniKey, aOption, aOptionTranslation, aTransLocale );
+ if( !aValueTranslation.isEmpty() )
+ m_pTranslator->insertValue( aUniKey, aOption, aValue, aValueTranslation, aTransLocale );
+
+ // eventually update query and remove from option list
+ if( bQuery && !pKey->m_bQueryValue )
+ {
+ pKey->m_bQueryValue = true;
+ pKey->eraseValue( pValue->m_aOption );
+ }
+ }
+
+ // second pass: fill in defaults
+ for( const auto& aLine : rLines )
+ {
+ if (aLine.startsWith("*Default"))
+ {
+ SAL_INFO("vcl.unx.print", "Found a default: '" << aLine << "'");
+ OUString aKey(OStringToOUString(aLine.subView(8), RTL_TEXTENCODING_MS_1252));
+ sal_Int32 nPos = aKey.indexOf( ':' );
+ if( nPos != -1 )
+ {
+ aKey = aKey.copy(0, nPos);
+ OUString aOption(OStringToOUString(
+ WhitespaceToSpace(aLine.subView(nPos+9)),
+ RTL_TEXTENCODING_MS_1252));
+ keyit = m_aKeys.find( aKey );
+ if( keyit != m_aKeys.end() )
+ {
+ PPDKey* pKey = keyit->second.get();
+ const PPDValue* pDefValue = pKey->getValue( aOption );
+ if( pKey->m_pDefaultValue == nullptr )
+ pKey->m_pDefaultValue = pDefValue;
+ }
+ else
+ {
+ // some PPDs contain defaults for keys that
+ // do not exist otherwise
+ // (example: DefaultResolution)
+ // so invent that key here and have a default value
+ std::unique_ptr<PPDKey> pKey(new PPDKey( aKey ));
+ pKey->insertValue( aOption, eInvocation /*or what ?*/ );
+ pKey->m_pDefaultValue = pKey->getValue( aOption );
+ insertKey( std::move(pKey) );
+ }
+ }
+ }
+ else if (aLine.startsWith("*UIConstraints") ||
+ aLine.startsWith("*NonUIConstraints"))
+ {
+ parseConstraint( aLine );
+ }
+ }
+}
+
+void PPDParser::parseOpenUI(const OString& rLine, std::string_view rPPDGroup)
+{
+ OUString aTranslation;
+ OString aKey = rLine;
+
+ sal_Int32 nPos = aKey.indexOf(':');
+ if( nPos != -1 )
+ aKey = aKey.copy(0, nPos);
+ nPos = aKey.indexOf('/');
+ if( nPos != -1 )
+ {
+ aTranslation = handleTranslation( aKey.copy( nPos + 1 ), false );
+ aKey = aKey.copy(0, nPos);
+ }
+ aKey = GetCommandLineToken( 1, aKey );
+ aKey = aKey.copy(1);
+
+ OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
+ PPDParser::hash_type::const_iterator keyit = m_aKeys.find( aUniKey );
+ PPDKey* pKey;
+ if( keyit == m_aKeys.end() )
+ {
+ pKey = new PPDKey( aUniKey );
+ insertKey( std::unique_ptr<PPDKey>(pKey) );
+ }
+ else
+ pKey = keyit->second.get();
+
+ pKey->m_bUIOption = true;
+ m_pTranslator->insertKey( pKey->getKey(), aTranslation );
+
+ pKey->m_aGroup = OStringToOUString(rPPDGroup, RTL_TEXTENCODING_MS_1252);
+}
+
+void PPDParser::parseOrderDependency(const OString& rLine)
+{
+ OString aLine(rLine);
+ sal_Int32 nPos = aLine.indexOf(':');
+ if( nPos != -1 )
+ aLine = aLine.copy( nPos+1 );
+
+ sal_Int32 nOrder = GetCommandLineToken( 0, aLine ).toInt32();
+ OString aSetup = GetCommandLineToken( 1, aLine );
+ OUString aKey(OStringToOUString(GetCommandLineToken(2, aLine), RTL_TEXTENCODING_MS_1252));
+ if( aKey[ 0 ] != '*' )
+ return; // invalid order dependency
+ aKey = aKey.replaceAt( 0, 1, u"" );
+
+ PPDKey* pKey;
+ PPDParser::hash_type::const_iterator keyit = m_aKeys.find( aKey );
+ if( keyit == m_aKeys.end() )
+ {
+ pKey = new PPDKey( aKey );
+ insertKey( std::unique_ptr<PPDKey>(pKey) );
+ }
+ else
+ pKey = keyit->second.get();
+
+ pKey->m_nOrderDependency = nOrder;
+ if( aSetup == "ExitServer" )
+ pKey->m_eSetupType = PPDKey::SetupType::ExitServer;
+ else if( aSetup == "Prolog" )
+ pKey->m_eSetupType = PPDKey::SetupType::Prolog;
+ else if( aSetup == "DocumentSetup" )
+ pKey->m_eSetupType = PPDKey::SetupType::DocumentSetup;
+ else if( aSetup == "PageSetup" )
+ pKey->m_eSetupType = PPDKey::SetupType::PageSetup;
+ else if( aSetup == "JCLSetup" )
+ pKey->m_eSetupType = PPDKey::SetupType::JCLSetup;
+ else
+ pKey->m_eSetupType = PPDKey::SetupType::AnySetup;
+}
+
+void PPDParser::parseConstraint( const OString& rLine )
+{
+ bool bFailed = false;
+
+ OUString aLine(OStringToOUString(rLine, RTL_TEXTENCODING_MS_1252));
+ sal_Int32 nIdx = rLine.indexOf(':');
+ if (nIdx != -1)
+ aLine = aLine.replaceAt(0, nIdx + 1, u"");
+ PPDConstraint aConstraint;
+ int nTokens = GetCommandLineTokenCount( aLine );
+ for( int i = 0; i < nTokens; i++ )
+ {
+ OUString aToken = GetCommandLineToken( i, aLine );
+ if( !aToken.isEmpty() && aToken[ 0 ] == '*' )
+ {
+ aToken = aToken.replaceAt( 0, 1, u"" );
+ if( aConstraint.m_pKey1 )
+ aConstraint.m_pKey2 = getKey( aToken );
+ else
+ aConstraint.m_pKey1 = getKey( aToken );
+ }
+ else
+ {
+ if( aConstraint.m_pKey2 )
+ {
+ if( ! ( aConstraint.m_pOption2 = aConstraint.m_pKey2->getValue( aToken ) ) )
+ bFailed = true;
+ }
+ else if( aConstraint.m_pKey1 )
+ {
+ if( ! ( aConstraint.m_pOption1 = aConstraint.m_pKey1->getValue( aToken ) ) )
+ bFailed = true;
+ }
+ else
+ // constraint for nonexistent keys; this happens
+ // e.g. in HP4PLUS3
+ bFailed = true;
+ }
+ }
+ // there must be two keywords
+ if( ! aConstraint.m_pKey1 || ! aConstraint.m_pKey2 || bFailed )
+ {
+ SAL_INFO("vcl.unx.print",
+ "Warning: constraint \"" << rLine << "\" is invalid");
+ }
+ else
+ m_aConstraints.push_back( aConstraint );
+}
+
+OUString PPDParser::getDefaultPaperDimension() const
+{
+ if( m_pDefaultPaperDimension )
+ return m_pDefaultPaperDimension->m_aOption;
+
+ return OUString();
+}
+
+bool PPDParser::getMargins(
+ std::u16string_view rPaperName,
+ int& rLeft, int& rRight,
+ int& rUpper, int& rLower ) const
+{
+ if( ! m_pImageableAreas || ! m_pPaperDimensions )
+ return false;
+
+ int nPDim=-1, nImArea=-1, i;
+ for( i = 0; i < m_pImageableAreas->countValues(); i++ )
+ if( rPaperName == m_pImageableAreas->getValue( i )->m_aOption )
+ nImArea = i;
+ for( i = 0; i < m_pPaperDimensions->countValues(); i++ )
+ if( rPaperName == m_pPaperDimensions->getValue( i )->m_aOption )
+ nPDim = i;
+ if( nPDim == -1 || nImArea == -1 )
+ return false;
+
+ double ImLLx, ImLLy, ImURx, ImURy;
+ double PDWidth, PDHeight;
+ OUString aArea = m_pImageableAreas->getValue( nImArea )->m_aValue;
+ ImLLx = StringToDouble( GetCommandLineToken( 0, aArea ) );
+ ImLLy = StringToDouble( GetCommandLineToken( 1, aArea ) );
+ ImURx = StringToDouble( GetCommandLineToken( 2, aArea ) );
+ ImURy = StringToDouble( GetCommandLineToken( 3, aArea ) );
+ aArea = m_pPaperDimensions->getValue( nPDim )->m_aValue;
+ PDWidth = StringToDouble( GetCommandLineToken( 0, aArea ) );
+ PDHeight = StringToDouble( GetCommandLineToken( 1, aArea ) );
+ rLeft = static_cast<int>(ImLLx + 0.5);
+ rLower = static_cast<int>(ImLLy + 0.5);
+ rUpper = static_cast<int>(PDHeight - ImURy + 0.5);
+ rRight = static_cast<int>(PDWidth - ImURx + 0.5);
+
+ return true;
+}
+
+bool PPDParser::getPaperDimension(
+ std::u16string_view rPaperName,
+ int& rWidth, int& rHeight ) const
+{
+ if( ! m_pPaperDimensions )
+ return false;
+
+ int nPDim=-1;
+ for( int i = 0; i < m_pPaperDimensions->countValues(); i++ )
+ if( rPaperName == m_pPaperDimensions->getValue( i )->m_aOption )
+ nPDim = i;
+ if( nPDim == -1 )
+ return false;
+
+ double PDWidth, PDHeight;
+ OUString aArea = m_pPaperDimensions->getValue( nPDim )->m_aValue;
+ PDWidth = StringToDouble( GetCommandLineToken( 0, aArea ) );
+ PDHeight = StringToDouble( GetCommandLineToken( 1, aArea ) );
+ rHeight = static_cast<int>(PDHeight + 0.5);
+ rWidth = static_cast<int>(PDWidth + 0.5);
+
+ return true;
+}
+
+OUString PPDParser::matchPaper( int nWidth, int nHeight ) const
+{
+ if( ! m_pPaperDimensions )
+ return OUString();
+
+ int nPDim = -1;
+ double fSort = 2e36, fNewSort;
+
+ for( int i = 0; i < m_pPaperDimensions->countValues(); i++ )
+ {
+ OUString aArea = m_pPaperDimensions->getValue( i )->m_aValue;
+ double PDWidth = StringToDouble( GetCommandLineToken( 0, aArea ) );
+ double PDHeight = StringToDouble( GetCommandLineToken( 1, aArea ) );
+ PDWidth /= static_cast<double>(nWidth);
+ PDHeight /= static_cast<double>(nHeight);
+ if( PDWidth >= 0.9 && PDWidth <= 1.1 &&
+ PDHeight >= 0.9 && PDHeight <= 1.1 )
+ {
+ fNewSort =
+ (1.0-PDWidth)*(1.0-PDWidth) + (1.0-PDHeight)*(1.0-PDHeight);
+ if( fNewSort == 0.0 ) // perfect match
+ return m_pPaperDimensions->getValue( i )->m_aOption;
+
+ if( fNewSort < fSort )
+ {
+ fSort = fNewSort;
+ nPDim = i;
+ }
+ }
+ }
+
+ static bool bDontSwap = false;
+ if( nPDim == -1 && ! bDontSwap )
+ {
+ // swap portrait/landscape and try again
+ bDontSwap = true;
+ OUString rRet = matchPaper( nHeight, nWidth );
+ bDontSwap = false;
+ return rRet;
+ }
+
+ return nPDim != -1 ? m_pPaperDimensions->getValue( nPDim )->m_aOption : OUString();
+}
+
+OUString PPDParser::getDefaultInputSlot() const
+{
+ if( m_pDefaultInputSlot )
+ return m_pDefaultInputSlot->m_aValue;
+ return OUString();
+}
+
+void PPDParser::getResolutionFromString(std::u16string_view rString,
+ int& rXRes, int& rYRes )
+{
+ rXRes = rYRes = 300;
+
+ const size_t nDPIPos {rString.find( u"dpi" )};
+ if( nDPIPos != std::u16string_view::npos )
+ {
+ const size_t nPos {rString.find( 'x' )};
+ if( nPos != std::u16string_view::npos )
+ {
+ rXRes = o3tl::toInt32(rString.substr( 0, nPos ));
+ rYRes = o3tl::toInt32(rString.substr(nPos+1, nDPIPos - nPos - 1));
+ }
+ else
+ rXRes = rYRes = o3tl::toInt32(rString.substr( 0, nDPIPos ));
+ }
+}
+
+void PPDParser::getDefaultResolution( int& rXRes, int& rYRes ) const
+{
+ if( m_pDefaultResolution )
+ {
+ getResolutionFromString( m_pDefaultResolution->m_aValue, rXRes, rYRes );
+ return;
+ }
+
+ rXRes = 300;
+ rYRes = 300;
+}
+
+OUString PPDParser::translateKey( const OUString& i_rKey ) const
+{
+ OUString aResult( m_pTranslator->translateKey( i_rKey ) );
+ if( aResult.isEmpty() )
+ aResult = i_rKey;
+ return aResult;
+}
+
+OUString PPDParser::translateOption( const OUString& i_rKey,
+ const OUString& i_rOption ) const
+{
+ OUString aResult( m_pTranslator->translateOption( i_rKey, i_rOption ) );
+ if( aResult.isEmpty() )
+ aResult = i_rOption;
+ return aResult;
+}
+
+/*
+ * PPDKey
+ */
+
+PPDKey::PPDKey( const OUString& rKey ) :
+ m_aKey( rKey ),
+ m_pDefaultValue( nullptr ),
+ m_bQueryValue( false ),
+ m_bUIOption( false ),
+ m_nOrderDependency( 100 ),
+ m_eSetupType( SetupType::AnySetup )
+{
+}
+
+PPDKey::~PPDKey()
+{
+}
+
+const PPDValue* PPDKey::getValue( int n ) const
+{
+ return (n >= 0 && o3tl::make_unsigned(n) < m_aOrderedValues.size()) ? m_aOrderedValues[n] : nullptr;
+}
+
+const PPDValue* PPDKey::getValue( const OUString& rOption ) const
+{
+ PPDKey::hash_type::const_iterator it = m_aValues.find( rOption );
+ return it != m_aValues.end() ? &it->second : nullptr;
+}
+
+const PPDValue* PPDKey::getValueCaseInsensitive( const OUString& rOption ) const
+{
+ const PPDValue* pValue = getValue( rOption );
+ if( ! pValue )
+ {
+ for( size_t n = 0; n < m_aOrderedValues.size() && ! pValue; n++ )
+ if( m_aOrderedValues[n]->m_aOption.equalsIgnoreAsciiCase( rOption ) )
+ pValue = m_aOrderedValues[n];
+ }
+
+ return pValue;
+}
+
+void PPDKey::eraseValue( const OUString& rOption )
+{
+ PPDKey::hash_type::iterator it = m_aValues.find( rOption );
+ if( it == m_aValues.end() )
+ return;
+
+ auto vit = std::find(m_aOrderedValues.begin(), m_aOrderedValues.end(), &(it->second ));
+ if( vit != m_aOrderedValues.end() )
+ m_aOrderedValues.erase( vit );
+
+ m_aValues.erase( it );
+}
+
+PPDValue* PPDKey::insertValue(const OUString& rOption, PPDValueType eType, bool bCustomOption)
+{
+ if( m_aValues.find( rOption ) != m_aValues.end() )
+ return nullptr;
+
+ PPDValue aValue;
+ aValue.m_aOption = rOption;
+ aValue.m_bCustomOption = bCustomOption;
+ aValue.m_eType = eType;
+ m_aValues[ rOption ] = aValue;
+ PPDValue* pValue = &m_aValues[rOption];
+ m_aOrderedValues.push_back( pValue );
+ return pValue;
+}
+
+/*
+ * PPDContext
+ */
+
+PPDContext::PPDContext() :
+ m_pParser( nullptr )
+{
+}
+
+PPDContext& PPDContext::operator=( PPDContext&& rCopy )
+{
+ std::swap(m_pParser, rCopy.m_pParser);
+ std::swap(m_aCurrentValues, rCopy.m_aCurrentValues);
+ return *this;
+}
+
+const PPDKey* PPDContext::getModifiedKey( std::size_t n ) const
+{
+ if( m_aCurrentValues.size() <= n )
+ return nullptr;
+
+ hash_type::const_iterator it = m_aCurrentValues.begin();
+ std::advance(it, n);
+ return it->first;
+}
+
+void PPDContext::setParser( const PPDParser* pParser )
+{
+ if( pParser != m_pParser )
+ {
+ m_aCurrentValues.clear();
+ m_pParser = pParser;
+ }
+}
+
+const PPDValue* PPDContext::getValue( const PPDKey* pKey ) const
+{
+ if( ! m_pParser )
+ return nullptr;
+
+ hash_type::const_iterator it = m_aCurrentValues.find( pKey );
+ if( it != m_aCurrentValues.end() )
+ return it->second;
+
+ if( ! m_pParser->hasKey( pKey ) )
+ return nullptr;
+
+ const PPDValue* pValue = pKey->getDefaultValue();
+ if( ! pValue )
+ pValue = pKey->getValue( 0 );
+
+ return pValue;
+}
+
+const PPDValue* PPDContext::setValue( const PPDKey* pKey, const PPDValue* pValue, bool bDontCareForConstraints )
+{
+ if( ! m_pParser || ! pKey )
+ return nullptr;
+
+ // pValue can be NULL - it means ignore this option
+
+ if( ! m_pParser->hasKey( pKey ) )
+ return nullptr;
+
+ // check constraints
+ if( pValue )
+ {
+ if( bDontCareForConstraints )
+ {
+ m_aCurrentValues[ pKey ] = pValue;
+ }
+ else if( checkConstraints( pKey, pValue, true ) )
+ {
+ m_aCurrentValues[ pKey ] = pValue;
+
+ // after setting this value, check all constraints !
+ hash_type::iterator it = m_aCurrentValues.begin();
+ while( it != m_aCurrentValues.end() )
+ {
+ if( it->first != pKey &&
+ ! checkConstraints( it->first, it->second, false ) )
+ {
+ SAL_INFO("vcl.unx.print", "PPDContext::setValue: option "
+ << it->first->getKey()
+ << " (" << it->second->m_aOption
+ << ") is constrained after setting "
+ << pKey->getKey()
+ << " to " << pValue->m_aOption);
+ resetValue( it->first, true );
+ it = m_aCurrentValues.begin();
+ }
+ else
+ ++it;
+ }
+ }
+ }
+ else
+ m_aCurrentValues[ pKey ] = nullptr;
+
+ return pValue;
+}
+
+bool PPDContext::checkConstraints( const PPDKey* pKey, const PPDValue* pValue )
+{
+ if( ! m_pParser || ! pKey || ! pValue )
+ return false;
+
+ // ensure that this key is already in the list if it exists at all
+ if( m_aCurrentValues.find( pKey ) != m_aCurrentValues.end() )
+ return checkConstraints( pKey, pValue, false );
+
+ // it is not in the list, insert it temporarily
+ bool bRet = false;
+ if( m_pParser->hasKey( pKey ) )
+ {
+ const PPDValue* pDefValue = pKey->getDefaultValue();
+ m_aCurrentValues[ pKey ] = pDefValue;
+ bRet = checkConstraints( pKey, pValue, false );
+ m_aCurrentValues.erase( pKey );
+ }
+
+ return bRet;
+}
+
+bool PPDContext::resetValue( const PPDKey* pKey, bool bDefaultable )
+{
+ if( ! pKey || ! m_pParser || ! m_pParser->hasKey( pKey ) )
+ return false;
+
+ const PPDValue* pResetValue = pKey->getValue( "None" );
+ if( ! pResetValue )
+ pResetValue = pKey->getValue( "False" );
+ if( ! pResetValue && bDefaultable )
+ pResetValue = pKey->getDefaultValue();
+
+ bool bRet = pResetValue && ( setValue( pKey, pResetValue ) == pResetValue );
+
+ return bRet;
+}
+
+bool PPDContext::checkConstraints( const PPDKey* pKey, const PPDValue* pNewValue, bool bDoReset )
+{
+ if( ! pNewValue )
+ return true;
+
+ // sanity checks
+ if( ! m_pParser )
+ return false;
+
+ if( pKey->getValue( pNewValue->m_aOption ) != pNewValue )
+ return false;
+
+ // None / False and the default can always be set, but be careful !
+ // setting them might influence constrained values
+ if( pNewValue->m_aOption == "None" || pNewValue->m_aOption == "False" ||
+ pNewValue == pKey->getDefaultValue() )
+ return true;
+
+ const ::std::vector< PPDParser::PPDConstraint >& rConstraints( m_pParser->getConstraints() );
+ for (auto const& constraint : rConstraints)
+ {
+ const PPDKey* pLeft = constraint.m_pKey1;
+ const PPDKey* pRight = constraint.m_pKey2;
+ if( ! pLeft || ! pRight || ( pKey != pLeft && pKey != pRight ) )
+ continue;
+
+ const PPDKey* pOtherKey = pKey == pLeft ? pRight : pLeft;
+ const PPDValue* pOtherKeyOption = pKey == pLeft ? constraint.m_pOption2 : constraint.m_pOption1;
+ const PPDValue* pKeyOption = pKey == pLeft ? constraint.m_pOption1 : constraint.m_pOption2;
+
+ // syntax *Key1 option1 *Key2 option2
+ if( pKeyOption && pOtherKeyOption )
+ {
+ if( pNewValue != pKeyOption )
+ continue;
+ if( pOtherKeyOption == getValue( pOtherKey ) )
+ {
+ return false;
+ }
+ }
+ // syntax *Key1 option *Key2 or *Key1 *Key2 option
+ else if( pOtherKeyOption || pKeyOption )
+ {
+ if( pKeyOption )
+ {
+ if( ! ( pOtherKeyOption = getValue( pOtherKey ) ) )
+ continue; // this should not happen, PPD broken
+
+ if( pKeyOption == pNewValue &&
+ pOtherKeyOption->m_aOption != "None" &&
+ pOtherKeyOption->m_aOption != "False" )
+ {
+ // check if the other value can be reset and
+ // do so if possible
+ if( bDoReset && resetValue( pOtherKey ) )
+ continue;
+
+ return false;
+ }
+ }
+ else if( pOtherKeyOption )
+ {
+ if( getValue( pOtherKey ) == pOtherKeyOption &&
+ pNewValue->m_aOption != "None" &&
+ pNewValue->m_aOption != "False" )
+ return false;
+ }
+ else
+ {
+ // this should not happen, PPD is broken
+ }
+ }
+ // syntax *Key1 *Key2
+ else
+ {
+ const PPDValue* pOtherValue = getValue( pOtherKey );
+ if( pOtherValue->m_aOption != "None" &&
+ pOtherValue->m_aOption != "False" &&
+ pNewValue->m_aOption != "None" &&
+ pNewValue->m_aOption != "False" )
+ return false;
+ }
+ }
+ return true;
+}
+
+char* PPDContext::getStreamableBuffer( sal_uLong& rBytes ) const
+{
+ rBytes = 0;
+ if( m_aCurrentValues.empty() )
+ return nullptr;
+ for (auto const& elem : m_aCurrentValues)
+ {
+ OString aCopy(OUStringToOString(elem.first->getKey(), RTL_TEXTENCODING_MS_1252));
+ rBytes += aCopy.getLength();
+ rBytes += 1; // for ':'
+ if( elem.second )
+ {
+ aCopy = OUStringToOString(elem.second->m_aOption, RTL_TEXTENCODING_MS_1252);
+ rBytes += aCopy.getLength();
+ }
+ else
+ rBytes += 4;
+ rBytes += 1; // for '\0'
+ }
+ rBytes += 1;
+ char* pBuffer = new char[ rBytes ];
+ memset( pBuffer, 0, rBytes );
+ char* pRun = pBuffer;
+ for (auto const& elem : m_aCurrentValues)
+ {
+ OString aCopy(OUStringToOString(elem.first->getKey(), RTL_TEXTENCODING_MS_1252));
+ int nBytes = aCopy.getLength();
+ memcpy( pRun, aCopy.getStr(), nBytes );
+ pRun += nBytes;
+ *pRun++ = ':';
+ if( elem.second )
+ aCopy = OUStringToOString(elem.second->m_aOption, RTL_TEXTENCODING_MS_1252);
+ else
+ aCopy = "*nil";
+ nBytes = aCopy.getLength();
+ memcpy( pRun, aCopy.getStr(), nBytes );
+ pRun += nBytes;
+
+ *pRun++ = 0;
+ }
+ return pBuffer;
+}
+
+void PPDContext::rebuildFromStreamBuffer(const std::vector<char> &rBuffer)
+{
+ if( ! m_pParser )
+ return;
+
+ m_aCurrentValues.clear();
+
+ const size_t nBytes = rBuffer.size() - 1;
+ size_t nRun = 0;
+ while (nRun < nBytes && rBuffer[nRun])
+ {
+ OString aLine(rBuffer.data() + nRun);
+ sal_Int32 nPos = aLine.indexOf(':');
+ if( nPos != -1 )
+ {
+ const PPDKey* pKey = m_pParser->getKey( OStringToOUString( aLine.subView( 0, nPos ), RTL_TEXTENCODING_MS_1252 ) );
+ if( pKey )
+ {
+ const PPDValue* pValue = nullptr;
+ OUString aOption(
+ OStringToOUString(aLine.subView(nPos+1), RTL_TEXTENCODING_MS_1252));
+ if (aOption != "*nil")
+ pValue = pKey->getValue( aOption );
+ m_aCurrentValues[ pKey ] = pValue;
+ SAL_INFO("vcl.unx.print",
+ "PPDContext::rebuildFromStreamBuffer: read PPDKeyValue { "
+ << pKey->getKey() << " , "
+ << (pValue ? aOption : "<nil>")
+ << " }");
+ }
+ }
+ nRun += aLine.getLength()+1;
+ }
+}
+
+int PPDContext::getRenderResolution() const
+{
+ // initialize to reasonable default, if parser is not set
+ int nDPI = 300;
+ if( m_pParser )
+ {
+ int nDPIx = 300, nDPIy = 300;
+ const PPDKey* pKey = m_pParser->getKey( "Resolution" );
+ if( pKey )
+ {
+ const PPDValue* pValue = getValue( pKey );
+ if( pValue )
+ PPDParser::getResolutionFromString( pValue->m_aOption, nDPIx, nDPIy );
+ else
+ m_pParser->getDefaultResolution( nDPIx, nDPIy );
+ }
+ else
+ m_pParser->getDefaultResolution( nDPIx, nDPIy );
+
+ nDPI = std::max(nDPIx, nDPIy);
+ }
+ return nDPI;
+}
+
+void PPDContext::getPageSize( OUString& rPaper, int& rWidth, int& rHeight ) const
+{
+ // initialize to reasonable default, if parser is not set
+ rPaper = "A4";
+ rWidth = 595;
+ rHeight = 842;
+ if( !m_pParser )
+ return;
+
+ const PPDKey* pKey = m_pParser->getKey( "PageSize" );
+ if( !pKey )
+ return;
+
+ const PPDValue* pValue = getValue( pKey );
+ if( pValue )
+ {
+ rPaper = pValue->m_aOption;
+ m_pParser->getPaperDimension( rPaper, rWidth, rHeight );
+ }
+ else
+ {
+ rPaper = m_pParser->getDefaultPaperDimension();
+ m_pParser->getDefaultPaperDimension( rWidth, rHeight );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/printer/printerinfomanager.cxx b/vcl/unx/generic/printer/printerinfomanager.cxx
new file mode 100644
index 000000000..ae87f6adf
--- /dev/null
+++ b/vcl/unx/generic/printer/printerinfomanager.cxx
@@ -0,0 +1,892 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unx/cpdmgr.hxx>
+#include <unx/cupsmgr.hxx>
+#include <unx/gendata.hxx>
+#include <unx/helper.hxx>
+
+#include <tools/urlobj.hxx>
+#include <tools/config.hxx>
+
+#include <i18nutil/paper.hxx>
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+
+#include <osl/file.hxx>
+#include <osl/thread.hxx>
+#include <osl/mutex.hxx>
+#include <o3tl/string_view.hxx>
+
+// filename of configuration files
+constexpr OUStringLiteral PRINT_FILENAME = u"psprint.conf";
+// the group of the global defaults
+constexpr OStringLiteral GLOBAL_DEFAULTS_GROUP = "__Global_Printer_Defaults__";
+
+#include <cstddef>
+#include <unordered_set>
+
+using namespace psp;
+using namespace osl;
+
+namespace psp
+{
+ class SystemQueueInfo final : public Thread
+ {
+ mutable Mutex m_aMutex;
+ bool m_bChanged;
+ std::vector< PrinterInfoManager::SystemPrintQueue >
+ m_aQueues;
+ OUString m_aCommand;
+
+ virtual void SAL_CALL run() override;
+
+ public:
+ SystemQueueInfo();
+ virtual ~SystemQueueInfo() override;
+
+ bool hasChanged() const;
+ OUString getCommand() const;
+
+ // sets changed status to false; therefore not const
+ void getSystemQueues( std::vector< PrinterInfoManager::SystemPrintQueue >& rQueues );
+ };
+} // namespace
+
+/*
+* class PrinterInfoManager
+*/
+
+PrinterInfoManager& PrinterInfoManager::get()
+{
+ // can't move to GenericUnixSalData, because of vcl/null/printerinfomanager.cxx
+ GenericUnixSalData* pSalData = GetGenericUnixSalData();
+ PrinterInfoManager* pPIM = pSalData->m_pPrinterInfoManager.get();
+ if (pPIM)
+ return *pPIM;
+
+ pPIM = CPDManager::tryLoadCPD();
+ if (!pPIM)
+ pPIM = CUPSManager::tryLoadCUPS();
+ if (!pPIM)
+ pPIM = new PrinterInfoManager();
+ pSalData->m_pPrinterInfoManager.reset(pPIM);
+ pPIM->initialize();
+
+ SAL_INFO("vcl.unx.print", "created PrinterInfoManager of type "
+ << static_cast<int>(pPIM->getType()));
+ return *pPIM;
+}
+
+PrinterInfoManager::PrinterInfoManager( Type eType ) :
+ m_eType( eType ),
+ m_bUseIncludeFeature( false ),
+ m_bUseJobPatch( true ),
+ m_aSystemDefaultPaper( "A4" )
+{
+ if( eType == Type::Default )
+ m_pQueueInfo.reset( new SystemQueueInfo );
+
+ m_aSystemDefaultPaper = OStringToOUString(
+ PaperInfo::toPSName(PaperInfo::getSystemDefaultPaper().getPaper()),
+ RTL_TEXTENCODING_UTF8);
+}
+
+PrinterInfoManager::~PrinterInfoManager()
+{
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "PrinterInfoManager: "
+ << "destroyed Manager of type "
+ << ((int) getType()));
+#endif
+}
+
+bool PrinterInfoManager::checkPrintersChanged( bool bWait )
+{
+ // check if files were created, deleted or modified since initialize()
+ bool bChanged = false;
+ for (auto const& watchFile : m_aWatchFiles)
+ {
+ DirectoryItem aItem;
+ if( DirectoryItem::get( watchFile.m_aFilePath, aItem ) )
+ {
+ if( watchFile.m_aModified.Seconds != 0 )
+ {
+ bChanged = true; // file probably has vanished
+ break;
+ }
+ }
+ else
+ {
+ FileStatus aStatus( osl_FileStatus_Mask_ModifyTime );
+ if( aItem.getFileStatus( aStatus ) )
+ {
+ bChanged = true; // unlikely but not impossible
+ break;
+ }
+ else
+ {
+ TimeValue aModified = aStatus.getModifyTime();
+ if( aModified.Seconds != watchFile.m_aModified.Seconds )
+ {
+ bChanged = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if( bWait && m_pQueueInfo )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "syncing printer discovery thread.");
+#endif
+ m_pQueueInfo->join();
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "done: syncing printer discovery thread.");
+#endif
+ }
+
+ if( ! bChanged && m_pQueueInfo )
+ bChanged = m_pQueueInfo->hasChanged();
+ if( bChanged )
+ {
+ initialize();
+ }
+
+ return bChanged;
+}
+
+void PrinterInfoManager::initialize()
+{
+ m_bUseIncludeFeature = false;
+ m_aPrinters.clear();
+ m_aWatchFiles.clear();
+ OUString aDefaultPrinter;
+
+ // first initialize the global defaults
+ // have to iterate over all possible files
+ // there should be only one global setup section in all
+ // available config files
+ m_aGlobalDefaults = PrinterInfo();
+
+ // need a parser for the PPDContext. generic printer should do.
+ m_aGlobalDefaults.m_pParser = PPDParser::getParser( "SGENPRT" );
+ m_aGlobalDefaults.m_aContext.setParser( m_aGlobalDefaults.m_pParser );
+
+ if( ! m_aGlobalDefaults.m_pParser )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "Error: no default PPD file "
+ << "SGENPRT available, shutting down psprint...");
+#endif
+ return;
+ }
+
+ std::vector< OUString > aDirList;
+ psp::getPrinterPathList( aDirList, nullptr );
+ for (auto const& printDir : aDirList)
+ {
+ INetURLObject aFile( printDir, INetProtocol::File, INetURLObject::EncodeMechanism::All );
+ aFile.Append( PRINT_FILENAME );
+ Config aConfig( aFile.PathToFileName() );
+ if( aConfig.HasGroup( GLOBAL_DEFAULTS_GROUP ) )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "found global defaults in "
+ << aFile.PathToFileName());
+#endif
+ aConfig.SetGroup( GLOBAL_DEFAULTS_GROUP );
+
+ OString aValue( aConfig.ReadKey( "Copies" ) );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nCopies = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "Orientation" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_eOrientation = aValue.equalsIgnoreAsciiCase("Landscape") ? orientation::Landscape : orientation::Portrait;
+
+ aValue = aConfig.ReadKey( "MarginAdjust" );
+ if (!aValue.isEmpty())
+ {
+ sal_Int32 nIdx {0};
+ m_aGlobalDefaults.m_nLeftMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ m_aGlobalDefaults.m_nRightMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ m_aGlobalDefaults.m_nTopMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ m_aGlobalDefaults.m_nBottomMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ }
+
+ aValue = aConfig.ReadKey( "ColorDepth", "24" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nColorDepth = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "ColorDevice" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nColorDevice = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "PSLevel" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nPSLevel = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "PDFDevice" );
+ if (!aValue.isEmpty())
+ m_aGlobalDefaults.m_nPDFDevice = aValue.toInt32();
+
+ // get the PPDContext of global JobData
+ for( int nKey = 0; nKey < aConfig.GetKeyCount(); ++nKey )
+ {
+ OString aKey( aConfig.GetKeyName( nKey ) );
+ if (aKey.startsWith("PPD_"))
+ {
+ aValue = aConfig.ReadKey( aKey );
+ const PPDKey* pKey = m_aGlobalDefaults.m_pParser->getKey(OStringToOUString(aKey.subView(4), RTL_TEXTENCODING_ISO_8859_1));
+ if( pKey )
+ {
+ m_aGlobalDefaults.m_aContext.
+ setValue( pKey,
+ aValue == "*nil" ? nullptr : pKey->getValue(OStringToOUString(aValue, RTL_TEXTENCODING_ISO_8859_1)),
+ true );
+ }
+ }
+ }
+ }
+ }
+ setDefaultPaper( m_aGlobalDefaults.m_aContext );
+
+ // now collect all available printers
+ for (auto const& printDir : aDirList)
+ {
+ INetURLObject aDir( printDir, INetProtocol::File, INetURLObject::EncodeMechanism::All );
+ INetURLObject aFile( aDir );
+ aFile.Append( PRINT_FILENAME );
+
+ // check directory validity
+ OUString aUniPath;
+ FileBase::getFileURLFromSystemPath( aDir.PathToFileName(), aUniPath );
+ Directory aDirectory( aUniPath );
+ if( aDirectory.open() )
+ continue;
+ aDirectory.close();
+
+ FileBase::getFileURLFromSystemPath( aFile.PathToFileName(), aUniPath );
+ FileStatus aStatus( osl_FileStatus_Mask_ModifyTime );
+ DirectoryItem aItem;
+
+ // setup WatchFile list
+ WatchFile aWatchFile;
+ aWatchFile.m_aFilePath = aUniPath;
+ if( ! DirectoryItem::get( aUniPath, aItem ) &&
+ ! aItem.getFileStatus( aStatus ) )
+ {
+ aWatchFile.m_aModified = aStatus.getModifyTime();
+ }
+ else
+ {
+ aWatchFile.m_aModified.Seconds = 0;
+ aWatchFile.m_aModified.Nanosec = 0;
+ }
+ m_aWatchFiles.push_back( aWatchFile );
+
+ Config aConfig( aFile.PathToFileName() );
+ for( int nGroup = 0; nGroup < aConfig.GetGroupCount(); nGroup++ )
+ {
+ aConfig.SetGroup( aConfig.GetGroupName( nGroup ) );
+ OString aValue = aConfig.ReadKey( "Printer" );
+ if (!aValue.isEmpty())
+ {
+ OUString aPrinterName;
+
+ sal_Int32 nNamePos = aValue.indexOf('/');
+ // check for valid value of "Printer"
+ if (nNamePos == -1)
+ continue;
+
+ Printer aPrinter;
+ // initialize to global defaults
+ aPrinter.m_aInfo = m_aGlobalDefaults;
+
+ aPrinterName = OStringToOUString(aValue.subView(nNamePos+1),
+ RTL_TEXTENCODING_UTF8);
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+ aPrinter.m_aInfo.m_aDriverName = OStringToOUString(aValue.subView(0, nNamePos), RTL_TEXTENCODING_UTF8);
+
+ // set parser, merge settings
+ // don't do this for CUPS printers as this is done
+ // by the CUPS system itself
+ if( !aPrinter.m_aInfo.m_aDriverName.startsWith( "CUPS:" ) )
+ {
+ aPrinter.m_aInfo.m_pParser = PPDParser::getParser( aPrinter.m_aInfo.m_aDriverName );
+ aPrinter.m_aInfo.m_aContext.setParser( aPrinter.m_aInfo.m_pParser );
+ // note: setParser also purges the context
+
+ // ignore this printer if its driver is not found
+ if( ! aPrinter.m_aInfo.m_pParser )
+ continue;
+
+ // merge the ppd context keys if the printer has the same keys and values
+ // this is a bit tricky, since it involves mixing two PPDs
+ // without constraints which might end up badly
+ // this feature should be use with caution
+ // it is mainly to select default paper sizes for new printers
+ for( std::size_t nPPDValueModified = 0; nPPDValueModified < m_aGlobalDefaults.m_aContext.countValuesModified(); nPPDValueModified++ )
+ {
+ const PPDKey* pDefKey = m_aGlobalDefaults.m_aContext.getModifiedKey( nPPDValueModified );
+ const PPDValue* pDefValue = m_aGlobalDefaults.m_aContext.getValue( pDefKey );
+ const PPDKey* pPrinterKey = pDefKey ? aPrinter.m_aInfo.m_pParser->getKey( pDefKey->getKey() ) : nullptr;
+ if( pDefKey && pPrinterKey )
+ // at least the options exist in both PPDs
+ {
+ if( pDefValue )
+ {
+ const PPDValue* pPrinterValue = pPrinterKey->getValue( pDefValue->m_aOption );
+ if( pPrinterValue )
+ // the printer has a corresponding option for the key
+ aPrinter.m_aInfo.m_aContext.setValue( pPrinterKey, pPrinterValue );
+ }
+ else
+ aPrinter.m_aInfo.m_aContext.setValue( pPrinterKey, nullptr );
+ }
+ }
+
+ aValue = aConfig.ReadKey( "Command" );
+ // no printer without a command
+ if (aValue.isEmpty())
+ {
+ /* TODO:
+ * porters: please append your platform to the Solaris
+ * case if your platform has SystemV printing per default.
+ */
+ #if defined __sun
+ aValue = "lp";
+ #else
+ aValue = "lpr";
+ #endif
+ }
+ aPrinter.m_aInfo.m_aCommand = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+ }
+
+ aValue = aConfig.ReadKey( "QuickCommand" );
+ aPrinter.m_aInfo.m_aQuickCommand = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ aValue = aConfig.ReadKey( "Features" );
+ aPrinter.m_aInfo.m_aFeatures = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ // override the settings in m_aGlobalDefaults if keys exist
+ aValue = aConfig.ReadKey( "DefaultPrinter" );
+ if (aValue != "0" && !aValue.equalsIgnoreAsciiCase("false"))
+ aDefaultPrinter = aPrinterName;
+
+ aValue = aConfig.ReadKey( "Location" );
+ aPrinter.m_aInfo.m_aLocation = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ aValue = aConfig.ReadKey( "Comment" );
+ aPrinter.m_aInfo.m_aComment = OStringToOUString(aValue, RTL_TEXTENCODING_UTF8);
+
+ aValue = aConfig.ReadKey( "Copies" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nCopies = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "Orientation" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_eOrientation = aValue.equalsIgnoreAsciiCase("Landscape") ? orientation::Landscape : orientation::Portrait;
+
+ aValue = aConfig.ReadKey( "MarginAdjust" );
+ if (!aValue.isEmpty())
+ {
+ sal_Int32 nIdx {0};
+ aPrinter.m_aInfo.m_nLeftMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ aPrinter.m_aInfo.m_nRightMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ aPrinter.m_aInfo.m_nTopMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ aPrinter.m_aInfo.m_nBottomMarginAdjust = o3tl::toInt32(o3tl::getToken(aValue, 0, ',', nIdx));
+ }
+
+ aValue = aConfig.ReadKey( "ColorDepth" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nColorDepth = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "ColorDevice" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nColorDevice = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "PSLevel" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nPSLevel = aValue.toInt32();
+
+ aValue = aConfig.ReadKey( "PDFDevice" );
+ if (!aValue.isEmpty())
+ aPrinter.m_aInfo.m_nPDFDevice = aValue.toInt32();
+
+ // now iterate over all keys to extract multi key information:
+ // 1. PPDContext information
+ for( int nKey = 0; nKey < aConfig.GetKeyCount(); ++nKey )
+ {
+ OString aKey( aConfig.GetKeyName( nKey ) );
+ if( aKey.startsWith("PPD_") && aPrinter.m_aInfo.m_pParser )
+ {
+ aValue = aConfig.ReadKey( aKey );
+ const PPDKey* pKey = aPrinter.m_aInfo.m_pParser->getKey(OStringToOUString(aKey.subView(4), RTL_TEXTENCODING_ISO_8859_1));
+ if( pKey )
+ {
+ aPrinter.m_aInfo.m_aContext.
+ setValue( pKey,
+ aValue == "*nil" ? nullptr : pKey->getValue(OStringToOUString(aValue, RTL_TEXTENCODING_ISO_8859_1)),
+ true );
+ }
+ }
+ }
+
+ setDefaultPaper( aPrinter.m_aInfo.m_aContext );
+
+ // if it's a "Generic Printer", apply defaults from config...
+ aPrinter.m_aInfo.resolveDefaultBackend();
+
+ // finally insert printer
+ FileBase::getFileURLFromSystemPath( aFile.PathToFileName(), aPrinter.m_aFile );
+ std::unordered_map< OUString, Printer >::const_iterator find_it =
+ m_aPrinters.find( aPrinterName );
+ if( find_it != m_aPrinters.end() )
+ {
+ aPrinter.m_aAlternateFiles = find_it->second.m_aAlternateFiles;
+ aPrinter.m_aAlternateFiles.insert( find_it->second.m_aFile );
+ }
+ m_aPrinters[ aPrinterName ] = aPrinter;
+ }
+ }
+ }
+
+ // set default printer
+ if( !m_aPrinters.empty() )
+ {
+ if( m_aPrinters.find( aDefaultPrinter ) == m_aPrinters.end() )
+ aDefaultPrinter = m_aPrinters.begin()->first;
+ }
+ else
+ aDefaultPrinter.clear();
+ m_aDefaultPrinter = aDefaultPrinter;
+
+ if( m_eType != Type::Default )
+ return;
+
+ // add a default printer for every available print queue
+ // merge paper default printer, all else from global defaults
+ PrinterInfo aMergeInfo( m_aGlobalDefaults );
+ aMergeInfo.m_aDriverName = "SGENPRT";
+ aMergeInfo.m_aFeatures = "autoqueue";
+
+ if( !m_aDefaultPrinter.isEmpty() )
+ {
+ PrinterInfo aDefaultInfo( getPrinterInfo( m_aDefaultPrinter ) );
+
+ const PPDKey* pDefKey = aDefaultInfo.m_pParser->getKey( "PageSize" );
+ const PPDKey* pMergeKey = aMergeInfo.m_pParser->getKey( "PageSize" );
+ const PPDValue* pDefValue = aDefaultInfo.m_aContext.getValue( pDefKey );
+ const PPDValue* pMergeValue = pMergeKey ? pMergeKey->getValue( pDefValue->m_aOption ) : nullptr;
+ if( pMergeKey && pMergeValue )
+ aMergeInfo.m_aContext.setValue( pMergeKey, pMergeValue );
+ }
+
+ if( m_pQueueInfo && m_pQueueInfo->hasChanged() )
+ {
+ m_aSystemPrintCommand = m_pQueueInfo->getCommand();
+ m_pQueueInfo->getSystemQueues( m_aSystemPrintQueues );
+ m_pQueueInfo.reset();
+ }
+ for (auto const& printQueue : m_aSystemPrintQueues)
+ {
+ OUString aPrinterName = "<" + printQueue.m_aQueue + ">";
+
+ if( m_aPrinters.find( aPrinterName ) != m_aPrinters.end() )
+ // probably user made this one permanent
+ continue;
+
+ OUString aCmd( m_aSystemPrintCommand );
+ aCmd = aCmd.replaceAll( "(PRINTER)", printQueue.m_aQueue );
+
+ Printer aPrinter;
+
+ // initialize to merged defaults
+ aPrinter.m_aInfo = aMergeInfo;
+ aPrinter.m_aInfo.m_aPrinterName = aPrinterName;
+ aPrinter.m_aInfo.m_aCommand = aCmd;
+ aPrinter.m_aInfo.m_aComment = printQueue.m_aComment;
+ aPrinter.m_aInfo.m_aLocation = printQueue.m_aLocation;
+
+ m_aPrinters[ aPrinterName ] = aPrinter;
+ }
+}
+
+void PrinterInfoManager::listPrinters( ::std::vector< OUString >& rVector ) const
+{
+ rVector.clear();
+ for (auto const& printer : m_aPrinters)
+ rVector.push_back(printer.first);
+}
+
+const PrinterInfo& PrinterInfoManager::getPrinterInfo( const OUString& rPrinter ) const
+{
+ static PrinterInfo aEmptyInfo;
+ std::unordered_map< OUString, Printer >::const_iterator it = m_aPrinters.find( rPrinter );
+
+ SAL_WARN_IF( it == m_aPrinters.end(), "vcl", "Do not ask for info about nonexistent printers" );
+
+ return it != m_aPrinters.end() ? it->second.m_aInfo : aEmptyInfo;
+}
+
+bool PrinterInfoManager::checkFeatureToken( const OUString& rPrinterName, const char* pToken ) const
+{
+ const PrinterInfo& rPrinterInfo( getPrinterInfo( rPrinterName ) );
+ sal_Int32 nIndex = 0;
+ while( nIndex != -1 )
+ {
+ OUString aOuterToken = rPrinterInfo.m_aFeatures.getToken( 0, ',', nIndex );
+ if( aOuterToken.getToken( 0, '=' ).equalsIgnoreAsciiCaseAscii( pToken ) )
+ return true;
+ }
+ return false;
+}
+
+FILE* PrinterInfoManager::startSpool( const OUString& rPrintername, bool bQuickCommand )
+{
+ const PrinterInfo& rPrinterInfo = getPrinterInfo (rPrintername);
+ const OUString& rCommand = (bQuickCommand && !rPrinterInfo.m_aQuickCommand.isEmpty() ) ?
+ rPrinterInfo.m_aQuickCommand : rPrinterInfo.m_aCommand;
+ OString aShellCommand = OUStringToOString (rCommand, RTL_TEXTENCODING_ISO_8859_1) +
+ " 2>/dev/null";
+
+ return popen (aShellCommand.getStr(), "w");
+}
+
+bool PrinterInfoManager::endSpool( const OUString& /*rPrintername*/, const OUString& /*rJobTitle*/, FILE* pFile, const JobData& /*rDocumentJobData*/, bool /*bBanner*/, const OUString& /*rFaxNumber*/ )
+{
+ return (0 == pclose( pFile ));
+}
+
+void PrinterInfoManager::setupJobContextData( JobData& rData )
+{
+ std::unordered_map< OUString, Printer >::iterator it =
+ m_aPrinters.find( rData.m_aPrinterName );
+ if( it != m_aPrinters.end() )
+ {
+ rData.m_pParser = it->second.m_aInfo.m_pParser;
+ rData.m_aContext = it->second.m_aInfo.m_aContext;
+ }
+}
+
+void PrinterInfoManager::setDefaultPaper( PPDContext& rContext ) const
+{
+ if( ! rContext.getParser() )
+ return;
+
+ const PPDKey* pPageSizeKey = rContext.getParser()->getKey( "PageSize" );
+ if( ! pPageSizeKey )
+ return;
+
+ std::size_t nModified = rContext.countValuesModified();
+ auto set = false;
+ for (std::size_t i = 0; i != nModified; ++i) {
+ if (rContext.getModifiedKey(i) == pPageSizeKey) {
+ set = true;
+ break;
+ }
+ }
+
+ if( set ) // paper was set already, do not modify
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_WARN("vcl.unx.print", "not setting default paper, already set "
+ << rContext.getValue( pPageSizeKey )->m_aOption);
+#endif
+ return;
+ }
+
+ // paper not set, fill in default value
+ const PPDValue* pPaperVal = nullptr;
+ int nValues = pPageSizeKey->countValues();
+ for( int i = 0; i < nValues && ! pPaperVal; i++ )
+ {
+ const PPDValue* pVal = pPageSizeKey->getValue( i );
+ if( pVal->m_aOption.equalsIgnoreAsciiCase( m_aSystemDefaultPaper ) )
+ pPaperVal = pVal;
+ }
+ if( pPaperVal )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "setting default paper "
+ << pPaperVal->m_aOption);
+#endif
+ rContext.setValue( pPageSizeKey, pPaperVal );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "-> got paper "
+ << rContext.getValue( pPageSizeKey )->m_aOption);
+#endif
+ }
+}
+
+SystemQueueInfo::SystemQueueInfo() :
+ m_bChanged( false )
+{
+ create();
+}
+
+SystemQueueInfo::~SystemQueueInfo()
+{
+ static const char* pNoSyncDetection = getenv( "SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION" );
+ if( ! pNoSyncDetection || !*pNoSyncDetection )
+ join();
+ else
+ terminate();
+}
+
+bool SystemQueueInfo::hasChanged() const
+{
+ MutexGuard aGuard( m_aMutex );
+ bool bChanged = m_bChanged;
+ return bChanged;
+}
+
+void SystemQueueInfo::getSystemQueues( std::vector< PrinterInfoManager::SystemPrintQueue >& rQueues )
+{
+ MutexGuard aGuard( m_aMutex );
+ rQueues = m_aQueues;
+ m_bChanged = false;
+}
+
+OUString SystemQueueInfo::getCommand() const
+{
+ MutexGuard aGuard( m_aMutex );
+ OUString aRet = m_aCommand;
+ return aRet;
+}
+
+namespace {
+
+struct SystemCommandParameters;
+
+}
+
+typedef void(* tokenHandler)(const std::vector< OString >&,
+ std::vector< PrinterInfoManager::SystemPrintQueue >&,
+ const SystemCommandParameters*);
+
+namespace {
+
+struct SystemCommandParameters
+{
+ const char* pQueueCommand;
+ const char* pPrintCommand;
+ const char* pForeToken;
+ const char* pAftToken;
+ unsigned int nForeTokenCount;
+ tokenHandler pHandler;
+};
+
+}
+
+#if ! (defined(LINUX) || defined(NETBSD) || defined(FREEBSD) || defined(OPENBSD))
+static void lpgetSysQueueTokenHandler(
+ const std::vector< OString >& i_rLines,
+ std::vector< PrinterInfoManager::SystemPrintQueue >& o_rQueues,
+ const SystemCommandParameters* )
+{
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ std::unordered_set< OUString > aUniqueSet;
+ std::unordered_set< OUString > aOnlySet;
+ aUniqueSet.insert( OUString( "_all" ) );
+ aUniqueSet.insert( OUString( "_default" ) );
+
+ // the eventual "all" attribute of the "_all" queue tells us, which
+ // printers are to be used for this user at all
+
+ // find _all: line
+ OString aAllLine( "_all:" );
+ OString aAllAttr( "all=" );
+ auto it = std::find_if(i_rLines.begin(), i_rLines.end(),
+ [&aAllLine](const OString& rLine) { return rLine.indexOf( aAllLine, 0 ) == 0; });
+ if( it != i_rLines.end() )
+ {
+ // now find the "all" attribute
+ ++it;
+ it = std::find_if(it, i_rLines.end(),
+ [&aAllAttr](const OString& rLine) { return WhitespaceToSpace( rLine ).startsWith( aAllAttr ); });
+ if( it != i_rLines.end() )
+ {
+ // insert the comma separated entries into the set of printers to use
+ OString aClean( WhitespaceToSpace( *it ) );
+ sal_Int32 nPos = aAllAttr.getLength();
+ while( nPos != -1 )
+ {
+ OString aTok( aClean.getToken( 0, ',', nPos ) );
+ if( !aTok.isEmpty() )
+ aOnlySet.insert( OStringToOUString( aTok, aEncoding ) );
+ }
+ }
+ }
+
+ bool bInsertAttribute = false;
+ OString aDescrStr( "description=" );
+ OString aLocStr( "location=" );
+ for (auto const& line : i_rLines)
+ {
+ sal_Int32 nPos = 0;
+ // find the begin of a new printer section
+ nPos = line.indexOf( ':', 0 );
+ if( nPos != -1 )
+ {
+ OUString aSysQueue( OStringToOUString( line.copy( 0, nPos ), aEncoding ) );
+ // do not insert duplicates (e.g. lpstat tends to produce such lines)
+ // in case there was a "_all" section, insert only those printer explicitly
+ // set in the "all" attribute
+ if( aUniqueSet.find( aSysQueue ) == aUniqueSet.end() &&
+ ( aOnlySet.empty() || aOnlySet.find( aSysQueue ) != aOnlySet.end() )
+ )
+ {
+ o_rQueues.push_back( PrinterInfoManager::SystemPrintQueue() );
+ o_rQueues.back().m_aQueue = aSysQueue;
+ o_rQueues.back().m_aLocation = aSysQueue;
+ aUniqueSet.insert( aSysQueue );
+ bInsertAttribute = true;
+ }
+ else
+ bInsertAttribute = false;
+ continue;
+ }
+ if( bInsertAttribute && ! o_rQueues.empty() )
+ {
+ // look for "description" attribute, insert as comment
+ nPos = line.indexOf( aDescrStr, 0 );
+ if( nPos != -1 )
+ {
+ OString aComment( WhitespaceToSpace( line.copy(nPos+12) ) );
+ if( !aComment.isEmpty() )
+ o_rQueues.back().m_aComment = OStringToOUString(aComment, aEncoding);
+ continue;
+ }
+ // look for "location" attribute, inser as location
+ nPos = line.indexOf( aLocStr, 0 );
+ if( nPos != -1 )
+ {
+ OString aLoc( WhitespaceToSpace( line.copy(nPos+9) ) );
+ if( !aLoc.isEmpty() )
+ o_rQueues.back().m_aLocation = OStringToOUString(aLoc, aEncoding);
+ continue;
+ }
+ }
+ }
+}
+#endif
+static void standardSysQueueTokenHandler(
+ const std::vector< OString >& i_rLines,
+ std::vector< PrinterInfoManager::SystemPrintQueue >& o_rQueues,
+ const SystemCommandParameters* i_pParms)
+{
+ rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
+ std::unordered_set< OUString > aUniqueSet;
+ OString aForeToken( i_pParms->pForeToken );
+ OString aAftToken( i_pParms->pAftToken );
+ /* Normal Unix print queue discovery, also used for Darwin 5 LPR printing
+ */
+ for (auto const& line : i_rLines)
+ {
+ sal_Int32 nPos = 0;
+
+ // search for a line describing a printer:
+ // find if there are enough tokens before the name
+ for( unsigned int i = 0; i < i_pParms->nForeTokenCount && nPos != -1; i++ )
+ {
+ nPos = line.indexOf( aForeToken, nPos );
+ if( nPos != -1 && line.getLength() >= nPos+aForeToken.getLength() )
+ nPos += aForeToken.getLength();
+ }
+ if( nPos != -1 )
+ {
+ // find if there is the token after the queue
+ sal_Int32 nAftPos = line.indexOf( aAftToken, nPos );
+ if( nAftPos != -1 )
+ {
+ // get the queue name between fore and aft tokens
+ OUString aSysQueue( OStringToOUString( line.subView( nPos, nAftPos - nPos ), aEncoding ) );
+ // do not insert duplicates (e.g. lpstat tends to produce such lines)
+ if( aUniqueSet.insert( aSysQueue ).second )
+ {
+ o_rQueues.emplace_back( );
+ o_rQueues.back().m_aQueue = aSysQueue;
+ o_rQueues.back().m_aLocation = aSysQueue;
+ }
+ }
+ }
+ }
+}
+
+const struct SystemCommandParameters aParms[] =
+{
+ #if defined(LINUX) || defined(NETBSD) || defined(FREEBSD) || defined(OPENBSD)
+ { "/usr/sbin/lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler },
+ { "lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler },
+ { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpstat -s", "lp -d \"(PRINTER)\"", "system for ", ": ", 1, standardSysQueueTokenHandler }
+ #else
+ { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpget list", "lp -d \"(PRINTER)\"", "", ":", 0, lpgetSysQueueTokenHandler },
+ { "LANG=C;LC_ALL=C;export LANG LC_ALL;lpstat -s", "lp -d \"(PRINTER)\"", "system for ", ": ", 1, standardSysQueueTokenHandler },
+ { "/usr/sbin/lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler },
+ { "lpc status", "lpr -P \"(PRINTER)\"", "", ":", 0, standardSysQueueTokenHandler }
+ #endif
+};
+
+void SystemQueueInfo::run()
+{
+ osl_setThreadName("LPR psp::SystemQueueInfo");
+
+ char pBuffer[1024];
+ std::vector< OString > aLines;
+
+ /* Discover which command we can use to get a list of all printer queues */
+ for(const auto & rParm : aParms)
+ {
+ aLines.clear();
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "trying print queue command \""
+ << rParm.pQueueCommand
+ << "\" ...");
+#endif
+ OString aCmdLine = rParm.pQueueCommand + OString::Concat(" 2>/dev/null");
+ FILE *pPipe;
+ if( (pPipe = popen( aCmdLine.getStr(), "r" )) )
+ {
+ while( fgets( pBuffer, 1024, pPipe ) )
+ aLines.emplace_back( pBuffer );
+ if( ! pclose( pPipe ) )
+ {
+ std::vector< PrinterInfoManager::SystemPrintQueue > aSysPrintQueues;
+ rParm.pHandler( aLines, aSysPrintQueues, &rParm );
+ MutexGuard aGuard( m_aMutex );
+ m_bChanged = true;
+ m_aQueues = aSysPrintQueues;
+ m_aCommand = OUString::createFromAscii( rParm.pPrintCommand );
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "printing queue command: success.");
+#endif
+ break;
+ }
+ }
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.unx.print", "printing queue command: failed.");
+#endif
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/window/salframe.cxx b/vcl/unx/generic/window/salframe.cxx
new file mode 100644
index 000000000..98b68c98e
--- /dev/null
+++ b/vcl/unx/generic/window/salframe.cxx
@@ -0,0 +1,4093 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <string.h>
+#include <string_view>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <tools/debug.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/keycodes.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/BitmapTools.hxx>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <X11/keysym.h>
+#include <X11/extensions/shape.h>
+
+#include <unx/saldisp.hxx>
+#include <unx/salgdi.h>
+#include <unx/salframe.h>
+#include <unx/wmadaptor.hxx>
+#include <unx/salbmp.h>
+#include <unx/i18n_ic.hxx>
+#include <unx/i18n_keysym.hxx>
+#include <opengl/zone.hxx>
+
+#include <unx/gensys.h>
+#include <window.h>
+
+#include <sal/macros.h>
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
+#include <com/sun/star/uno/Exception.hpp>
+
+#include <svdata.hxx>
+#include <bitmaps.hlst>
+
+#include <optional>
+
+#include <algorithm>
+
+#ifndef Button6
+# define Button6 6
+#endif
+#ifndef Button7
+# define Button7 7
+#endif
+
+using namespace vcl_sal;
+
+constexpr auto CLIENT_EVENTS = StructureNotifyMask
+ | SubstructureNotifyMask
+ | KeyPressMask
+ | KeyReleaseMask
+ | ButtonPressMask
+ | ButtonReleaseMask
+ | PointerMotionMask
+ | EnterWindowMask
+ | LeaveWindowMask
+ | FocusChangeMask
+ | ExposureMask
+ | VisibilityChangeMask
+ | PropertyChangeMask
+ | ColormapChangeMask;
+
+static ::Window hPresentationWindow = None, hPresFocusWindow = None;
+static ::std::list< ::Window > aPresentationReparentList;
+static int nVisibleFloats = 0;
+
+static void doReparentPresentationDialogues( SalDisplay const * pDisplay )
+{
+ GetGenericUnixSalData()->ErrorTrapPush();
+ for (auto const& elem : aPresentationReparentList)
+ {
+ int x, y;
+ ::Window aRoot, aChild;
+ unsigned int w, h, bw, d;
+ XGetGeometry( pDisplay->GetDisplay(),
+ elem,
+ &aRoot,
+ &x, &y, &w, &h, &bw, &d );
+ XTranslateCoordinates( pDisplay->GetDisplay(),
+ hPresentationWindow,
+ aRoot,
+ x, y,
+ &x, &y,
+ &aChild );
+ XReparentWindow( pDisplay->GetDisplay(),
+ elem,
+ aRoot,
+ x, y );
+ }
+ aPresentationReparentList.clear();
+ if( hPresFocusWindow )
+ XSetInputFocus( pDisplay->GetDisplay(), hPresFocusWindow, PointerRoot, CurrentTime );
+ XSync( pDisplay->GetDisplay(), False );
+ GetGenericUnixSalData()->ErrorTrapPop();
+}
+
+bool X11SalFrame::IsOverrideRedirect() const
+{
+ return
+ ((nStyle_ & SalFrameStyleFlags::INTRO) && !pDisplay_->getWMAdaptor()->supportsSplash())
+ ||
+ (!( nStyle_ & ~SalFrameStyleFlags::DEFAULT ) && !pDisplay_->getWMAdaptor()->supportsFullScreen())
+ ;
+}
+
+bool X11SalFrame::IsFloatGrabWindow() const
+{
+ static const char* pDisableGrab = getenv( "SAL_DISABLE_FLOATGRAB" );
+
+ return
+ ( ( !pDisableGrab || !*pDisableGrab ) &&
+ (
+ (nStyle_ & SalFrameStyleFlags::FLOAT) &&
+ ! (nStyle_ & SalFrameStyleFlags::TOOLTIP) &&
+ ! (nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION)
+ )
+ );
+}
+
+void X11SalFrame::setXEmbedInfo()
+{
+ if( !m_bXEmbed )
+ return;
+
+ tools::Long aInfo[2];
+ aInfo[0] = 1; // XEMBED protocol version
+ aInfo[1] = (bMapped_ ? 1 : 0); // XEMBED_MAPPED
+ XChangeProperty( pDisplay_->GetDisplay(),
+ mhWindow,
+ pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::XEMBED_INFO ),
+ pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::XEMBED_INFO ),
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(aInfo),
+ SAL_N_ELEMENTS(aInfo) );
+}
+
+void X11SalFrame::askForXEmbedFocus( sal_Int32 i_nTimeCode )
+{
+ XEvent aEvent;
+
+ memset( &aEvent, 0, sizeof(aEvent) );
+ aEvent.xclient.window = mhForeignParent;
+ aEvent.xclient.type = ClientMessage;
+ aEvent.xclient.message_type = pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::XEMBED );
+ aEvent.xclient.format = 32;
+ aEvent.xclient.data.l[0] = i_nTimeCode ? i_nTimeCode : CurrentTime;
+ aEvent.xclient.data.l[1] = 3; // XEMBED_REQUEST_FOCUS
+ aEvent.xclient.data.l[2] = 0;
+ aEvent.xclient.data.l[3] = 0;
+ aEvent.xclient.data.l[4] = 0;
+
+ GetGenericUnixSalData()->ErrorTrapPush();
+ XSendEvent( pDisplay_->GetDisplay(),
+ mhForeignParent,
+ False, NoEventMask, &aEvent );
+ XSync( pDisplay_->GetDisplay(), False );
+ GetGenericUnixSalData()->ErrorTrapPop();
+}
+
+typedef std::vector< unsigned long > NetWmIconData;
+
+namespace
+{
+ constexpr rtl::OUStringConstExpr SV_ICON_SIZE48[] =
+ {
+ MAINAPP_48_8,
+ MAINAPP_48_8,
+ ODT_48_8,
+ OTT_48_8,
+ ODS_48_8,
+ OTS_48_8,
+ ODG_48_8,
+ MAINAPP_48_8,
+ ODP_48_8,
+ MAINAPP_48_8,
+ ODM_48_8,
+ MAINAPP_48_8,
+ ODB_48_8,
+ ODF_48_8
+ };
+
+ constexpr rtl::OUStringConstExpr SV_ICON_SIZE32[] =
+ {
+ MAINAPP_32_8,
+ MAINAPP_32_8,
+ ODT_32_8,
+ OTT_32_8,
+ ODS_32_8,
+ OTS_32_8,
+ ODG_32_8,
+ MAINAPP_32_8,
+ ODP_32_8,
+ MAINAPP_32_8,
+ ODM_32_8,
+ MAINAPP_32_8,
+ ODB_32_8,
+ ODF_32_8
+ };
+
+ constexpr rtl::OUStringConstExpr SV_ICON_SIZE16[] =
+ {
+ MAINAPP_16_8,
+ MAINAPP_16_8,
+ ODT_16_8,
+ OTT_16_8,
+ ODS_16_8,
+ OTS_16_8,
+ ODG_16_8,
+ MAINAPP_16_8,
+ ODP_16_8,
+ MAINAPP_16_8,
+ ODM_16_8,
+ MAINAPP_16_8,
+ ODB_16_8,
+ ODF_16_8
+ };
+}
+
+static void CreateNetWmAppIcon( sal_uInt16 nIcon, NetWmIconData& netwm_icon )
+{
+ const int sizes[ 3 ] = { 48, 32, 16 };
+ netwm_icon.resize( 48 * 48 + 32 * 32 + 16 * 16 + 3 * 2 );
+ int pos = 0;
+ for(int size : sizes)
+ {
+ OUString sIcon;
+ if( size >= 48 )
+ sIcon = SV_ICON_SIZE48[nIcon];
+ else if( size >= 32 )
+ sIcon = SV_ICON_SIZE32[nIcon];
+ else
+ sIcon = SV_ICON_SIZE16[nIcon];
+
+ BitmapEx aIcon = vcl::bitmap::loadFromName(sIcon, ImageLoadFlags::IgnoreScalingFactor);
+
+ if( aIcon.IsEmpty())
+ continue;
+ vcl::bitmap::convertBitmap32To24Plus8(aIcon, aIcon);
+ Bitmap icon = aIcon.GetBitmap();
+ AlphaMask mask = aIcon.GetAlpha();
+ BitmapReadAccess* iconData = icon.AcquireReadAccess();
+ BitmapReadAccess* maskData = mask.AcquireReadAccess();
+ netwm_icon[ pos++ ] = size; // width
+ netwm_icon[ pos++ ] = size; // height
+ for( int y = 0; y < size; ++y )
+ for( int x = 0; x < size; ++x )
+ {
+ BitmapColor col = iconData->GetColor( y, x );
+ BitmapColor alpha = maskData->GetColor( y, x );
+ netwm_icon[ pos++ ] = (((( 255 - alpha.GetBlue()) * 256U ) + col.GetRed()) * 256 + col.GetGreen()) * 256 + col.GetBlue();
+ }
+ Bitmap::ReleaseAccess( iconData );
+ mask.ReleaseAccess( maskData );
+ }
+ netwm_icon.resize( pos );
+}
+
+static bool lcl_SelectAppIconPixmap( SalDisplay const *pDisplay, SalX11Screen nXScreen,
+ sal_uInt16 nIcon, sal_uInt16 iconSize,
+ Pixmap& icon_pixmap, Pixmap& icon_mask, NetWmIconData& netwm_icon)
+{
+ CreateNetWmAppIcon( nIcon, netwm_icon );
+
+ OUString sIcon;
+
+ if( iconSize >= 48 )
+ sIcon = SV_ICON_SIZE48[nIcon];
+ else if( iconSize >= 32 )
+ sIcon = SV_ICON_SIZE32[nIcon];
+ else if( iconSize >= 16 )
+ sIcon = SV_ICON_SIZE16[nIcon];
+ else
+ return false;
+
+ BitmapEx aIcon = vcl::bitmap::loadFromName(sIcon, ImageLoadFlags::IgnoreScalingFactor);
+
+ if( aIcon.IsEmpty() )
+ return false;
+
+ X11SalBitmap *pBitmap = dynamic_cast < X11SalBitmap * >
+ (aIcon.ImplGetBitmapSalBitmap().get());
+ if (!pBitmap) // FIXME: opengl , TODO SKIA
+ return false;
+
+ icon_pixmap = XCreatePixmap( pDisplay->GetDisplay(),
+ pDisplay->GetRootWindow( nXScreen ),
+ iconSize, iconSize,
+ DefaultDepth( pDisplay->GetDisplay(),
+ nXScreen.getXScreen() )
+ );
+
+ SalTwoRect aRect(0, 0, iconSize, iconSize, 0, 0, iconSize, iconSize);
+
+ pBitmap->ImplDraw( icon_pixmap,
+ nXScreen,
+ DefaultDepth( pDisplay->GetDisplay(),
+ nXScreen.getXScreen() ),
+ aRect,
+ DefaultGC( pDisplay->GetDisplay(),
+ nXScreen.getXScreen() ) );
+
+ icon_mask = None;
+
+ if( aIcon.IsAlpha() )
+ {
+ icon_mask = XCreatePixmap( pDisplay->GetDisplay(),
+ pDisplay->GetRootWindow( pDisplay->GetDefaultXScreen() ),
+ iconSize, iconSize, 1);
+
+ XGCValues aValues;
+ aValues.foreground = 0xffffffff;
+ aValues.background = 0;
+ aValues.function = GXcopy;
+ GC aMonoGC = XCreateGC( pDisplay->GetDisplay(), icon_mask,
+ GCFunction|GCForeground|GCBackground, &aValues );
+
+ Bitmap aMask = aIcon.GetAlpha();
+ aMask.Invert();
+
+ X11SalBitmap *pMask = static_cast < X11SalBitmap * >
+ (aMask.ImplGetSalBitmap().get());
+
+ pMask->ImplDraw(icon_mask, nXScreen, 1, aRect, aMonoGC);
+ XFreeGC( pDisplay->GetDisplay(), aMonoGC );
+ }
+
+ return true;
+}
+
+void X11SalFrame::Init( SalFrameStyleFlags nSalFrameStyle, SalX11Screen nXScreen, SystemParentData const * pParentData, bool bUseGeometry )
+{
+ if( nXScreen.getXScreen() >= GetDisplay()->GetXScreenCount() )
+ nXScreen = GetDisplay()->GetDefaultXScreen();
+ if( mpParent )
+ nXScreen = mpParent->m_nXScreen;
+
+ m_nXScreen = nXScreen;
+ nStyle_ = nSalFrameStyle;
+ XWMHints Hints;
+ Hints.flags = InputHint;
+ Hints.input = (nSalFrameStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) ? False : True;
+ NetWmIconData netwm_icon;
+
+ int x = 0, y = 0;
+ unsigned int w = 500, h = 500;
+ XSetWindowAttributes Attributes;
+
+ int nAttrMask = CWBorderPixel
+ | CWBackPixmap
+ | CWColormap
+ | CWOverrideRedirect
+ | CWEventMask
+ ;
+ Attributes.border_pixel = 0;
+ Attributes.background_pixmap = None;
+ Attributes.colormap = GetDisplay()->GetColormap( m_nXScreen ).GetXColormap();
+ Attributes.override_redirect = False;
+ Attributes.event_mask = CLIENT_EVENTS;
+
+ const SalVisual& rVis = GetDisplay()->GetVisual( m_nXScreen );
+ ::Window aFrameParent = pParentData ? pParentData->aWindow : GetDisplay()->GetRootWindow( m_nXScreen );
+ ::Window aClientLeader = None;
+
+ if( bUseGeometry )
+ {
+ x = maGeometry.nX;
+ y = maGeometry.nY;
+ w = maGeometry.nWidth;
+ h = maGeometry.nHeight;
+ }
+
+ if( (nSalFrameStyle & SalFrameStyleFlags::FLOAT) &&
+ ! (nSalFrameStyle & SalFrameStyleFlags::OWNERDRAWDECORATION)
+ )
+ {
+ if( nShowState_ == X11ShowState::Unknown )
+ {
+ w = 10;
+ h = 10;
+ }
+ Attributes.override_redirect = True;
+ }
+ else if( nSalFrameStyle & SalFrameStyleFlags::SYSTEMCHILD )
+ {
+ SAL_WARN_IF( !mpParent, "vcl", "SalFrameStyleFlags::SYSTEMCHILD window without parent" );
+ if( mpParent )
+ {
+ aFrameParent = mpParent->mhWindow;
+ // FIXME: since with SalFrameStyleFlags::SYSTEMCHILD
+ // multiple X11SalFrame objects can have the same shell window
+ // dispatching events in saldisp.cxx is unclear (the first frame)
+ // wins. HTH this correctly is unclear yet
+ // for the time being, treat set the shell window to own window
+ // like for a normal frame
+ // mhShellWindow = mpParent->GetShellWindow();
+ }
+ }
+ else if( pParentData )
+ {
+ // plugin parent may be killed unexpectedly by plugging
+ // process; start permanently ignoring X errors...
+ GetGenericUnixSalData()->ErrorTrapPush();
+
+ nStyle_ |= SalFrameStyleFlags::PLUG;
+ Attributes.override_redirect = True;
+ if( pParentData->nSize >= sizeof(SystemParentData) )
+ m_bXEmbed = pParentData->bXEmbedSupport;
+
+ int x_ret, y_ret;
+ unsigned int bw, d;
+ ::Window aRoot, aParent;
+
+ XGetGeometry( GetXDisplay(), pParentData->aWindow,
+ &aRoot, &x_ret, &y_ret, &w, &h, &bw, &d );
+ mhForeignParent = pParentData->aWindow;
+
+ mhShellWindow = aParent = mhForeignParent;
+ ::Window* pChildren;
+ unsigned int nChildren;
+ bool bBreak = false;
+ do
+ {
+ XQueryTree( GetDisplay()->GetDisplay(), mhShellWindow,
+ &aRoot, &aParent, &pChildren, &nChildren );
+ XFree( pChildren );
+ if( aParent != aRoot )
+ mhShellWindow = aParent;
+ int nCount = 0;
+ Atom* pProps = XListProperties( GetDisplay()->GetDisplay(),
+ mhShellWindow,
+ &nCount );
+ for( int i = 0; i < nCount && ! bBreak; ++i )
+ bBreak = (pProps[i] == XA_WM_HINTS);
+ if( pProps )
+ XFree( pProps );
+ } while( aParent != aRoot && ! bBreak );
+
+ // check if this is really one of our own frames
+ // do not change the input mask in that case
+ bool bIsReallyOurFrame = false;
+ for (auto pSalFrame : GetDisplay()->getFrames() )
+ if ( static_cast<const X11SalFrame*>( pSalFrame )->GetWindow() == mhForeignParent )
+ {
+ bIsReallyOurFrame = true;
+ break;
+ }
+ if (!bIsReallyOurFrame)
+ {
+ XSelectInput( GetDisplay()->GetDisplay(), mhForeignParent, StructureNotifyMask | FocusChangeMask );
+ XSelectInput( GetDisplay()->GetDisplay(), mhShellWindow, StructureNotifyMask | FocusChangeMask );
+ }
+ }
+ else
+ {
+ if( ! bUseGeometry )
+ {
+ Size aScreenSize( GetDisplay()->getDataForScreen( m_nXScreen ).m_aSize );
+ w = aScreenSize.Width();
+ h = aScreenSize.Height();
+ if( nSalFrameStyle & SalFrameStyleFlags::SIZEABLE &&
+ nSalFrameStyle & SalFrameStyleFlags::MOVEABLE )
+ {
+ Size aBestFitSize(bestmaxFrameSizeForScreenSize(aScreenSize));
+ w = aBestFitSize.Width();
+ h = aBestFitSize.Height();
+ }
+ if( ! mpParent )
+ {
+ // find the last document window (if any)
+ const X11SalFrame* pFrame = nullptr;
+ bool bIsDocumentWindow = false;
+ for (auto pSalFrame : GetDisplay()->getFrames() )
+ {
+ pFrame = static_cast< const X11SalFrame* >( pSalFrame );
+ if( !pFrame->mpParent
+ && !pFrame->mbFullScreen
+ && ( pFrame->nStyle_ & SalFrameStyleFlags::SIZEABLE )
+ && pFrame->GetUnmirroredGeometry().nWidth
+ && pFrame->GetUnmirroredGeometry().nHeight )
+ {
+ bIsDocumentWindow = true;
+ break;
+ }
+ }
+
+ if( bIsDocumentWindow )
+ {
+ // set a document position and size
+ // the first frame gets positioned by the window manager
+ const SalFrameGeometry& rGeom( pFrame->GetUnmirroredGeometry() );
+ x = rGeom.nX;
+ y = rGeom.nY;
+ if( x+static_cast<int>(w)+40 <= static_cast<int>(aScreenSize.Width()) &&
+ y+static_cast<int>(h)+40 <= static_cast<int>(aScreenSize.Height())
+ )
+ {
+ y += 40;
+ x += 40;
+ }
+ else
+ {
+ x = 10; // leave some space for decoration
+ y = 20;
+ }
+ }
+ else if( GetDisplay()->IsXinerama() )
+ {
+ // place frame on same screen as mouse pointer
+ ::Window aRoot, aChild;
+ int root_x = 0, root_y = 0, lx, ly;
+ unsigned int mask;
+ XQueryPointer( GetXDisplay(),
+ GetDisplay()->GetRootWindow( m_nXScreen ),
+ &aRoot, &aChild,
+ &root_x, &root_y, &lx, &ly, &mask );
+ const std::vector< tools::Rectangle >& rScreens = GetDisplay()->GetXineramaScreens();
+ for(const auto & rScreen : rScreens)
+ if( rScreen.Contains( Point( root_x, root_y ) ) )
+ {
+ x = rScreen.Left();
+ y = rScreen.Top();
+ break;
+ }
+ }
+ }
+ }
+ Attributes.win_gravity = pDisplay_->getWMAdaptor()->getInitWinGravity();
+ nAttrMask |= CWWinGravity;
+ if( mpParent )
+ {
+ Attributes.save_under = True;
+ nAttrMask |= CWSaveUnder;
+ }
+ if( IsOverrideRedirect() )
+ Attributes.override_redirect = True;
+ // default icon
+ if( !(nStyle_ & SalFrameStyleFlags::INTRO) && !(nStyle_ & SalFrameStyleFlags::NOICON))
+ {
+ bool bOk=false;
+ try
+ {
+ bOk = lcl_SelectAppIconPixmap( pDisplay_, m_nXScreen,
+ mnIconID != SV_ICON_ID_OFFICE ? mnIconID :
+ (mpParent ? mpParent->mnIconID : SV_ICON_ID_OFFICE), 32,
+ Hints.icon_pixmap, Hints.icon_mask, netwm_icon );
+ }
+ catch( css::uno::Exception& )
+ {
+ // can happen - no ucb during early startup
+ }
+ if( bOk )
+ {
+ Hints.flags |= IconPixmapHint;
+ if( Hints.icon_mask )
+ Hints.flags |= IconMaskHint;
+ }
+ }
+
+ // find the top level frame of the transience hierarchy
+ X11SalFrame* pFrame = this;
+ while( pFrame->mpParent )
+ pFrame = pFrame->mpParent;
+ if( pFrame->nStyle_ & SalFrameStyleFlags::PLUG )
+ {
+ // if the top level window is a plugin window,
+ // then we should place us in the same window group as
+ // the parent application (or none if there is no window group
+ // hint in the parent).
+ if( pFrame->GetShellWindow() )
+ {
+ XWMHints* pWMHints = XGetWMHints( pDisplay_->GetDisplay(),
+ pFrame->GetShellWindow() );
+ if( pWMHints )
+ {
+ if( pWMHints->flags & WindowGroupHint )
+ {
+ Hints.flags |= WindowGroupHint;
+ Hints.window_group = pWMHints->window_group;
+ }
+ XFree( pWMHints );
+ }
+ }
+ }
+ else
+ {
+ Hints.flags |= WindowGroupHint;
+ Hints.window_group = pFrame->GetShellWindow();
+ // note: for a normal document window this will produce None
+ // as the window is not yet created and the shell window is
+ // initialized to None. This must be corrected after window creation.
+ aClientLeader = GetDisplay()->GetDrawable( m_nXScreen );
+ }
+ }
+
+ nShowState_ = X11ShowState::Unknown;
+ bViewable_ = true;
+ bMapped_ = false;
+ nVisibility_ = VisibilityFullyObscured;
+ mhWindow = XCreateWindow( GetXDisplay(),
+ aFrameParent,
+ x, y,
+ w, h,
+ 0,
+ rVis.GetDepth(),
+ InputOutput,
+ rVis.GetVisual(),
+ nAttrMask,
+ &Attributes );
+ // FIXME: see above: fake shell window for now to own window
+ if( pParentData == nullptr )
+ {
+ mhShellWindow = mhWindow;
+ }
+
+ // correct window group if necessary
+ if( (Hints.flags & WindowGroupHint) == WindowGroupHint )
+ {
+ if( Hints.window_group == None )
+ Hints.window_group = GetShellWindow();
+ }
+
+ maGeometry.nX = x;
+ maGeometry.nY = y;
+ maGeometry.nWidth = w;
+ maGeometry.nHeight = h;
+ updateScreenNumber();
+
+ XSync( GetXDisplay(), False );
+ setXEmbedInfo();
+
+ Time nUserTime = (nStyle_ & (SalFrameStyleFlags::OWNERDRAWDECORATION | SalFrameStyleFlags::TOOLWINDOW) ) == SalFrameStyleFlags::NONE ?
+ pDisplay_->GetLastUserEventTime() : 0;
+ pDisplay_->getWMAdaptor()->setUserTime( this, nUserTime );
+
+ if( ! pParentData && ! IsChildWindow() && ! Attributes.override_redirect )
+ {
+ XSetWMHints( GetXDisplay(), mhWindow, &Hints );
+ // WM Protocols && internals
+ Atom a[3];
+ int n = 0;
+ a[n++] = pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::WM_DELETE_WINDOW );
+
+// LibreOffice advertises NET_WM_PING atom, so mutter rightfully warns of an unresponsive application during debugging.
+// Hack that out unconditionally for debug builds, as per https://bugzilla.redhat.com/show_bug.cgi?id=981149
+// upstream refuses to make this configurable in any way.
+// NOTE: You need to use the 'gen' backend for this to work (SAL_USE_VCLPLUGIN=gen)
+#if OSL_DEBUG_LEVEL < 1
+ if( pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_PING ) )
+ a[n++] = pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_PING );
+#endif
+
+ if( nSalFrameStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
+ a[n++] = pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::WM_TAKE_FOCUS );
+ XSetWMProtocols( GetXDisplay(), GetShellWindow(), a, n );
+
+ // force wm class hint
+ mnExtStyle = ~0;
+ if (mpParent)
+ m_sWMClass = mpParent->m_sWMClass;
+ SetExtendedFrameStyle( 0 );
+
+ XSizeHints* pHints = XAllocSizeHints();
+ pHints->flags = PWinGravity | PPosition;
+ pHints->win_gravity = GetDisplay()->getWMAdaptor()->getPositionWinGravity();
+ pHints->x = 0;
+ pHints->y = 0;
+ if( mbFullScreen )
+ {
+ pHints->flags |= PMaxSize | PMinSize;
+ pHints->max_width = w+100;
+ pHints->max_height = h+100;
+ pHints->min_width = w;
+ pHints->min_height = h;
+ }
+ XSetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints );
+ XFree (pHints);
+
+ // set PID and WM_CLIENT_MACHINE
+ pDisplay_->getWMAdaptor()->setClientMachine( this );
+ pDisplay_->getWMAdaptor()->setPID( this );
+
+ // set client leader
+ if( aClientLeader )
+ {
+ XChangeProperty( GetXDisplay(),
+ mhWindow,
+ pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::WM_CLIENT_LEADER),
+ XA_WINDOW,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(&aClientLeader),
+ 1
+ );
+ }
+#define DECOFLAGS (SalFrameStyleFlags::MOVEABLE | SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::CLOSEABLE)
+ int nDecoFlags = WMAdaptor::decoration_All;
+ if( (nStyle_ & SalFrameStyleFlags::PARTIAL_FULLSCREEN) ||
+ (nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION)
+ )
+ nDecoFlags = 0;
+ else if( (nStyle_ & DECOFLAGS ) != DECOFLAGS || (nStyle_ & SalFrameStyleFlags::TOOLWINDOW) )
+ {
+ if( nStyle_ & DECOFLAGS )
+ // if any decoration, then show a border
+ nDecoFlags = WMAdaptor::decoration_Border;
+ else
+ nDecoFlags = 0;
+
+ if( ! mpParent && (nStyle_ & DECOFLAGS) )
+ // don't add a min button if window should be decorationless
+ nDecoFlags |= WMAdaptor::decoration_MinimizeBtn;
+ if( nStyle_ & SalFrameStyleFlags::CLOSEABLE )
+ nDecoFlags |= WMAdaptor::decoration_CloseBtn;
+ if( nStyle_ & SalFrameStyleFlags::SIZEABLE )
+ {
+ nDecoFlags |= WMAdaptor::decoration_Resize;
+ if( ! (nStyle_ & SalFrameStyleFlags::TOOLWINDOW) )
+ nDecoFlags |= WMAdaptor::decoration_MaximizeBtn;
+ }
+ if( nStyle_ & SalFrameStyleFlags::MOVEABLE )
+ nDecoFlags |= WMAdaptor::decoration_Title;
+ }
+
+ WMWindowType eType = WMWindowType::Normal;
+ if( nStyle_ & SalFrameStyleFlags::INTRO )
+ eType = WMWindowType::Splash;
+ if( (nStyle_ & SalFrameStyleFlags::DIALOG) && hPresentationWindow == None )
+ eType = WMWindowType::ModelessDialogue;
+ if( nStyle_ & SalFrameStyleFlags::TOOLWINDOW )
+ eType = WMWindowType::Utility;
+ if( nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION )
+ eType = WMWindowType::Toolbar;
+ if( (nStyle_ & SalFrameStyleFlags::PARTIAL_FULLSCREEN)
+ && GetDisplay()->getWMAdaptor()->isLegacyPartialFullscreen() )
+ eType = WMWindowType::Dock;
+
+ GetDisplay()->getWMAdaptor()->
+ setFrameTypeAndDecoration( this,
+ eType,
+ nDecoFlags,
+ hPresentationWindow ? nullptr : mpParent );
+
+ if( (nStyle_ & (SalFrameStyleFlags::DEFAULT |
+ SalFrameStyleFlags::OWNERDRAWDECORATION|
+ SalFrameStyleFlags::FLOAT |
+ SalFrameStyleFlags::INTRO |
+ SalFrameStyleFlags::PARTIAL_FULLSCREEN) )
+ == SalFrameStyleFlags::DEFAULT )
+ pDisplay_->getWMAdaptor()->maximizeFrame( this );
+
+ if( !netwm_icon.empty() && GetDisplay()->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_ICON ))
+ XChangeProperty( GetXDisplay(), mhWindow,
+ GetDisplay()->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_ICON ),
+ XA_CARDINAL, 32, PropModeReplace, reinterpret_cast<unsigned char*>(netwm_icon.data()), netwm_icon.size());
+ }
+
+ m_nWorkArea = GetDisplay()->getWMAdaptor()->getCurrentWorkArea();
+
+ // Pointer
+ SetPointer( PointerStyle::Arrow );
+}
+
+X11SalFrame::X11SalFrame( SalFrame *pParent, SalFrameStyleFlags nSalFrameStyle,
+ SystemParentData const * pSystemParent ) :
+ m_nXScreen( 0 ),
+ maAlwaysOnTopRaiseTimer( "vcl::X11SalFrame maAlwaysOnTopRaiseTimer" )
+{
+ GenericUnixSalData *pData = GetGenericUnixSalData();
+
+ mpParent = static_cast< X11SalFrame* >( pParent );
+
+ mbTransientForRoot = false;
+
+ pDisplay_ = vcl_sal::getSalDisplay(pData);
+ // insert frame in framelist
+ pDisplay_->registerFrame( this );
+
+ mhWindow = None;
+ mhShellWindow = None;
+ mhStackingWindow = None;
+ mhForeignParent = None;
+ m_bSetFocusOnMap = false;
+
+ pGraphics_ = nullptr;
+ pFreeGraphics_ = nullptr;
+
+ hCursor_ = None;
+ nCaptured_ = 0;
+
+ mbSendExtKeyModChange = false;
+ mnExtKeyMod = ModKeyFlags::NONE;
+
+ nShowState_ = X11ShowState::Unknown;
+ nWidth_ = 0;
+ nHeight_ = 0;
+ nStyle_ = SalFrameStyleFlags::NONE;
+ mnExtStyle = 0;
+ bAlwaysOnTop_ = false;
+
+ // set bViewable_ to true: hack GetClientSize to report something
+ // different to 0/0 before first map
+ bViewable_ = true;
+ bMapped_ = false;
+ bDefaultPosition_ = true;
+ nVisibility_ = VisibilityFullyObscured;
+ m_nWorkArea = 0;
+ m_bXEmbed = false;
+
+
+ mpInputContext = nullptr;
+ mbInputFocus = False;
+
+ maAlwaysOnTopRaiseTimer.SetInvokeHandler( LINK( this, X11SalFrame, HandleAlwaysOnTopRaise ) );
+ maAlwaysOnTopRaiseTimer.SetTimeout( 100 );
+
+ meWindowType = WMWindowType::Normal;
+ mbMaximizedVert = false;
+ mbMaximizedHorz = false;
+ mbFullScreen = false;
+
+ mnIconID = SV_ICON_ID_OFFICE;
+
+ if( mpParent )
+ mpParent->maChildren.push_back( this );
+
+ Init( nSalFrameStyle, GetDisplay()->GetDefaultXScreen(), pSystemParent );
+}
+
+X11SalFrame::~X11SalFrame()
+{
+ notifyDelete();
+
+ m_vClipRectangles.clear();
+
+ if( mhStackingWindow )
+ aPresentationReparentList.remove( mhStackingWindow );
+
+ // remove from parent's list
+ if( mpParent )
+ mpParent->maChildren.remove( this );
+
+ // deregister on SalDisplay
+ pDisplay_->deregisterFrame( this );
+
+ // unselect all events, some may be still in the queue anyway
+ if( ! IsSysChildWindow() )
+ XSelectInput( GetXDisplay(), GetShellWindow(), 0 );
+ XSelectInput( GetXDisplay(), GetWindow(), 0 );
+
+ ShowFullScreen( false, 0 );
+
+ if( bMapped_ )
+ Show( false );
+
+ if( mpInputContext )
+ {
+ mpInputContext->UnsetICFocus();
+ mpInputContext->Unmap();
+ mpInputContext.reset();
+ }
+
+ if( GetWindow() == hPresentationWindow )
+ {
+ hPresentationWindow = None;
+ doReparentPresentationDialogues( GetDisplay() );
+ }
+
+ if( pGraphics_ )
+ {
+ pGraphics_->DeInit();
+ pGraphics_.reset();
+ }
+
+ if( pFreeGraphics_ )
+ {
+ pFreeGraphics_->DeInit();
+ pFreeGraphics_.reset();
+ }
+
+ // reset all OpenGL contexts using this window
+ rtl::Reference<OpenGLContext> pContext = ImplGetSVData()->maGDIData.mpLastContext;
+ while( pContext.is() )
+ {
+ if (static_cast<const GLX11Window&>(pContext->getOpenGLWindow()).win == mhWindow)
+ pContext->reset();
+ pContext = pContext->mpPrevContext;
+ }
+
+ XDestroyWindow( GetXDisplay(), mhWindow );
+}
+
+void X11SalFrame::SetExtendedFrameStyle( SalExtStyle nStyle )
+{
+ if( nStyle != mnExtStyle && ! IsChildWindow() )
+ {
+ mnExtStyle = nStyle;
+ updateWMClass();
+ }
+}
+
+const SystemEnvData* X11SalFrame::GetSystemData() const
+{
+ X11SalFrame *pFrame = const_cast<X11SalFrame*>(this);
+ pFrame->maSystemChildData.pDisplay = GetXDisplay();
+ pFrame->maSystemChildData.SetWindowHandle(pFrame->GetWindow());
+ pFrame->maSystemChildData.pSalFrame = pFrame;
+ pFrame->maSystemChildData.pWidget = nullptr;
+ pFrame->maSystemChildData.pVisual = GetDisplay()->GetVisual( m_nXScreen ).GetVisual();
+ pFrame->maSystemChildData.nScreen = m_nXScreen.getXScreen();
+ pFrame->maSystemChildData.aShellWindow = pFrame->GetShellWindow();
+ pFrame->maSystemChildData.toolkit = SystemEnvData::Toolkit::Gen;
+ pFrame->maSystemChildData.platform = SystemEnvData::Platform::Xcb;
+ return &maSystemChildData;
+}
+
+SalGraphics *X11SalFrame::AcquireGraphics()
+{
+ if( pGraphics_ )
+ return nullptr;
+
+ if( pFreeGraphics_ )
+ {
+ pGraphics_ = std::move(pFreeGraphics_);
+ }
+ else
+ {
+ pGraphics_.reset(new X11SalGraphics());
+ pGraphics_->Init( this, GetWindow(), m_nXScreen );
+ }
+
+ return pGraphics_.get();
+}
+
+void X11SalFrame::ReleaseGraphics( SalGraphics *pGraphics )
+{
+ SAL_WARN_IF( pGraphics != pGraphics_.get(), "vcl", "SalFrame::ReleaseGraphics pGraphics!=pGraphics_" );
+
+ if( pGraphics != pGraphics_.get() )
+ return;
+
+ pFreeGraphics_ = std::move(pGraphics_);
+}
+
+void X11SalFrame::updateGraphics( bool bClear )
+{
+ Drawable aDrawable = bClear ? None : GetWindow();
+ if( pGraphics_ )
+ pGraphics_->SetDrawable( aDrawable, nullptr, m_nXScreen );
+ if( pFreeGraphics_ )
+ pFreeGraphics_->SetDrawable( aDrawable, nullptr, m_nXScreen );
+}
+
+void X11SalFrame::SetIcon( sal_uInt16 nIcon )
+{
+ if ( IsChildWindow() )
+ return;
+
+ // 0 == default icon -> #1
+ if ( nIcon == 0 )
+ nIcon = 1;
+
+ mnIconID = nIcon;
+
+ XIconSize *pIconSize = nullptr;
+ int nSizes = 0;
+ int iconSize = 32;
+ if ( XGetIconSizes( GetXDisplay(), GetDisplay()->GetRootWindow( m_nXScreen ), &pIconSize, &nSizes ) )
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.window", "X11SalFrame::SetIcon(): found "
+ << nSizes
+ << " IconSizes:");
+#endif
+ int i;
+ for( i=0; i<nSizes; i++)
+ {
+ // select largest supported icon
+ if( pIconSize[i].max_width > iconSize )
+ {
+ iconSize = pIconSize[i].max_width;
+ }
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.window", "min: "
+ << pIconSize[i].min_width
+ << ", "
+ << pIconSize[i].min_height);
+ SAL_INFO("vcl.window", "max: "
+ << pIconSize[i].max_width
+ << ", "
+ << pIconSize[i].max_height);
+ SAL_INFO("vcl.window", "inc: "
+ << pIconSize[i].width_inc
+ << ", "
+ << pIconSize[i].height_inc);
+#endif
+ }
+
+ XFree( pIconSize );
+ }
+ else
+ {
+ const OUString& rWM( pDisplay_->getWMAdaptor()->getWindowManagerName() );
+ if( rWM == "KWin" ) // assume KDE is running
+ iconSize = 48;
+ static bool bGnomeIconSize = false;
+ static bool bGnomeChecked = false;
+ if( ! bGnomeChecked )
+ {
+ bGnomeChecked=true;
+ int nCount = 0;
+ Atom* pProps = XListProperties( GetXDisplay(),
+ GetDisplay()->GetRootWindow( m_nXScreen ),
+ &nCount );
+ for( int i = 0; i < nCount && !bGnomeIconSize; i++ )
+ {
+ char* pName = XGetAtomName( GetXDisplay(), pProps[i] );
+ if( pName )
+ {
+ if( !strcmp( pName, "GNOME_PANEL_DESKTOP_AREA" ) )
+ bGnomeIconSize = true;
+ XFree( pName );
+ }
+ }
+ if( pProps )
+ XFree( pProps );
+ }
+ if( bGnomeIconSize )
+ iconSize = 48;
+ }
+
+ XWMHints Hints;
+ Hints.flags = 0;
+ XWMHints *pHints = XGetWMHints( GetXDisplay(), GetShellWindow() );
+ if( pHints )
+ {
+ memcpy(&Hints, pHints, sizeof( XWMHints ));
+ XFree( pHints );
+ }
+ pHints = &Hints;
+
+ NetWmIconData netwm_icon;
+ bool bOk = lcl_SelectAppIconPixmap( GetDisplay(), m_nXScreen,
+ nIcon, iconSize,
+ pHints->icon_pixmap, pHints->icon_mask, netwm_icon );
+ if ( !bOk )
+ {
+ // load default icon (0)
+ bOk = lcl_SelectAppIconPixmap( GetDisplay(), m_nXScreen,
+ 0, iconSize,
+ pHints->icon_pixmap, pHints->icon_mask, netwm_icon );
+ }
+ if( bOk )
+ {
+ pHints->flags |= IconPixmapHint;
+ if( pHints->icon_mask )
+ pHints->flags |= IconMaskHint;
+
+ XSetWMHints( GetXDisplay(), GetShellWindow(), pHints );
+ if( !netwm_icon.empty() && GetDisplay()->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_ICON ))
+ XChangeProperty( GetXDisplay(), mhWindow,
+ GetDisplay()->getWMAdaptor()->getAtom( WMAdaptor::NET_WM_ICON ),
+ XA_CARDINAL, 32, PropModeReplace, reinterpret_cast<unsigned char*>(netwm_icon.data()), netwm_icon.size());
+ }
+
+}
+
+void X11SalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight )
+{
+ if( IsChildWindow() )
+ return;
+
+ if( !GetShellWindow() ||
+ (nStyle_ & (SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::OWNERDRAWDECORATION) ) == SalFrameStyleFlags::FLOAT )
+ return;
+
+ XSizeHints* pHints = XAllocSizeHints();
+ tools::Long nSupplied = 0;
+ XGetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints,
+ &nSupplied
+ );
+ pHints->max_width = nWidth;
+ pHints->max_height = nHeight;
+ pHints->flags |= PMaxSize;
+ XSetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints );
+ XFree( pHints );
+}
+
+void X11SalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight )
+{
+ if( IsChildWindow() )
+ return;
+
+ if( !GetShellWindow() ||
+ (nStyle_ & (SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::OWNERDRAWDECORATION) ) == SalFrameStyleFlags::FLOAT )
+ return;
+
+ XSizeHints* pHints = XAllocSizeHints();
+ tools::Long nSupplied = 0;
+ XGetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints,
+ &nSupplied
+ );
+ pHints->min_width = nWidth;
+ pHints->min_height = nHeight;
+ pHints->flags |= PMinSize;
+ XSetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints );
+ XFree( pHints );
+}
+
+// Show + Pos (x,y,z) + Size (width,height)
+
+void X11SalFrame::Show( bool bVisible, bool bNoActivate )
+{
+ if( ( bVisible && bMapped_ )
+ || ( !bVisible && !bMapped_ ) )
+ return;
+
+ // HACK: this is a workaround for (at least) kwin
+ // even though transient frames should be kept above their parent
+ // this does not necessarily hold true for DOCK type windows
+ // so artificially set ABOVE and remove it again on hide
+ if( mpParent && (mpParent->nStyle_ & SalFrameStyleFlags::PARTIAL_FULLSCREEN ) && pDisplay_->getWMAdaptor()->isLegacyPartialFullscreen())
+ pDisplay_->getWMAdaptor()->enableAlwaysOnTop( this, bVisible );
+
+ bMapped_ = bVisible;
+ bViewable_ = bVisible;
+ setXEmbedInfo();
+ if( bVisible )
+ {
+ if( ! (nStyle_ & SalFrameStyleFlags::INTRO) )
+ {
+ // hide all INTRO frames
+ for (auto pSalFrame : GetDisplay()->getFrames() )
+ {
+ const X11SalFrame* pFrame = static_cast< const X11SalFrame* >( pSalFrame );
+ // look for intro bit map; if present, hide it
+ if( pFrame->nStyle_ & SalFrameStyleFlags::INTRO )
+ {
+ if( pFrame->bMapped_ )
+ const_cast<X11SalFrame*>(pFrame)->Show( false );
+ }
+ }
+ }
+
+ // update NET_WM_STATE which may have been deleted due to earlier Show(false)
+ if( nShowState_ == X11ShowState::Hidden )
+ GetDisplay()->getWMAdaptor()->frameIsMapping( this );
+
+ /*
+ * Actually this is rather exotic and currently happens only in conjunction
+ * with the basic dialogue editor,
+ * which shows a frame and instantly hides it again. After that the
+ * editor window is shown and the WM takes this as an opportunity
+ * to show our hidden transient frame also. So Show( false ) must
+ * withdraw the frame AND delete the WM_TRANSIENT_FOR property.
+ * In case the frame is shown again, the transient hint must be restored here.
+ */
+ if( ! IsChildWindow()
+ && ! IsOverrideRedirect()
+ && ! IsFloatGrabWindow()
+ && mpParent
+ )
+ {
+ GetDisplay()->getWMAdaptor()->changeReferenceFrame( this, mpParent );
+ }
+
+ // #i45160# switch to desktop where a dialog with parent will appear
+ if( mpParent && mpParent->m_nWorkArea != m_nWorkArea )
+ GetDisplay()->getWMAdaptor()->switchToWorkArea( mpParent->m_nWorkArea );
+
+ if( IsFloatGrabWindow() &&
+ mpParent &&
+ nVisibleFloats == 0 &&
+ ! GetDisplay()->GetCaptureFrame() )
+ {
+ /* #i39420#
+ * outsmart KWin's "focus strictly under mouse" mode
+ * which insists on taking the focus from the document
+ * to the new float. Grab focus to parent frame BEFORE
+ * showing the float (cannot grab it to the float
+ * before show).
+ */
+ XGrabPointer( GetXDisplay(),
+ mpParent->GetWindow(),
+ True,
+ PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
+ GrabModeAsync,
+ GrabModeAsync,
+ None,
+ mpParent ? mpParent->GetCursor() : None,
+ CurrentTime
+ );
+ }
+
+ Time nUserTime = 0;
+ if( ! bNoActivate && !(nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION) )
+ nUserTime = pDisplay_->GetX11ServerTime();
+ GetDisplay()->getWMAdaptor()->setUserTime( this, nUserTime );
+ if( ! bNoActivate && (nStyle_ & SalFrameStyleFlags::TOOLWINDOW) )
+ m_bSetFocusOnMap = true;
+
+ // actually map the window
+ if( m_bXEmbed )
+ askForXEmbedFocus( 0 );
+ else
+ {
+ if( GetWindow() != GetShellWindow() && ! IsSysChildWindow() )
+ {
+ if( IsChildWindow() )
+ XMapWindow( GetXDisplay(), GetShellWindow() );
+ XSelectInput( GetXDisplay(), GetShellWindow(), CLIENT_EVENTS );
+ }
+ if( nStyle_ & SalFrameStyleFlags::FLOAT )
+ XMapRaised( GetXDisplay(), GetWindow() );
+ else
+ XMapWindow( GetXDisplay(), GetWindow() );
+ }
+ XSelectInput( GetXDisplay(), GetWindow(), CLIENT_EVENTS );
+
+ if( maGeometry.nWidth > 0
+ && maGeometry.nHeight > 0
+ && ( nWidth_ != static_cast<int>(maGeometry.nWidth)
+ || nHeight_ != static_cast<int>(maGeometry.nHeight) ) )
+ {
+ nWidth_ = maGeometry.nWidth;
+ nHeight_ = maGeometry.nHeight;
+ }
+
+ XSync( GetXDisplay(), False );
+
+ if( IsFloatGrabWindow() )
+ {
+ /*
+ * Sawfish and twm can be switched to enter-exit focus behaviour. In this case
+ * we must grab the pointer else the dumb WM will put the focus to the
+ * override-redirect float window. The application window will be deactivated
+ * which causes that the floats are destroyed, so the user can never click on
+ * a menu because it vanishes as soon as he enters it.
+ */
+ nVisibleFloats++;
+ if( nVisibleFloats == 1 && ! GetDisplay()->GetCaptureFrame() )
+ {
+ /* #i39420# now move grab to the new float window */
+ XGrabPointer( GetXDisplay(),
+ GetWindow(),
+ True,
+ PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
+ GrabModeAsync,
+ GrabModeAsync,
+ None,
+ mpParent ? mpParent->GetCursor() : None,
+ CurrentTime
+ );
+ }
+ }
+ CallCallback( SalEvent::Resize, nullptr );
+
+ /*
+ * sometimes a message box/dialogue is brought up when a frame is not mapped
+ * the corresponding TRANSIENT_FOR hint is then set to the root window
+ * so that the dialogue shows in all cases. Correct it here if the
+ * frame is shown afterwards.
+ */
+ if( ! IsChildWindow()
+ && ! IsOverrideRedirect()
+ && ! IsFloatGrabWindow()
+ )
+ {
+ for (auto const& child : maChildren)
+ {
+ if( child->mbTransientForRoot )
+ GetDisplay()->getWMAdaptor()->changeReferenceFrame( child, this );
+ }
+ }
+ /*
+ * leave X11ShowState::Unknown as this indicates first mapping
+ * and is only reset int HandleSizeEvent
+ */
+ if( nShowState_ != X11ShowState::Unknown )
+ nShowState_ = X11ShowState::Normal;
+
+ /*
+ * plugged windows don't necessarily get the
+ * focus on show because the parent may already be mapped
+ * and have the focus. So try to set the focus
+ * to the child on Show(true)
+ */
+ if( (nStyle_ & SalFrameStyleFlags::PLUG) && ! m_bXEmbed )
+ XSetInputFocus( GetXDisplay(),
+ GetWindow(),
+ RevertToParent,
+ CurrentTime );
+
+ if( mpParent )
+ {
+ // push this frame so it will be in front of its siblings
+ // only necessary for insane transient behaviour of Dtwm/olwm
+ mpParent->maChildren.remove( this );
+ mpParent->maChildren.push_front(this);
+ }
+ }
+ else
+ {
+ if( getInputContext() )
+ getInputContext()->Unmap();
+
+ if( ! IsChildWindow() )
+ {
+ /* FIXME: Is deleting the property really necessary ? It hurts
+ * owner drawn windows at least.
+ */
+ if( mpParent && ! (nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION) )
+ XDeleteProperty( GetXDisplay(), GetShellWindow(), GetDisplay()->getWMAdaptor()->getAtom( WMAdaptor::WM_TRANSIENT_FOR ) );
+ XWithdrawWindow( GetXDisplay(), GetShellWindow(), m_nXScreen.getXScreen() );
+ }
+ else if( ! m_bXEmbed )
+ XUnmapWindow( GetXDisplay(), GetWindow() );
+
+ nShowState_ = X11ShowState::Hidden;
+ if( IsFloatGrabWindow() && nVisibleFloats )
+ {
+ nVisibleFloats--;
+ if( nVisibleFloats == 0 && ! GetDisplay()->GetCaptureFrame() )
+ XUngrabPointer( GetXDisplay(),
+ CurrentTime );
+ }
+ // flush here; there may be a very seldom race between
+ // the display connection used for clipboard and our connection
+ Flush();
+ }
+}
+
+void X11SalFrame::ToTop( SalFrameToTop nFlags )
+{
+ if( ( nFlags & SalFrameToTop::RestoreWhenMin )
+ && ! ( nStyle_ & SalFrameStyleFlags::FLOAT )
+ && nShowState_ != X11ShowState::Hidden
+ && nShowState_ != X11ShowState::Unknown
+ )
+ {
+ GetDisplay()->getWMAdaptor()->frameIsMapping( this );
+ if( GetWindow() != GetShellWindow() && ! IsSysChildWindow() )
+ XMapWindow( GetXDisplay(), GetShellWindow() );
+ XMapWindow( GetXDisplay(), GetWindow() );
+ }
+
+ ::Window aToTopWindow = IsSysChildWindow() ? GetWindow() : GetShellWindow();
+ if( ! (nFlags & SalFrameToTop::GrabFocusOnly) )
+ {
+ XRaiseWindow( GetXDisplay(), aToTopWindow );
+ }
+
+ if( ( ( nFlags & SalFrameToTop::GrabFocus ) || ( nFlags & SalFrameToTop::GrabFocusOnly ) )
+ && bMapped_ )
+ {
+ if( m_bXEmbed )
+ askForXEmbedFocus( 0 );
+ else
+ XSetInputFocus( GetXDisplay(), aToTopWindow, RevertToParent, CurrentTime );
+ }
+ else if( ( nFlags & SalFrameToTop::RestoreWhenMin ) || ( nFlags & SalFrameToTop::ForegroundTask ) )
+ {
+ Time nTimestamp = pDisplay_->GetX11ServerTime();
+ GetDisplay()->getWMAdaptor()->activateWindow( this, nTimestamp );
+ }
+}
+
+void X11SalFrame::GetWorkArea( tools::Rectangle& rWorkArea )
+{
+ rWorkArea = pDisplay_->getWMAdaptor()->getWorkArea( 0 );
+}
+
+void X11SalFrame::GetClientSize( tools::Long &rWidth, tools::Long &rHeight )
+{
+ if( ! bViewable_ )
+ {
+ rWidth = rHeight = 0;
+ return;
+ }
+
+ rWidth = maGeometry.nWidth;
+ rHeight = maGeometry.nHeight;
+
+ if( !rWidth || !rHeight )
+ {
+ XWindowAttributes aAttrib;
+
+ XGetWindowAttributes( GetXDisplay(), GetWindow(), &aAttrib );
+
+ maGeometry.nWidth = rWidth = aAttrib.width;
+ maGeometry.nHeight = rHeight = aAttrib.height;
+ }
+}
+
+void X11SalFrame::Center( )
+{
+ int nX, nY, nScreenWidth, nScreenHeight;
+ int nRealScreenWidth, nRealScreenHeight;
+ int nScreenX = 0, nScreenY = 0;
+
+ const Size& aScreenSize = GetDisplay()->getDataForScreen( m_nXScreen ).m_aSize;
+ nScreenWidth = aScreenSize.Width();
+ nScreenHeight = aScreenSize.Height();
+ nRealScreenWidth = nScreenWidth;
+ nRealScreenHeight = nScreenHeight;
+
+ if( GetDisplay()->IsXinerama() )
+ {
+ // get xinerama screen we are on
+ // if there is a parent, use its center for screen determination
+ // else use the pointer
+ ::Window aRoot, aChild;
+ int root_x, root_y, x, y;
+ unsigned int mask;
+ if( mpParent )
+ {
+ root_x = mpParent->maGeometry.nX + mpParent->maGeometry.nWidth/2;
+ root_y = mpParent->maGeometry.nY + mpParent->maGeometry.nHeight/2;
+ }
+ else
+ XQueryPointer( GetXDisplay(),
+ GetShellWindow(),
+ &aRoot, &aChild,
+ &root_x, &root_y,
+ &x, &y,
+ &mask );
+ const std::vector< tools::Rectangle >& rScreens = GetDisplay()->GetXineramaScreens();
+ for(const auto & rScreen : rScreens)
+ if( rScreen.Contains( Point( root_x, root_y ) ) )
+ {
+ nScreenX = rScreen.Left();
+ nScreenY = rScreen.Top();
+ nRealScreenWidth = rScreen.GetWidth();
+ nRealScreenHeight = rScreen.GetHeight();
+ break;
+ }
+ }
+
+ if( mpParent )
+ {
+ X11SalFrame* pFrame = mpParent;
+ while( pFrame->mpParent )
+ pFrame = pFrame->mpParent;
+ if( pFrame->maGeometry.nWidth < 1 || pFrame->maGeometry.nHeight < 1 )
+ {
+ tools::Rectangle aRect;
+ pFrame->GetPosSize( aRect );
+ pFrame->maGeometry.nX = aRect.Left();
+ pFrame->maGeometry.nY = aRect.Top();
+ pFrame->maGeometry.nWidth = aRect.GetWidth();
+ pFrame->maGeometry.nHeight = aRect.GetHeight();
+ }
+
+ if( pFrame->nStyle_ & SalFrameStyleFlags::PLUG )
+ {
+ ::Window aRoot;
+ unsigned int bw, depth;
+ XGetGeometry( GetXDisplay(),
+ pFrame->GetShellWindow(),
+ &aRoot,
+ &nScreenX, &nScreenY,
+ reinterpret_cast<unsigned int*>(&nScreenWidth),
+ reinterpret_cast<unsigned int*>(&nScreenHeight),
+ &bw, &depth );
+ }
+ else
+ {
+ nScreenX = pFrame->maGeometry.nX;
+ nScreenY = pFrame->maGeometry.nY;
+ nScreenWidth = pFrame->maGeometry.nWidth;
+ nScreenHeight = pFrame->maGeometry.nHeight;
+ }
+ }
+
+ if( mpParent && mpParent->nShowState_ == X11ShowState::Normal )
+ {
+ if( maGeometry.nWidth >= mpParent->maGeometry.nWidth &&
+ maGeometry.nHeight >= mpParent->maGeometry.nHeight )
+ {
+ nX = nScreenX + 40;
+ nY = nScreenY + 40;
+ }
+ else
+ {
+ // center the window relative to the top level frame
+ nX = (nScreenWidth - static_cast<int>(maGeometry.nWidth) ) / 2 + nScreenX;
+ nY = (nScreenHeight - static_cast<int>(maGeometry.nHeight)) / 2 + nScreenY;
+ }
+ }
+ else
+ {
+ // center the window relative to screen
+ nX = (nRealScreenWidth - static_cast<int>(maGeometry.nWidth) ) / 2 + nScreenX;
+ nY = (nRealScreenHeight - static_cast<int>(maGeometry.nHeight)) / 2 + nScreenY;
+ }
+ nX = nX < 0 ? 0 : nX;
+ nY = nY < 0 ? 0 : nY;
+
+ bDefaultPosition_ = False;
+ if( mpParent )
+ {
+ nX -= mpParent->maGeometry.nX;
+ nY -= mpParent->maGeometry.nY;
+ }
+
+ Point aPoint(nX, nY);
+ SetPosSize( tools::Rectangle( aPoint, Size( maGeometry.nWidth, maGeometry.nHeight ) ) );
+}
+
+void X11SalFrame::updateScreenNumber()
+{
+ if( GetDisplay()->IsXinerama() && GetDisplay()->GetXineramaScreens().size() > 1 )
+ {
+ Point aPoint( maGeometry.nX, maGeometry.nY );
+ const std::vector<tools::Rectangle>& rScreenRects( GetDisplay()->GetXineramaScreens() );
+ size_t nScreens = rScreenRects.size();
+ for( size_t i = 0; i < nScreens; i++ )
+ {
+ if( rScreenRects[i].Contains( aPoint ) )
+ {
+ maGeometry.nDisplayScreenNumber = static_cast<unsigned int>(i);
+ break;
+ }
+ }
+ }
+ else
+ maGeometry.nDisplayScreenNumber = m_nXScreen.getXScreen();
+}
+
+void X11SalFrame::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags )
+{
+ if( nStyle_ & SalFrameStyleFlags::PLUG )
+ return;
+
+ // relative positioning in X11SalFrame::SetPosSize
+ tools::Rectangle aPosSize( Point( maGeometry.nX, maGeometry.nY ), Size( maGeometry.nWidth, maGeometry.nHeight ) );
+ aPosSize.Justify();
+
+ if( ! ( nFlags & SAL_FRAME_POSSIZE_X ) )
+ {
+ nX = aPosSize.Left();
+ if( mpParent )
+ nX -= mpParent->maGeometry.nX;
+ }
+ if( ! ( nFlags & SAL_FRAME_POSSIZE_Y ) )
+ {
+ nY = aPosSize.Top();
+ if( mpParent )
+ nY -= mpParent->maGeometry.nY;
+ }
+ if( ! ( nFlags & SAL_FRAME_POSSIZE_WIDTH ) )
+ nWidth = aPosSize.GetWidth();
+ if( ! ( nFlags & SAL_FRAME_POSSIZE_HEIGHT ) )
+ nHeight = aPosSize.GetHeight();
+
+ aPosSize = tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) );
+
+ if( ! ( nFlags & ( SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ) ) )
+ {
+ if( bDefaultPosition_ )
+ {
+ maGeometry.nWidth = aPosSize.GetWidth();
+ maGeometry.nHeight = aPosSize.GetHeight();
+ Center();
+ }
+ else
+ SetSize( Size( nWidth, nHeight ) );
+ }
+ else
+ SetPosSize( aPosSize );
+
+ bDefaultPosition_ = False;
+}
+
+void X11SalFrame::SetAlwaysOnTop( bool bOnTop )
+{
+ if( ! IsOverrideRedirect() )
+ {
+ bAlwaysOnTop_ = bOnTop;
+ pDisplay_->getWMAdaptor()->enableAlwaysOnTop( this, bOnTop );
+ }
+}
+
+constexpr auto FRAMESTATE_MASK_GEOMETRY =
+ WindowStateMask::X | WindowStateMask::Y |
+ WindowStateMask::Width | WindowStateMask::Height;
+constexpr auto FRAMESTATE_MASK_MAXIMIZED_GEOMETRY =
+ WindowStateMask::MaximizedX | WindowStateMask::MaximizedY |
+ WindowStateMask::MaximizedWidth | WindowStateMask::MaximizedHeight;
+
+void X11SalFrame::SetWindowState( const SalFrameState *pState )
+{
+ if (pState == nullptr)
+ return;
+
+ // Request for position or size change
+ if (pState->mnMask & FRAMESTATE_MASK_GEOMETRY)
+ {
+ /* #i44325#
+ * if maximized, set restore size and guess maximized size from last time
+ * in state change below maximize window
+ */
+ if( ! IsChildWindow() &&
+ (pState->mnMask & WindowStateMask::State) &&
+ (pState->mnState & WindowStateState::Maximized) &&
+ (pState->mnMask & FRAMESTATE_MASK_GEOMETRY) == FRAMESTATE_MASK_GEOMETRY &&
+ (pState->mnMask & FRAMESTATE_MASK_MAXIMIZED_GEOMETRY) == FRAMESTATE_MASK_MAXIMIZED_GEOMETRY
+ )
+ {
+ XSizeHints* pHints = XAllocSizeHints();
+ tools::Long nSupplied = 0;
+ XGetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints,
+ &nSupplied );
+ pHints->flags |= PPosition | PWinGravity;
+ pHints->x = pState->mnX;
+ pHints->y = pState->mnY;
+ pHints->win_gravity = pDisplay_->getWMAdaptor()->getPositionWinGravity();
+ XSetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints );
+ XFree( pHints );
+
+ XMoveResizeWindow( GetXDisplay(), GetShellWindow(),
+ pState->mnX, pState->mnY,
+ pState->mnWidth, pState->mnHeight );
+ // guess maximized geometry from last time
+ maGeometry.nX = pState->mnMaximizedX;
+ maGeometry.nY = pState->mnMaximizedY;
+ maGeometry.nWidth = pState->mnMaximizedWidth;
+ maGeometry.nHeight = pState->mnMaximizedHeight;
+ updateScreenNumber();
+ }
+ else
+ {
+ bool bDoAdjust = false;
+ tools::Rectangle aPosSize;
+ // initialize with current geometry
+ if ((pState->mnMask & FRAMESTATE_MASK_GEOMETRY) != FRAMESTATE_MASK_GEOMETRY)
+ GetPosSize (aPosSize);
+
+ sal_uInt16 nPosFlags = 0;
+
+ // change requested properties
+ if (pState->mnMask & WindowStateMask::X)
+ {
+ aPosSize.SetPosX(pState->mnX - (mpParent ? mpParent->maGeometry.nX : 0));
+ nPosFlags |= SAL_FRAME_POSSIZE_X;
+ }
+ if (pState->mnMask & WindowStateMask::Y)
+ {
+ aPosSize.SetPosY(pState->mnY - (mpParent ? mpParent->maGeometry.nY : 0));
+ nPosFlags |= SAL_FRAME_POSSIZE_Y;
+ }
+ if (pState->mnMask & WindowStateMask::Width)
+ {
+ tools::Long nWidth = pState->mnWidth > 0 ? pState->mnWidth - 1 : 0;
+ aPosSize.setWidth (nWidth);
+ bDoAdjust = true;
+ }
+ if (pState->mnMask & WindowStateMask::Height)
+ {
+ int nHeight = pState->mnHeight > 0 ? pState->mnHeight - 1 : 0;
+ aPosSize.setHeight (nHeight);
+ bDoAdjust = true;
+ }
+
+ const Size& aScreenSize = pDisplay_->getDataForScreen( m_nXScreen ).m_aSize;
+
+ if( bDoAdjust && aPosSize.GetWidth() <= aScreenSize.Width()
+ && aPosSize.GetHeight() <= aScreenSize.Height() )
+ {
+ SalFrameGeometry aGeom = maGeometry;
+
+ if( ! (nStyle_ & ( SalFrameStyleFlags::FLOAT | SalFrameStyleFlags::PLUG ) ) &&
+ mpParent &&
+ aGeom.nLeftDecoration == 0 &&
+ aGeom.nTopDecoration == 0 )
+ {
+ aGeom = mpParent->maGeometry;
+ if( aGeom.nLeftDecoration == 0 &&
+ aGeom.nTopDecoration == 0 )
+ {
+ aGeom.nLeftDecoration = 5;
+ aGeom.nTopDecoration = 20;
+ aGeom.nRightDecoration = 5;
+ aGeom.nBottomDecoration = 5;
+ }
+ }
+
+ auto nRight = aPosSize.Right() + (mpParent ? mpParent->maGeometry.nX : 0);
+ auto nBottom = aPosSize.Bottom() + (mpParent ? mpParent->maGeometry.nY : 0);
+ auto nLeft = aPosSize.Left() + (mpParent ? mpParent->maGeometry.nX : 0);
+ auto nTop = aPosSize.Top() + (mpParent ? mpParent->maGeometry.nY : 0);
+
+ // adjust position so that frame fits onto screen
+ if( nRight+static_cast<tools::Long>(aGeom.nRightDecoration) > aScreenSize.Width()-1 )
+ aPosSize.Move( aScreenSize.Width() - nRight - static_cast<tools::Long>(aGeom.nRightDecoration), 0 );
+ if( nBottom+static_cast<tools::Long>(aGeom.nBottomDecoration) > aScreenSize.Height()-1 )
+ aPosSize.Move( 0, aScreenSize.Height() - nBottom - static_cast<tools::Long>(aGeom.nBottomDecoration) );
+ if( nLeft < static_cast<tools::Long>(aGeom.nLeftDecoration) )
+ aPosSize.Move( static_cast<tools::Long>(aGeom.nLeftDecoration) - nLeft, 0 );
+ if( nTop < static_cast<tools::Long>(aGeom.nTopDecoration) )
+ aPosSize.Move( 0, static_cast<tools::Long>(aGeom.nTopDecoration) - nTop );
+ }
+
+ SetPosSize(aPosSize.getX(), aPosSize.getY(),
+ aPosSize.GetWidth(), aPosSize.GetHeight(),
+ SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT |
+ nPosFlags);
+ }
+ }
+
+ // request for status change
+ if (!(pState->mnMask & WindowStateMask::State))
+ return;
+
+ if (pState->mnState & WindowStateState::Maximized)
+ {
+ nShowState_ = X11ShowState::Normal;
+ if( ! (pState->mnState & (WindowStateState::MaximizedHorz|WindowStateState::MaximizedVert) ) )
+ Maximize();
+ else
+ {
+ bool bHorz(pState->mnState & WindowStateState::MaximizedHorz);
+ bool bVert(pState->mnState & WindowStateState::MaximizedVert);
+ GetDisplay()->getWMAdaptor()->maximizeFrame( this, bHorz, bVert );
+ }
+ maRestorePosSize.SetLeft( pState->mnX );
+ maRestorePosSize.SetTop( pState->mnY );
+ maRestorePosSize.SetRight( maRestorePosSize.Left() + pState->mnWidth );
+ maRestorePosSize.SetRight( maRestorePosSize.Left() + pState->mnHeight );
+ }
+ else if( mbMaximizedHorz || mbMaximizedVert )
+ GetDisplay()->getWMAdaptor()->maximizeFrame( this, false, false );
+
+ if (pState->mnState & WindowStateState::Minimized)
+ {
+ if (nShowState_ == X11ShowState::Unknown)
+ nShowState_ = X11ShowState::Normal;
+ Minimize();
+ }
+ if (pState->mnState & WindowStateState::Normal)
+ {
+ if (nShowState_ != X11ShowState::Normal)
+ Restore();
+ }
+}
+
+bool X11SalFrame::GetWindowState( SalFrameState* pState )
+{
+ if( X11ShowState::Minimized == nShowState_ )
+ pState->mnState = WindowStateState::Minimized;
+ else
+ pState->mnState = WindowStateState::Normal;
+
+ tools::Rectangle aPosSize;
+ if( maRestorePosSize.IsEmpty() )
+ GetPosSize( aPosSize );
+ else
+ aPosSize = maRestorePosSize;
+
+ if( mbMaximizedHorz )
+ pState->mnState |= WindowStateState::MaximizedHorz;
+ if( mbMaximizedVert )
+ pState->mnState |= WindowStateState::MaximizedVert;
+
+ pState->mnX = aPosSize.Left();
+ pState->mnY = aPosSize.Top();
+ pState->mnWidth = aPosSize.GetWidth();
+ pState->mnHeight = aPosSize.GetHeight();
+
+ pState->mnMask = FRAMESTATE_MASK_GEOMETRY | WindowStateMask::State;
+
+ if (! maRestorePosSize.IsEmpty() )
+ {
+ GetPosSize( aPosSize );
+ pState->mnState |= WindowStateState::Maximized;
+ pState->mnMaximizedX = aPosSize.Left();
+ pState->mnMaximizedY = aPosSize.Top();
+ pState->mnMaximizedWidth = aPosSize.GetWidth();
+ pState->mnMaximizedHeight = aPosSize.GetHeight();
+ pState->mnMask |= FRAMESTATE_MASK_MAXIMIZED_GEOMETRY;
+ }
+
+ return true;
+}
+
+// native menu implementation - currently empty
+void X11SalFrame::DrawMenuBar()
+{
+}
+
+void X11SalFrame::SetMenu( SalMenu* )
+{
+}
+
+void X11SalFrame::GetPosSize( tools::Rectangle &rPosSize )
+{
+ if( maGeometry.nWidth < 1 || maGeometry.nHeight < 1 )
+ {
+ const Size& aScreenSize = pDisplay_->getDataForScreen( m_nXScreen ).m_aSize;
+ tools::Long w = aScreenSize.Width() - maGeometry.nLeftDecoration - maGeometry.nRightDecoration;
+ tools::Long h = aScreenSize.Height() - maGeometry.nTopDecoration - maGeometry.nBottomDecoration;
+
+ rPosSize = tools::Rectangle( Point( maGeometry.nX, maGeometry.nY ), Size( w, h ) );
+ }
+ else
+ rPosSize = tools::Rectangle( Point( maGeometry.nX, maGeometry.nY ),
+ Size( maGeometry.nWidth, maGeometry.nHeight ) );
+}
+
+void X11SalFrame::SetSize( const Size &rSize )
+{
+ if( rSize.IsEmpty() )
+ return;
+
+ if( ! ( nStyle_ & SalFrameStyleFlags::SIZEABLE )
+ && ! IsChildWindow()
+ && ( nStyle_ & (SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::OWNERDRAWDECORATION) ) != SalFrameStyleFlags::FLOAT )
+ {
+ XSizeHints* pHints = XAllocSizeHints();
+ tools::Long nSupplied = 0;
+ XGetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints,
+ &nSupplied
+ );
+ pHints->min_width = rSize.Width();
+ pHints->min_height = rSize.Height();
+ pHints->max_width = rSize.Width();
+ pHints->max_height = rSize.Height();
+ pHints->flags |= PMinSize | PMaxSize;
+ XSetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints );
+ XFree( pHints );
+ }
+ XResizeWindow( GetXDisplay(), IsSysChildWindow() ? GetWindow() : GetShellWindow(), rSize.Width(), rSize.Height() );
+ if( GetWindow() != GetShellWindow() )
+ {
+ if( nStyle_ & SalFrameStyleFlags::PLUG )
+ XMoveResizeWindow( GetXDisplay(), GetWindow(), 0, 0, rSize.Width(), rSize.Height() );
+ else
+ XResizeWindow( GetXDisplay(), GetWindow(), rSize.Width(), rSize.Height() );
+ }
+
+ maGeometry.nWidth = rSize.Width();
+ maGeometry.nHeight = rSize.Height();
+
+ // allow the external status window to reposition
+ if (mbInputFocus && mpInputContext != nullptr)
+ mpInputContext->SetICFocus ( this );
+}
+
+void X11SalFrame::SetPosSize( const tools::Rectangle &rPosSize )
+{
+ XWindowChanges values;
+ values.x = rPosSize.Left();
+ values.y = rPosSize.Top();
+ values.width = rPosSize.GetWidth();
+ values.height = rPosSize.GetHeight();
+
+ if( !values.width || !values.height )
+ return;
+
+ if( mpParent && ! IsSysChildWindow() )
+ {
+ if( AllSettings::GetLayoutRTL() )
+ values.x = mpParent->maGeometry.nWidth-values.width-1-values.x;
+
+ ::Window aChild;
+ // coordinates are relative to parent, so translate to root coordinates
+ XTranslateCoordinates( GetDisplay()->GetDisplay(),
+ mpParent->GetWindow(),
+ GetDisplay()->GetRootWindow( m_nXScreen ),
+ values.x, values.y,
+ &values.x, &values.y,
+ & aChild );
+ }
+
+ bool bMoved = false;
+ bool bSized = false;
+ if( values.x != maGeometry.nX || values.y != maGeometry.nY )
+ bMoved = true;
+ if( values.width != static_cast<int>(maGeometry.nWidth) || values.height != static_cast<int>(maGeometry.nHeight) )
+ bSized = true;
+
+ // do not set WMNormalHints for...
+ if(
+ // child windows
+ ! IsChildWindow()
+ // popups (menu, help window, etc.)
+ && (nStyle_ & (SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::OWNERDRAWDECORATION) ) != SalFrameStyleFlags::FLOAT
+ // shown, sizeable windows
+ && ( nShowState_ == X11ShowState::Unknown ||
+ nShowState_ == X11ShowState::Hidden ||
+ ! ( nStyle_ & SalFrameStyleFlags::SIZEABLE )
+ )
+ )
+ {
+ XSizeHints* pHints = XAllocSizeHints();
+ tools::Long nSupplied = 0;
+ XGetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints,
+ &nSupplied
+ );
+ if( ! ( nStyle_ & SalFrameStyleFlags::SIZEABLE ) )
+ {
+ pHints->min_width = rPosSize.GetWidth();
+ pHints->min_height = rPosSize.GetHeight();
+ pHints->max_width = rPosSize.GetWidth();
+ pHints->max_height = rPosSize.GetHeight();
+ pHints->flags |= PMinSize | PMaxSize;
+ }
+ if( nShowState_ == X11ShowState::Unknown || nShowState_ == X11ShowState::Hidden )
+ {
+ pHints->flags |= PPosition | PWinGravity;
+ pHints->x = values.x;
+ pHints->y = values.y;
+ pHints->win_gravity = pDisplay_->getWMAdaptor()->getPositionWinGravity();
+ }
+ if( mbFullScreen )
+ {
+ pHints->max_width = 10000;
+ pHints->max_height = 10000;
+ pHints->flags |= PMaxSize;
+ }
+ XSetWMNormalHints( GetXDisplay(),
+ GetShellWindow(),
+ pHints );
+ XFree( pHints );
+ }
+
+ XMoveResizeWindow( GetXDisplay(), IsSysChildWindow() ? GetWindow() : GetShellWindow(), values.x, values.y, values.width, values.height );
+ if( GetShellWindow() != GetWindow() )
+ {
+ if( nStyle_ & SalFrameStyleFlags::PLUG )
+ XMoveResizeWindow( GetXDisplay(), GetWindow(), 0, 0, values.width, values.height );
+ else
+ XMoveResizeWindow( GetXDisplay(), GetWindow(), values.x, values.y, values.width, values.height );
+ }
+
+ maGeometry.nX = values.x;
+ maGeometry.nY = values.y;
+ maGeometry.nWidth = values.width;
+ maGeometry.nHeight = values.height;
+ if( IsSysChildWindow() && mpParent )
+ {
+ // translate back to root coordinates
+ maGeometry.nX += mpParent->maGeometry.nX;
+ maGeometry.nY += mpParent->maGeometry.nY;
+ }
+
+ updateScreenNumber();
+ if( bSized && ! bMoved )
+ CallCallback( SalEvent::Resize, nullptr );
+ else if( bMoved && ! bSized )
+ CallCallback( SalEvent::Move, nullptr );
+ else
+ CallCallback( SalEvent::MoveResize, nullptr );
+
+ // allow the external status window to reposition
+ if (mbInputFocus && mpInputContext != nullptr)
+ mpInputContext->SetICFocus ( this );
+}
+
+void X11SalFrame::Minimize()
+{
+ if( IsSysChildWindow() )
+ return;
+
+ if( X11ShowState::Unknown == nShowState_ || X11ShowState::Hidden == nShowState_ )
+ {
+ SAL_WARN( "vcl", "X11SalFrame::Minimize on withdrawn window" );
+ return;
+ }
+
+ if( XIconifyWindow( GetXDisplay(),
+ GetShellWindow(),
+ pDisplay_->GetDefaultXScreen().getXScreen() ) )
+ nShowState_ = X11ShowState::Minimized;
+}
+
+void X11SalFrame::Maximize()
+{
+ if( IsSysChildWindow() )
+ return;
+
+ if( X11ShowState::Minimized == nShowState_ )
+ {
+ GetDisplay()->getWMAdaptor()->frameIsMapping( this );
+ XMapWindow( GetXDisplay(), GetShellWindow() );
+ nShowState_ = X11ShowState::Normal;
+ }
+
+ pDisplay_->getWMAdaptor()->maximizeFrame( this );
+}
+
+void X11SalFrame::Restore()
+{
+ if( IsSysChildWindow() )
+ return;
+
+ if( X11ShowState::Unknown == nShowState_ || X11ShowState::Hidden == nShowState_ )
+ {
+ SAL_INFO( "vcl", "X11SalFrame::Restore on withdrawn window" );
+ return;
+ }
+
+ if( X11ShowState::Minimized == nShowState_ )
+ {
+ GetDisplay()->getWMAdaptor()->frameIsMapping( this );
+ XMapWindow( GetXDisplay(), GetShellWindow() );
+ nShowState_ = X11ShowState::Normal;
+ }
+
+ pDisplay_->getWMAdaptor()->maximizeFrame( this, false, false );
+}
+
+void X11SalFrame::SetScreenNumber( unsigned int nNewScreen )
+{
+ if( nNewScreen == maGeometry.nDisplayScreenNumber )
+ return;
+
+ if( GetDisplay()->IsXinerama() && GetDisplay()->GetXineramaScreens().size() > 1 )
+ {
+ if( nNewScreen >= GetDisplay()->GetXineramaScreens().size() )
+ return;
+
+ tools::Rectangle aOldScreenRect( GetDisplay()->GetXineramaScreens()[maGeometry.nDisplayScreenNumber] );
+ tools::Rectangle aNewScreenRect( GetDisplay()->GetXineramaScreens()[nNewScreen] );
+ bool bVisible = bMapped_;
+ if( bVisible )
+ Show( false );
+ maGeometry.nX = aNewScreenRect.Left() + (maGeometry.nX - aOldScreenRect.Left());
+ maGeometry.nY = aNewScreenRect.Top() + (maGeometry.nY - aOldScreenRect.Top());
+ createNewWindow( None, m_nXScreen );
+ if( bVisible )
+ Show( true );
+ maGeometry.nDisplayScreenNumber = nNewScreen;
+ }
+ else if( nNewScreen < GetDisplay()->GetXScreenCount() )
+ {
+ bool bVisible = bMapped_;
+ if( bVisible )
+ Show( false );
+ createNewWindow( None, SalX11Screen( nNewScreen ) );
+ if( bVisible )
+ Show( true );
+ maGeometry.nDisplayScreenNumber = nNewScreen;
+ }
+}
+
+void X11SalFrame::SetApplicationID( const OUString &rWMClass )
+{
+ if( rWMClass != m_sWMClass && ! IsChildWindow() )
+ {
+ m_sWMClass = rWMClass;
+ updateWMClass();
+ for (auto const& child : maChildren)
+ child->SetApplicationID(rWMClass);
+ }
+}
+
+void X11SalFrame::updateWMClass()
+{
+ XClassHint* pClass = XAllocClassHint();
+ OString aResName = SalGenericSystem::getFrameResName();
+ pClass->res_name = const_cast<char*>(aResName.getStr());
+
+ OString aResClass = OUStringToOString(m_sWMClass, RTL_TEXTENCODING_ASCII_US);
+ const char *pResClass = !aResClass.isEmpty() ? aResClass.getStr() :
+ SalGenericSystem::getFrameClassName();
+
+ pClass->res_class = const_cast<char*>(pResClass);
+ XSetClassHint( GetXDisplay(), GetShellWindow(), pClass );
+ XFree( pClass );
+}
+
+void X11SalFrame::ShowFullScreen( bool bFullScreen, sal_Int32 nScreen )
+{
+ if( GetDisplay()->IsXinerama() && GetDisplay()->GetXineramaScreens().size() > 1 )
+ {
+ if( mbFullScreen == bFullScreen )
+ return;
+ if( bFullScreen )
+ {
+ maRestorePosSize = tools::Rectangle( Point( maGeometry.nX, maGeometry.nY ),
+ Size( maGeometry.nWidth, maGeometry.nHeight ) );
+ tools::Rectangle aRect;
+ if( nScreen < 0 || o3tl::make_unsigned(nScreen) >= GetDisplay()->GetXineramaScreens().size() )
+ aRect = tools::Rectangle( Point(0,0), GetDisplay()->GetScreenSize( m_nXScreen ) );
+ else
+ aRect = GetDisplay()->GetXineramaScreens()[nScreen];
+ nStyle_ |= SalFrameStyleFlags::PARTIAL_FULLSCREEN;
+ bool bVisible = bMapped_;
+ if( bVisible )
+ Show( false );
+ maGeometry.nX = aRect.Left();
+ maGeometry.nY = aRect.Top();
+ maGeometry.nWidth = aRect.GetWidth();
+ maGeometry.nHeight = aRect.GetHeight();
+ mbMaximizedHorz = mbMaximizedVert = false;
+ mbFullScreen = true;
+ createNewWindow( None, m_nXScreen );
+ if( GetDisplay()->getWMAdaptor()->isLegacyPartialFullscreen() )
+ GetDisplay()->getWMAdaptor()->enableAlwaysOnTop( this, true );
+ else
+ GetDisplay()->getWMAdaptor()->showFullScreen( this, true );
+ if( bVisible )
+ Show(true);
+
+ }
+ else
+ {
+ mbFullScreen = false;
+ nStyle_ &= ~SalFrameStyleFlags::PARTIAL_FULLSCREEN;
+ bool bVisible = bMapped_;
+ tools::Rectangle aRect = maRestorePosSize;
+ maRestorePosSize = tools::Rectangle();
+ if( bVisible )
+ Show( false );
+ createNewWindow( None, m_nXScreen );
+ if( !aRect.IsEmpty() )
+ SetPosSize( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(),
+ SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y |
+ SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
+ if( bVisible )
+ Show( true );
+ }
+ }
+ else
+ {
+ if( nScreen < 0 || o3tl::make_unsigned(nScreen) >= GetDisplay()->GetXScreenCount() )
+ nScreen = m_nXScreen.getXScreen();
+ if( nScreen != static_cast<int>(m_nXScreen.getXScreen()) )
+ {
+ bool bVisible = bMapped_;
+ if( mbFullScreen )
+ pDisplay_->getWMAdaptor()->showFullScreen( this, false );
+ if( bVisible )
+ Show( false );
+ createNewWindow( None, SalX11Screen( nScreen ) );
+ if( mbFullScreen )
+ pDisplay_->getWMAdaptor()->showFullScreen( this, true );
+ if( bVisible )
+ Show( true );
+ }
+ if( mbFullScreen == bFullScreen )
+ return;
+
+ pDisplay_->getWMAdaptor()->showFullScreen( this, bFullScreen );
+ }
+}
+
+void X11SalFrame::StartPresentation( bool bStart )
+{
+ maScreenSaverInhibitor.inhibit( bStart,
+ u"presentation",
+ true, // isX11
+ mhWindow,
+ GetXDisplay() );
+
+ if( ! bStart && hPresentationWindow != None )
+ doReparentPresentationDialogues( GetDisplay() );
+ hPresentationWindow = (bStart && IsOverrideRedirect() ) ? GetWindow() : None;
+
+ if( bStart && hPresentationWindow )
+ {
+ /* #i10559# workaround for WindowMaker: try to restore
+ * current focus after presentation window is gone
+ */
+ int revert_to = 0;
+ XGetInputFocus( GetXDisplay(), &hPresFocusWindow, &revert_to );
+ }
+}
+
+// Pointer
+
+void X11SalFrame::SetPointer( PointerStyle ePointerStyle )
+{
+ hCursor_ = pDisplay_->GetPointer( ePointerStyle );
+ XDefineCursor( GetXDisplay(), GetWindow(), hCursor_ );
+
+ if( IsCaptured() || nVisibleFloats > 0 )
+ XChangeActivePointerGrab( GetXDisplay(),
+ PointerMotionMask|ButtonPressMask|ButtonReleaseMask,
+ hCursor_,
+ CurrentTime );
+}
+
+void X11SalFrame::SetPointerPos(tools::Long nX, tools::Long nY)
+{
+ /* when the application tries to center the mouse in the dialog the
+ * window isn't mapped already. So use coordinates relative to the root window.
+ */
+ unsigned int nWindowLeft = maGeometry.nX + nX;
+ unsigned int nWindowTop = maGeometry.nY + nY;
+
+ XWarpPointer( GetXDisplay(), None, pDisplay_->GetRootWindow( pDisplay_->GetDefaultXScreen() ),
+ 0, 0, 0, 0, nWindowLeft, nWindowTop);
+}
+
+// delay handling of extended text input
+#if !defined(__synchronous_extinput__)
+void
+X11SalFrame::HandleExtTextEvent (XClientMessageEvent const *pEvent)
+{
+ #if SAL_TYPES_SIZEOFLONG > 4
+ void* pExtTextEvent = reinterpret_cast<void*>( (pEvent->data.l[0] & 0xffffffff)
+ | (pEvent->data.l[1] << 32) );
+ #else
+ void* pExtTextEvent = reinterpret_cast<void*>(pEvent->data.l[0]);
+ #endif
+ SalEvent nExtTextEventType = SalEvent(pEvent->data.l[2]);
+
+ CallCallback(nExtTextEventType, pExtTextEvent);
+
+ switch (nExtTextEventType)
+ {
+ case SalEvent::EndExtTextInput:
+ break;
+
+ case SalEvent::ExtTextInput:
+ break;
+
+ default:
+ SAL_WARN("vcl.window",
+ "X11SalFrame::HandleExtTextEvent: invalid extended input.");
+ }
+}
+#endif /* defined(__synchronous_extinput__) */
+
+// PostEvent
+
+bool X11SalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
+{
+ GetDisplay()->SendInternalEvent( this, pData.release() );
+ return true;
+}
+
+// Title
+
+void X11SalFrame::SetTitle( const OUString& rTitle )
+{
+ if( ! ( IsChildWindow() || (nStyle_ & SalFrameStyleFlags::FLOAT ) ) )
+ {
+ m_aTitle = rTitle;
+ GetDisplay()->getWMAdaptor()->setWMName( this, rTitle );
+ }
+}
+
+void X11SalFrame::Flush()
+{
+ if( pGraphics_ )
+ pGraphics_->Flush();
+ XFlush( GetDisplay()->GetDisplay() );
+}
+
+// Keyboard
+
+void X11SalFrame::SetInputContext( SalInputContext* pContext )
+{
+ if (pContext == nullptr)
+ return;
+
+ // 1. We should create an input context for this frame
+ // only when InputContextFlags::Text is set.
+
+ if (!(pContext->mnOptions & InputContextFlags::Text))
+ {
+ if( mpInputContext )
+ mpInputContext->Unmap();
+ return;
+ }
+
+ // 2. We should use on-the-spot inputstyle
+ // only when InputContextFlags::ExtTExt is set.
+
+ if (mpInputContext == nullptr)
+ {
+ mpInputContext.reset( new SalI18N_InputContext( this ) );
+ if (mpInputContext->UseContext())
+ {
+ mpInputContext->ExtendEventMask( GetShellWindow() );
+ if (mbInputFocus)
+ mpInputContext->SetICFocus( this );
+ }
+ }
+ else
+ mpInputContext->Map( this );
+}
+
+void X11SalFrame::EndExtTextInput( EndExtTextInputFlags )
+{
+ if (mpInputContext != nullptr)
+ mpInputContext->EndExtTextInput();
+}
+
+OUString X11SalFrame::GetKeyName( sal_uInt16 nKeyCode )
+{
+ return GetDisplay()->GetKeyName( nKeyCode );
+}
+
+bool X11SalFrame::MapUnicodeToKeyCode( sal_Unicode , LanguageType , vcl::KeyCode& )
+{
+ // not supported yet
+ return false;
+}
+
+LanguageType X11SalFrame::GetInputLanguage()
+{
+ // could be improved by checking unicode ranges of the last input
+ return LANGUAGE_DONTKNOW;
+}
+
+// Settings
+
+void X11SalFrame::UpdateSettings( AllSettings& rSettings )
+{
+ StyleSettings aStyleSettings = rSettings.GetStyleSettings();
+ aStyleSettings.SetCursorBlinkTime( 500 );
+ aStyleSettings.SetMenuBarTextColor( aStyleSettings.GetPersonaMenuBarTextColor().value_or( COL_BLACK ) );
+ rSettings.SetStyleSettings( aStyleSettings );
+}
+
+void X11SalFrame::CaptureMouse( bool bCapture )
+{
+ nCaptured_ = pDisplay_->CaptureMouse( bCapture ? this : nullptr );
+}
+
+void X11SalFrame::SetParent( SalFrame* pNewParent )
+{
+ if( mpParent != pNewParent )
+ {
+ if( mpParent )
+ mpParent->maChildren.remove( this );
+
+ mpParent = static_cast<X11SalFrame*>(pNewParent);
+ mpParent->maChildren.push_back( this );
+ if( mpParent->m_nXScreen != m_nXScreen )
+ createNewWindow( None, mpParent->m_nXScreen );
+ GetDisplay()->getWMAdaptor()->changeReferenceFrame( this, mpParent );
+ }
+}
+
+SalFrame* X11SalFrame::GetParent() const
+{
+ return mpParent;
+}
+
+void X11SalFrame::createNewWindow( ::Window aNewParent, SalX11Screen nXScreen )
+{
+ bool bWasVisible = bMapped_;
+ if( bWasVisible )
+ Show( false );
+
+ if( nXScreen.getXScreen() >= GetDisplay()->GetXScreenCount() )
+ nXScreen = m_nXScreen;
+
+ SystemParentData aParentData;
+ aParentData.nSize = sizeof(SystemParentData);
+ aParentData.aWindow = aNewParent;
+ aParentData.bXEmbedSupport = (aNewParent != None && m_bXEmbed); // caution: this is guesswork
+ if( aNewParent == None )
+ {
+ aParentData.aWindow = None;
+ m_bXEmbed = false;
+ }
+ else
+ {
+ // is new parent a root window ?
+ Display* pDisp = GetDisplay()->GetDisplay();
+ int nScreens = GetDisplay()->GetXScreenCount();
+ for( int i = 0; i < nScreens; i++ )
+ {
+ if( aNewParent == RootWindow( pDisp, i ) )
+ {
+ nXScreen = SalX11Screen( i );
+ aParentData.aWindow = None;
+ m_bXEmbed = false;
+ break;
+ }
+ }
+ }
+
+ // first deinit frame
+ updateGraphics(true);
+ if( mpInputContext )
+ {
+ mpInputContext->UnsetICFocus();
+ mpInputContext->Unmap();
+ }
+ if( GetWindow() == hPresentationWindow )
+ {
+ hPresentationWindow = None;
+ doReparentPresentationDialogues( GetDisplay() );
+ }
+ XDestroyWindow( GetXDisplay(), mhWindow );
+ mhWindow = None;
+
+ // now init with new parent again
+ if ( aParentData.aWindow != None )
+ Init( nStyle_ | SalFrameStyleFlags::PLUG, nXScreen, &aParentData );
+ else
+ Init( nStyle_ & ~SalFrameStyleFlags::PLUG, nXScreen, nullptr, true );
+
+ // update graphics if necessary
+ updateGraphics(false);
+
+ if( ! m_aTitle.isEmpty() )
+ SetTitle( m_aTitle );
+
+ if( mpParent )
+ {
+ if( mpParent->m_nXScreen != m_nXScreen )
+ SetParent( nullptr );
+ else
+ pDisplay_->getWMAdaptor()->changeReferenceFrame( this, mpParent );
+ }
+
+ if( bWasVisible )
+ Show( true );
+
+ std::list< X11SalFrame* > aChildren = maChildren;
+ for (auto const& child : aChildren)
+ child->createNewWindow( None, m_nXScreen );
+
+ // FIXME: SalObjects
+}
+
+void X11SalFrame::SetPluginParent( SystemParentData* pNewParent )
+{
+ if( pNewParent->nSize >= sizeof(SystemParentData) )
+ m_bXEmbed = pNewParent->aWindow != None && pNewParent->bXEmbedSupport;
+
+ createNewWindow(pNewParent->aWindow);
+}
+
+// Sound
+void X11SalFrame::Beep()
+{
+ GetDisplay()->Beep();
+}
+
+// Event Handling
+
+static sal_uInt16 sal_GetCode( int state )
+{
+ sal_uInt16 nCode = 0;
+
+ if( state & Button1Mask )
+ nCode |= MOUSE_LEFT;
+ if( state & Button2Mask )
+ nCode |= MOUSE_MIDDLE;
+ if( state & Button3Mask )
+ nCode |= MOUSE_RIGHT;
+
+ if( state & ShiftMask )
+ nCode |= KEY_SHIFT;
+ if( state & ControlMask )
+ nCode |= KEY_MOD1;
+ if( state & Mod1Mask )
+ nCode |= KEY_MOD2;
+
+ // Map Meta/Super modifier to MOD3 on all Unix systems
+ // except macOS
+ if( state & Mod3Mask )
+ nCode |= KEY_MOD3;
+
+ return nCode;
+}
+
+SalFrame::SalPointerState X11SalFrame::GetPointerState()
+{
+ SalPointerState aState;
+ ::Window aRoot, aChild;
+ int rx, ry, wx, wy;
+ unsigned int nMask = 0;
+ XQueryPointer( GetXDisplay(),
+ GetShellWindow(),
+ &aRoot,
+ &aChild,
+ &rx, &ry,
+ &wx, &wy,
+ &nMask
+ );
+
+ aState.maPos = Point(wx, wy);
+ aState.mnState = sal_GetCode( nMask );
+ return aState;
+}
+
+KeyIndicatorState X11SalFrame::GetIndicatorState()
+{
+ return vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetIndicatorState();
+}
+
+void X11SalFrame::SimulateKeyPress( sal_uInt16 nKeyCode )
+{
+ vcl_sal::getSalDisplay(GetGenericUnixSalData())->SimulateKeyPress(nKeyCode);
+}
+
+namespace
+{
+struct CompressWheelEventsData
+{
+ XEvent* firstEvent;
+ bool ignore;
+ int count; // number of compressed events
+};
+
+Bool compressWheelEvents( Display*, XEvent* event, XPointer p )
+{
+ CompressWheelEventsData* data = reinterpret_cast< CompressWheelEventsData* >( p );
+ if( data->ignore )
+ return False; // we're already after the events to compress
+ if( event->type == ButtonPress || event->type == ButtonRelease )
+ {
+ const unsigned int mask = Button1Mask << ( event->xbutton.button - Button1 );
+ if( event->xbutton.button == data->firstEvent->xbutton.button
+ && event->xbutton.window == data->firstEvent->xbutton.window
+ && event->xbutton.x == data->firstEvent->xbutton.x
+ && event->xbutton.y == data->firstEvent->xbutton.y
+ && ( event->xbutton.state | mask ) == ( data->firstEvent->xbutton.state | mask ))
+ {
+ // Count if it's another press (i.e. wheel start event).
+ if( event->type == ButtonPress )
+ ++data->count;
+ return True; // And remove the event from the queue.
+ }
+ }
+ // Non-matching event, skip certain events that cannot possibly affect input processing,
+ // but otherwise ignore all further events.
+ switch( event->type )
+ {
+ case Expose:
+ case NoExpose:
+ break;
+ default:
+ data->ignore = true;
+ break;
+ }
+ return False;
+}
+
+} // namespace
+
+bool X11SalFrame::HandleMouseEvent( XEvent *pEvent )
+{
+ SalMouseEvent aMouseEvt;
+ SalEvent nEvent = SalEvent::NONE;
+ bool bClosePopups = false;
+
+ if( nVisibleFloats && pEvent->type == EnterNotify )
+ return false;
+
+ if( LeaveNotify == pEvent->type || EnterNotify == pEvent->type )
+ {
+ /*
+ * some WMs (and/or) applications have a passive grab on
+ * mouse buttons (XGrabButton). This leads to enter/leave notifies
+ * with mouse buttons pressed in the state mask before the actual
+ * ButtonPress event gets dispatched. But EnterNotify
+ * is reported in vcl as MouseMove event. Some office code
+ * decides that a pressed button in a MouseMove belongs to
+ * a drag operation which leads to doing things differently.
+ *
+ * ignore Enter/LeaveNotify resulting from grabs so that
+ * help windows do not disappear just after appearing
+ *
+ * hopefully this workaround will not break anything.
+ */
+ if( pEvent->xcrossing.mode == NotifyGrab || pEvent->xcrossing.mode == NotifyUngrab )
+ return false;
+
+ aMouseEvt.mnX = pEvent->xcrossing.x;
+ aMouseEvt.mnY = pEvent->xcrossing.y;
+ aMouseEvt.mnTime = pEvent->xcrossing.time;
+ aMouseEvt.mnCode = sal_GetCode( pEvent->xcrossing.state );
+ aMouseEvt.mnButton = 0;
+
+ nEvent = LeaveNotify == pEvent->type
+ ? SalEvent::MouseLeave
+ : SalEvent::MouseMove;
+ }
+ else if( pEvent->type == MotionNotify )
+ {
+ aMouseEvt.mnX = pEvent->xmotion.x;
+ aMouseEvt.mnY = pEvent->xmotion.y;
+ aMouseEvt.mnTime = pEvent->xmotion.time;
+ aMouseEvt.mnCode = sal_GetCode( pEvent->xmotion.state );
+
+ aMouseEvt.mnButton = 0;
+
+ nEvent = SalEvent::MouseMove;
+ if( nVisibleFloats > 0 && mpParent )
+ {
+ Cursor aCursor = mpParent->GetCursor();
+ if( pEvent->xmotion.x >= 0 && pEvent->xmotion.x < static_cast<int>(maGeometry.nWidth) &&
+ pEvent->xmotion.y >= 0 && pEvent->xmotion.y < static_cast<int>(maGeometry.nHeight) )
+ aCursor = None;
+
+ XChangeActivePointerGrab( GetXDisplay(),
+ PointerMotionMask|ButtonPressMask|ButtonReleaseMask,
+ aCursor,
+ CurrentTime );
+ }
+ }
+ else
+ {
+ // let mouse events reach the correct window
+ if( nVisibleFloats < 1 )
+ {
+ if( ! (nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION) )
+ XUngrabPointer( GetXDisplay(), CurrentTime );
+ }
+ else if( pEvent->type == ButtonPress )
+ {
+ // see if the user clicks outside all of the floats
+ // if yes release the grab
+ bool bInside = false;
+ for (auto pSalFrame : GetDisplay()->getFrames() )
+ {
+ const X11SalFrame* pFrame = static_cast< const X11SalFrame* >( pSalFrame );
+ if( pFrame->IsFloatGrabWindow() &&
+ pFrame->bMapped_ &&
+ pEvent->xbutton.x_root >= pFrame->maGeometry.nX &&
+ pEvent->xbutton.x_root < pFrame->maGeometry.nX + static_cast<int>(pFrame->maGeometry.nWidth) &&
+ pEvent->xbutton.y_root >= pFrame->maGeometry.nY &&
+ pEvent->xbutton.y_root < pFrame->maGeometry.nY + static_cast<int>(pFrame->maGeometry.nHeight) )
+ {
+ bInside = true;
+ break;
+ }
+ }
+ if( ! bInside )
+ {
+ // need not take care of the XUngrabPointer in Show( false )
+ // because XUngrabPointer does not produce errors if pointer
+ // is not grabbed
+ XUngrabPointer( GetXDisplay(), CurrentTime );
+ bClosePopups = true;
+
+ /* #i15246# only close popups if pointer is outside all our frames
+ * cannot use our own geometry data here because stacking
+ * is unknown (the above case implicitly assumes
+ * that floats are on top which should be true)
+ */
+ ::Window aRoot, aChild;
+ int root_x, root_y, win_x, win_y;
+ unsigned int mask_return;
+ if( XQueryPointer( GetXDisplay(),
+ GetDisplay()->GetRootWindow( m_nXScreen ),
+ &aRoot, &aChild,
+ &root_x, &root_y,
+ &win_x, &win_y,
+ &mask_return )
+ && aChild // pointer may not be in any child
+ )
+ {
+ for (auto pSalFrame : GetDisplay()->getFrames() )
+ {
+ const X11SalFrame* pFrame = static_cast< const X11SalFrame* >( pSalFrame );
+ if( ! pFrame->IsFloatGrabWindow()
+ && ( pFrame->GetWindow() == aChild ||
+ pFrame->GetShellWindow() == aChild ||
+ pFrame->GetStackingWindow() == aChild )
+ )
+ {
+ // #i63638# check that pointer is inside window, not
+ // only inside stacking window
+ if( root_x >= pFrame->maGeometry.nX && root_x < sal::static_int_cast< int >(pFrame->maGeometry.nX+pFrame->maGeometry.nWidth) &&
+ root_y >= pFrame->maGeometry.nY && root_y < sal::static_int_cast< int >(pFrame->maGeometry.nX+pFrame->maGeometry.nHeight) )
+ {
+ bClosePopups = false;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if( m_bXEmbed && pEvent->xbutton.button == Button1 )
+ askForXEmbedFocus( pEvent->xbutton.time );
+
+ if( pEvent->xbutton.button == Button1 ||
+ pEvent->xbutton.button == Button2 ||
+ pEvent->xbutton.button == Button3 )
+ {
+ aMouseEvt.mnX = pEvent->xbutton.x;
+ aMouseEvt.mnY = pEvent->xbutton.y;
+ aMouseEvt.mnTime = pEvent->xbutton.time;
+ aMouseEvt.mnCode = sal_GetCode( pEvent->xbutton.state );
+
+ if( Button1 == pEvent->xbutton.button )
+ aMouseEvt.mnButton = MOUSE_LEFT;
+ else if( Button2 == pEvent->xbutton.button )
+ aMouseEvt.mnButton = MOUSE_MIDDLE;
+ else if( Button3 == pEvent->xbutton.button )
+ aMouseEvt.mnButton = MOUSE_RIGHT;
+
+ nEvent = ButtonPress == pEvent->type
+ ? SalEvent::MouseButtonDown
+ : SalEvent::MouseButtonUp;
+ }
+ else if( pEvent->xbutton.button == Button4 ||
+ pEvent->xbutton.button == Button5 ||
+ pEvent->xbutton.button == Button6 ||
+ pEvent->xbutton.button == Button7 )
+ {
+ const bool bIncrement(
+ pEvent->xbutton.button == Button4 ||
+ pEvent->xbutton.button == Button6 );
+ const bool bHoriz(
+ pEvent->xbutton.button == Button6 ||
+ pEvent->xbutton.button == Button7 );
+
+ if( pEvent->type == ButtonRelease )
+ return false;
+
+ static sal_uLong nLines = 0;
+ if( ! nLines )
+ {
+ char* pEnv = getenv( "SAL_WHEELLINES" );
+ nLines = pEnv ? atoi( pEnv ) : 3;
+ if( nLines > 10 )
+ nLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL;
+ }
+
+ // Compress consecutive wheel events (way too fine scrolling may cause lags if one scrolling steps takes long).
+ CompressWheelEventsData data;
+ data.firstEvent = pEvent;
+ data.count = 1;
+ XEvent dummy;
+ do
+ {
+ data.ignore = false;
+ } while( XCheckIfEvent( pEvent->xany.display, &dummy, compressWheelEvents, reinterpret_cast< XPointer >( &data )));
+
+ SalWheelMouseEvent aWheelEvt;
+ aWheelEvt.mnTime = pEvent->xbutton.time;
+ aWheelEvt.mnX = pEvent->xbutton.x;
+ aWheelEvt.mnY = pEvent->xbutton.y;
+ aWheelEvt.mnDelta = ( bIncrement ? 120 : -120 ) * data.count;
+ aWheelEvt.mnNotchDelta = bIncrement ? 1 : -1;
+ aWheelEvt.mnScrollLines = nLines * data.count;
+ aWheelEvt.mnCode = sal_GetCode( pEvent->xbutton.state );
+ aWheelEvt.mbHorz = bHoriz;
+
+ nEvent = SalEvent::WheelMouse;
+
+ if( AllSettings::GetLayoutRTL() )
+ aWheelEvt.mnX = nWidth_-1-aWheelEvt.mnX;
+ return CallCallback( nEvent, &aWheelEvt );
+ }
+ }
+
+ bool nRet = false;
+ if( nEvent == SalEvent::MouseLeave
+ || ( aMouseEvt.mnX < nWidth_ && aMouseEvt.mnX > -1 &&
+ aMouseEvt.mnY < nHeight_ && aMouseEvt.mnY > -1 )
+ || pDisplay_->MouseCaptured( this )
+ )
+ {
+ if( AllSettings::GetLayoutRTL() )
+ aMouseEvt.mnX = nWidth_-1-aMouseEvt.mnX;
+ nRet = CallCallback( nEvent, &aMouseEvt );
+ }
+
+ if( bClosePopups )
+ {
+ /* #108213# close popups after dispatching the event outside the popup;
+ * applications do weird things.
+ */
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->mpWinData->mpFirstFloat)
+ {
+ if (!(pSVData->mpWinData->mpFirstFloat->GetPopupModeFlags()
+ & FloatWinPopupFlags::NoAppFocusClose))
+ pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel
+ | FloatWinPopupEndFlags::CloseAll);
+ }
+ }
+
+ return nRet;
+}
+
+namespace {
+
+// F10 means either KEY_F10 or KEY_MENU, which has to be decided
+// in the independent part.
+struct KeyAlternate
+{
+ sal_uInt16 nKeyCode;
+ sal_Unicode nCharCode;
+ KeyAlternate() : nKeyCode( 0 ), nCharCode( 0 ) {}
+ KeyAlternate( sal_uInt16 nKey, sal_Unicode nChar = 0 ) : nKeyCode( nKey ), nCharCode( nChar ) {}
+};
+
+}
+
+static KeyAlternate
+GetAlternateKeyCode( const sal_uInt16 nKeyCode )
+{
+ KeyAlternate aAlternate;
+
+ switch( nKeyCode )
+ {
+ case KEY_F10: aAlternate = KeyAlternate( KEY_MENU );break;
+ case KEY_F24: aAlternate = KeyAlternate( KEY_SUBTRACT, '-' );break;
+ }
+
+ return aAlternate;
+}
+
+void X11SalFrame::beginUnicodeSequence()
+{
+ OUString& rSeq( GetGenericUnixSalData()->GetUnicodeCommand() );
+ vcl::DeletionListener aDeleteWatch( this );
+
+ if( !rSeq.isEmpty() )
+ endUnicodeSequence();
+
+ rSeq = "u";
+
+ if( ! aDeleteWatch.isDeleted() )
+ {
+ ExtTextInputAttr nTextAttr = ExtTextInputAttr::Underline;
+ SalExtTextInputEvent aEv;
+ aEv.maText = rSeq;
+ aEv.mpTextAttr = &nTextAttr;
+ aEv.mnCursorPos = 0;
+ aEv.mnCursorFlags = 0;
+
+ CallCallback(SalEvent::ExtTextInput, static_cast<void*>(&aEv));
+ }
+}
+
+bool X11SalFrame::appendUnicodeSequence( sal_Unicode c )
+{
+ bool bRet = false;
+ OUString& rSeq( GetGenericUnixSalData()->GetUnicodeCommand() );
+ if( !rSeq.isEmpty() )
+ {
+ // range check
+ if( (c >= '0' && c <= '9') ||
+ (c >= 'a' && c <= 'f') ||
+ (c >= 'A' && c <= 'F') )
+ {
+ rSeq += OUStringChar(c);
+ std::vector<ExtTextInputAttr> attribs( rSeq.getLength(), ExtTextInputAttr::Underline );
+
+ SalExtTextInputEvent aEv;
+ aEv.maText = rSeq;
+ aEv.mpTextAttr = attribs.data();
+ aEv.mnCursorPos = 0;
+ aEv.mnCursorFlags = 0;
+
+ CallCallback(SalEvent::ExtTextInput, static_cast<void*>(&aEv));
+ bRet = true;
+ }
+ else
+ bRet = endUnicodeSequence();
+ }
+ else
+ endUnicodeSequence();
+ return bRet;
+}
+
+bool X11SalFrame::endUnicodeSequence()
+{
+ OUString& rSeq( GetGenericUnixSalData()->GetUnicodeCommand() );
+
+ vcl::DeletionListener aDeleteWatch( this );
+ if( rSeq.getLength() > 1 && rSeq.getLength() < 6 )
+ {
+ // cut the "u"
+ std::u16string_view aNumbers( rSeq.subView( 1 ) );
+ sal_uInt32 nValue = o3tl::toUInt32(aNumbers, 16);
+ if( nValue >= 32 )
+ {
+ ExtTextInputAttr nTextAttr = ExtTextInputAttr::Underline;
+ SalExtTextInputEvent aEv;
+ aEv.maText = OUString( sal_Unicode(nValue) );
+ aEv.mpTextAttr = &nTextAttr;
+ aEv.mnCursorPos = 0;
+ aEv.mnCursorFlags = 0;
+ CallCallback(SalEvent::ExtTextInput, static_cast<void*>(&aEv));
+ }
+ }
+ bool bWasInput = !rSeq.isEmpty();
+ rSeq.clear();
+ if( bWasInput && ! aDeleteWatch.isDeleted() )
+ CallCallback(SalEvent::EndExtTextInput, nullptr);
+ return bWasInput;
+}
+
+bool X11SalFrame::HandleKeyEvent( XKeyEvent *pEvent )
+{
+ if( pEvent->type == KeyRelease )
+ {
+ // Ignore autorepeat keyrelease events. If there is a series of keypress+keyrelease+keypress events
+ // generated by holding down a key, and if these are from autorepeat (keyrelease and the following keypress
+ // have the same timestamp), drop the autorepeat keyrelease event. Not exactly sure why this is done
+ // (possibly hiding differences between platforms, or just making it more sensible, because technically
+ // the key has not been released at all).
+ bool ignore = false;
+ // Discard queued excessive autorepeat events.
+ // If the user presses and holds down a key, the autorepeating keypress events
+ // may overload LO (e.g. if the key is PageDown and the LO cannot keep up scrolling).
+ // Reduce the load by simply discarding such excessive events (so for a KeyRelease event,
+ // check if it's followed by matching KeyPress+KeyRelease pair(s) and discard those).
+ // This shouldn't have any negative effects - unlike with normal (non-autorepeat
+ // events), the user is unlikely to rely on the exact number of resulting actions
+ // (since autorepeat generates keypress events rather quickly and it's hard to estimate
+ // how many exactly) and the idea should be just keeping the key pressed until something
+ // happens (in which case more events that just lag LO shouldn't make a difference).
+ Display* dpy = pEvent->display;
+ XKeyEvent previousRelease = *pEvent;
+ while( XPending( dpy ))
+ {
+ XEvent nextEvent1;
+ bool discard1 = false;
+ XNextEvent( dpy, &nextEvent1 );
+ if( nextEvent1.type == KeyPress && nextEvent1.xkey.time == previousRelease.time
+ && !nextEvent1.xkey.send_event && nextEvent1.xkey.window == previousRelease.window
+ && nextEvent1.xkey.state == previousRelease.state && nextEvent1.xkey.keycode == previousRelease.keycode )
+ { // This looks like another autorepeat keypress.
+ ignore = true;
+ if( XPending( dpy ))
+ {
+ XEvent nextEvent2;
+ XNextEvent( dpy, &nextEvent2 );
+ if( nextEvent2.type == KeyRelease && nextEvent2.xkey.time <= ( previousRelease.time + 100 )
+ && !nextEvent2.xkey.send_event && nextEvent2.xkey.window == previousRelease.window
+ && nextEvent2.xkey.state == previousRelease.state && nextEvent2.xkey.keycode == previousRelease.keycode )
+ { // And the matching keyrelease -> drop them both.
+ discard1 = true;
+ previousRelease = nextEvent2.xkey;
+ ignore = false; // There either will be another autorepeating keypress that'll lead to discarding
+ // the pEvent keyrelease, it this discarding makes that keyrelease the last one.
+ }
+ else
+ {
+ XPutBackEvent( dpy, &nextEvent2 );
+ break;
+ }
+ }
+ }
+ if( !discard1 )
+ { // Unrelated event, put back and stop compressing.
+ XPutBackEvent( dpy, &nextEvent1 );
+ break;
+ }
+ }
+ if( ignore ) // This autorepeating keyrelease is followed by another keypress.
+ return false;
+ }
+
+ KeySym nKeySym;
+ KeySym nUnmodifiedKeySym;
+ int nLen = 2048;
+ char *pPrintable = static_cast<char*>(alloca( nLen ));
+
+ // singlebyte code composed by input method, the new default
+ if (mpInputContext != nullptr && mpInputContext->UseContext())
+ {
+ // returns a keysym as well as the pPrintable (in system encoding)
+ // printable may be empty.
+ Status nStatus;
+ nKeySym = pDisplay_->GetKeySym( pEvent, pPrintable, &nLen,
+ &nUnmodifiedKeySym,
+ &nStatus, mpInputContext->GetContext() );
+ if ( nStatus == XBufferOverflow )
+ {
+ // In case of overflow, XmbLookupString (called by GetKeySym)
+ // returns required size
+ // TODO : check if +1 is needed for 0 terminator
+ nLen += 1;
+ pPrintable = static_cast<char*>(alloca( nLen ));
+ nKeySym = pDisplay_->GetKeySym( pEvent, pPrintable, &nLen,
+ &nUnmodifiedKeySym,
+ &nStatus, mpInputContext->GetContext() );
+ }
+ }
+ else
+ {
+ // fallback, this should never ever be called
+ Status nStatus = 0;
+ nKeySym = pDisplay_->GetKeySym( pEvent, pPrintable, &nLen, &nUnmodifiedKeySym, &nStatus );
+ }
+
+ SalKeyEvent aKeyEvt;
+ sal_uInt16 nKeyCode;
+ sal_uInt16 nModCode = 0;
+ char aDummy;
+
+ if( pEvent->state & ShiftMask )
+ nModCode |= KEY_SHIFT;
+ if( pEvent->state & ControlMask )
+ nModCode |= KEY_MOD1;
+ if( pEvent->state & Mod1Mask )
+ nModCode |= KEY_MOD2;
+
+ if( nModCode != (KEY_SHIFT|KEY_MOD1) )
+ endUnicodeSequence();
+
+ if( nKeySym == XK_Shift_L || nKeySym == XK_Shift_R
+ || nKeySym == XK_Control_L || nKeySym == XK_Control_R
+ || nKeySym == XK_Alt_L || nKeySym == XK_Alt_R
+ || nKeySym == XK_Meta_L || nKeySym == XK_Meta_R
+ || nKeySym == XK_Super_L || nKeySym == XK_Super_R )
+ {
+ SalKeyModEvent aModEvt;
+ aModEvt.mbDown = false; // auto-accelerator feature not supported here.
+ aModEvt.mnModKeyCode = ModKeyFlags::NONE;
+ if( pEvent->type == KeyPress && mnExtKeyMod == ModKeyFlags::NONE )
+ mbSendExtKeyModChange = true;
+ else if( pEvent->type == KeyRelease && mbSendExtKeyModChange )
+ {
+ aModEvt.mnModKeyCode = mnExtKeyMod;
+ mnExtKeyMod = ModKeyFlags::NONE;
+ }
+
+ // pressing just the ctrl key leads to a keysym of XK_Control but
+ // the event state does not contain ControlMask. In the release
+ // event it's the other way round: it does contain the Control mask.
+ // The modifier mode therefore has to be adapted manually.
+ ModKeyFlags nExtModMask = ModKeyFlags::NONE;
+ sal_uInt16 nModMask = 0;
+ switch( nKeySym )
+ {
+ case XK_Control_L:
+ nExtModMask = ModKeyFlags::LeftMod1;
+ nModMask = KEY_MOD1;
+ break;
+ case XK_Control_R:
+ nExtModMask = ModKeyFlags::RightMod1;
+ nModMask = KEY_MOD1;
+ break;
+ case XK_Alt_L:
+ nExtModMask = ModKeyFlags::LeftMod2;
+ nModMask = KEY_MOD2;
+ break;
+ case XK_Alt_R:
+ nExtModMask = ModKeyFlags::RightMod2;
+ nModMask = KEY_MOD2;
+ break;
+ case XK_Shift_L:
+ nExtModMask = ModKeyFlags::LeftShift;
+ nModMask = KEY_SHIFT;
+ break;
+ case XK_Shift_R:
+ nExtModMask = ModKeyFlags::RightShift;
+ nModMask = KEY_SHIFT;
+ break;
+ // Map Meta/Super keys to MOD3 modifier on all Unix systems
+ // except macOS
+ case XK_Meta_L:
+ case XK_Super_L:
+ nExtModMask = ModKeyFlags::LeftMod3;
+ nModMask = KEY_MOD3;
+ break;
+ case XK_Meta_R:
+ case XK_Super_R:
+ nExtModMask = ModKeyFlags::RightMod3;
+ nModMask = KEY_MOD3;
+ break;
+ }
+ if( pEvent->type == KeyRelease )
+ {
+ nModCode &= ~nModMask;
+ mnExtKeyMod &= ~nExtModMask;
+ }
+ else
+ {
+ nModCode |= nModMask;
+ mnExtKeyMod |= nExtModMask;
+ }
+
+ aModEvt.mnCode = nModCode;
+
+ return CallCallback( SalEvent::KeyModChange, &aModEvt );
+ }
+
+ mbSendExtKeyModChange = false;
+
+ // try to figure out the vcl code for the keysym
+ // #i52338# use the unmodified KeySym if there is none for the real KeySym
+ // because the independent part has only keycodes for unshifted keys
+ nKeyCode = pDisplay_->GetKeyCode( nKeySym, &aDummy );
+ if( nKeyCode == 0 )
+ nKeyCode = pDisplay_->GetKeyCode( nUnmodifiedKeySym, &aDummy );
+
+ // try to figure out a printable if XmbLookupString returns only a keysym
+ // and NOT a printable. Do not store it in pPrintable[0] since it is expected to
+ // be in system encoding, not unicode.
+ // #i8988##, if KeySym and printable look equally promising then prefer KeySym
+ // the printable is bound to the encoding so the KeySym might contain more
+ // information (in et_EE locale: "Compose + Z + <" delivers "," in printable and
+ // (the desired) Zcaron in KeySym
+ sal_Unicode nKeyString = 0x0;
+ if ( (nLen == 0)
+ || ((nLen == 1) && (nKeySym > 0)) )
+ nKeyString = KeysymToUnicode (nKeySym);
+ // if we have nothing we give up
+ if( !nKeyCode && !nLen && !nKeyString)
+ return false;
+
+ vcl::DeletionListener aDeleteWatch( this );
+
+ if( nModCode == (KEY_SHIFT | KEY_MOD1) && pEvent->type == KeyPress )
+ {
+ sal_uInt16 nSeqKeyCode = pDisplay_->GetKeyCode( nUnmodifiedKeySym, &aDummy );
+ if( nSeqKeyCode == KEY_U )
+ {
+ beginUnicodeSequence();
+ return true;
+ }
+ else if( nSeqKeyCode >= KEY_0 && nSeqKeyCode <= KEY_9 )
+ {
+ if( appendUnicodeSequence( u'0' + sal_Unicode(nSeqKeyCode - KEY_0) ) )
+ return true;
+ }
+ else if( nSeqKeyCode >= KEY_A && nSeqKeyCode <= KEY_F )
+ {
+ if( appendUnicodeSequence( u'a' + sal_Unicode(nSeqKeyCode - KEY_A) ) )
+ return true;
+ }
+ else
+ endUnicodeSequence();
+ }
+
+ if( aDeleteWatch.isDeleted() )
+ return false;
+
+ rtl_TextEncoding nEncoding = osl_getThreadTextEncoding();
+
+ sal_Unicode *pBuffer;
+ sal_Unicode *pString;
+ sal_Size nBufferSize = nLen * 2;
+ sal_Size nSize;
+ pBuffer = static_cast<sal_Unicode*>(malloc( nBufferSize + 2 ));
+ pBuffer[ 0 ] = 0;
+
+ if (nKeyString != 0)
+ {
+ pString = &nKeyString;
+ nSize = 1;
+ }
+ else if (nLen > 0 && nEncoding != RTL_TEXTENCODING_UNICODE)
+ {
+ // create text converter
+ rtl_TextToUnicodeConverter aConverter =
+ rtl_createTextToUnicodeConverter( nEncoding );
+ rtl_TextToUnicodeContext aContext =
+ rtl_createTextToUnicodeContext( aConverter );
+
+ sal_uInt32 nConversionInfo;
+ sal_Size nConvertedChars;
+
+ // convert to single byte text stream
+ nSize = rtl_convertTextToUnicode(
+ aConverter, aContext,
+ pPrintable, nLen,
+ pBuffer, nBufferSize,
+ RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_IGNORE |
+ RTL_TEXTTOUNICODE_FLAGS_INVALID_IGNORE,
+ &nConversionInfo, &nConvertedChars );
+
+ // destroy converter
+ rtl_destroyTextToUnicodeContext( aConverter, aContext );
+ rtl_destroyTextToUnicodeConverter( aConverter );
+
+ pString = pBuffer;
+ }
+ else if (nLen > 0 /* nEncoding == RTL_TEXTENCODING_UNICODE */)
+ {
+ pString = reinterpret_cast<sal_Unicode*>(pPrintable);
+ nSize = nLen;
+ }
+ else
+ {
+ pString = pBuffer;
+ nSize = 0;
+ }
+
+ if ( mpInputContext != nullptr
+ && mpInputContext->UseContext()
+ && KeyRelease != pEvent->type
+ && ( (nSize > 1)
+ || (nSize > 0 && mpInputContext->IsPreeditMode())) )
+ {
+ mpInputContext->CommitKeyEvent(pString, nSize);
+ }
+ else
+ // normal single character keyinput
+ {
+ aKeyEvt.mnCode = nKeyCode | nModCode;
+ aKeyEvt.mnRepeat = 0;
+ aKeyEvt.mnCharCode = pString[ 0 ];
+
+ if( KeyRelease == pEvent->type )
+ {
+ CallCallback( SalEvent::KeyUp, &aKeyEvt );
+ }
+ else
+ {
+ if ( ! CallCallback(SalEvent::KeyInput, &aKeyEvt) )
+ {
+ // independent layer doesn't want to handle key-event, so check
+ // whether the keycode may have an alternate meaning
+ KeyAlternate aAlternate = GetAlternateKeyCode( nKeyCode );
+ if ( aAlternate.nKeyCode != 0 )
+ {
+ aKeyEvt.mnCode = aAlternate.nKeyCode | nModCode;
+ if( aAlternate.nCharCode )
+ aKeyEvt.mnCharCode = aAlternate.nCharCode;
+ CallCallback(SalEvent::KeyInput, &aKeyEvt);
+ }
+ }
+ }
+ }
+
+ // update the spot location for PreeditPosition IME style
+
+ if (! aDeleteWatch.isDeleted())
+ {
+ if (mpInputContext != nullptr && mpInputContext->UseContext())
+ mpInputContext->UpdateSpotLocation();
+ }
+
+ free (pBuffer);
+ return true;
+}
+
+bool X11SalFrame::HandleFocusEvent( XFocusChangeEvent const *pEvent )
+{
+ // ReflectionX in Windows mode changes focus while mouse is grabbed
+ if( nVisibleFloats > 0 && GetDisplay()->getWMAdaptor()->getWindowManagerName() == "ReflectionX Windows" )
+ return true;
+
+ /* ignore focusout resulting from keyboard grabs
+ * we do not grab it and are not interested when
+ * someone else does CDE e.g. does a XGrabKey on arrow keys
+ * handle focus events with mode NotifyWhileGrabbed
+ * because with CDE alt-tab focus changing we do not get
+ * normal focus events
+ * cast focus event to the input context, otherwise the
+ * status window does not follow the application frame
+ */
+
+ if ( mpInputContext != nullptr )
+ {
+ if( FocusIn == pEvent->type )
+ mpInputContext->SetICFocus( this );
+ }
+
+ if ( pEvent->mode == NotifyNormal || pEvent->mode == NotifyWhileGrabbed ||
+ ( ( nStyle_ & SalFrameStyleFlags::PLUG ) && pEvent->window == GetShellWindow() )
+ )
+ {
+ if( hPresentationWindow != None && hPresentationWindow != GetShellWindow() )
+ return false;
+
+ if( FocusIn == pEvent->type )
+ {
+ GetSalInstance()->updatePrinterUpdate();
+ mbInputFocus = True;
+ ImplSVData* pSVData = ImplGetSVData();
+
+ bool nRet = CallCallback( SalEvent::GetFocus, nullptr );
+ if ((mpParent != nullptr && nStyle_ == SalFrameStyleFlags::NONE)
+ && pSVData->mpWinData->mpFirstFloat)
+ {
+ FloatWinPopupFlags nMode = pSVData->mpWinData->mpFirstFloat->GetPopupModeFlags();
+ pSVData->mpWinData->mpFirstFloat->SetPopupModeFlags(
+ nMode & ~FloatWinPopupFlags::NoAppFocusClose);
+ }
+ return nRet;
+ }
+ else
+ {
+ mbInputFocus = False;
+ mbSendExtKeyModChange = false;
+ mnExtKeyMod = ModKeyFlags::NONE;
+ return CallCallback( SalEvent::LoseFocus, nullptr );
+ }
+ }
+
+ return false;
+}
+
+bool X11SalFrame::HandleExposeEvent( XEvent const *pEvent )
+{
+ XRectangle aRect = { 0, 0, 0, 0 };
+ sal_uInt16 nCount = 0;
+
+ if( pEvent->type == Expose )
+ {
+ aRect.x = pEvent->xexpose.x;
+ aRect.y = pEvent->xexpose.y;
+ aRect.width = pEvent->xexpose.width;
+ aRect.height = pEvent->xexpose.height;
+ nCount = pEvent->xexpose.count;
+ }
+ else if( pEvent->type == GraphicsExpose )
+ {
+ aRect.x = pEvent->xgraphicsexpose.x;
+ aRect.y = pEvent->xgraphicsexpose.y;
+ aRect.width = pEvent->xgraphicsexpose.width;
+ aRect.height = pEvent->xgraphicsexpose.height;
+ nCount = pEvent->xgraphicsexpose.count;
+ }
+
+ if( IsOverrideRedirect() && mbFullScreen &&
+ aPresentationReparentList.empty() )
+ // we are in fullscreen mode -> override redirect
+ // focus is possibly lost, so reget it
+ XSetInputFocus( GetXDisplay(), GetShellWindow(), RevertToNone, CurrentTime );
+
+ // width and height are extents, so they are of by one for rectangle
+ maPaintRegion.Union( tools::Rectangle( Point(aRect.x, aRect.y), Size(aRect.width+1, aRect.height+1) ) );
+
+ if( nCount )
+ // wait for last expose rectangle, do not wait for resize timer
+ // if a completed graphics expose sequence is available
+ return true;
+
+ SalPaintEvent aPEvt( maPaintRegion.Left(), maPaintRegion.Top(), maPaintRegion.GetWidth(), maPaintRegion.GetHeight() );
+
+ CallCallback( SalEvent::Paint, &aPEvt );
+ maPaintRegion = tools::Rectangle();
+
+ return true;
+}
+
+void X11SalFrame::RestackChildren( ::Window* pTopLevelWindows, int nTopLevelWindows )
+{
+ if( maChildren.empty() )
+ return;
+
+ int nWindow = nTopLevelWindows;
+ while( nWindow-- )
+ if( pTopLevelWindows[nWindow] == GetStackingWindow() )
+ break;
+ if( nWindow < 0 )
+ return;
+
+ for (auto const& child : maChildren)
+ {
+ if( child->bMapped_ )
+ {
+ int nChild = nWindow;
+ while( nChild-- )
+ {
+ if( pTopLevelWindows[nChild] == child->GetStackingWindow() )
+ {
+ // if a child is behind its parent, place it above the
+ // parent (for insane WMs like Dtwm and olwm)
+ XWindowChanges aCfg;
+ aCfg.sibling = GetStackingWindow();
+ aCfg.stack_mode = Above;
+ XConfigureWindow( GetXDisplay(), child->GetStackingWindow(), CWSibling|CWStackMode, &aCfg );
+ break;
+ }
+ }
+ }
+ }
+ for (auto const& child : maChildren)
+ {
+ child->RestackChildren( pTopLevelWindows, nTopLevelWindows );
+ }
+}
+
+void X11SalFrame::RestackChildren()
+{
+ if( maChildren.empty() )
+ return;
+
+ ::Window aRoot, aParent, *pChildren = nullptr;
+ unsigned int nChildren;
+ if( XQueryTree( GetXDisplay(),
+ GetDisplay()->GetRootWindow( m_nXScreen ),
+ &aRoot,
+ &aParent,
+ &pChildren,
+ &nChildren ) )
+ {
+ RestackChildren( pChildren, nChildren );
+ XFree( pChildren );
+ }
+}
+
+static Bool size_event_predicate( Display*, XEvent* event, XPointer arg )
+{
+ if( event->type != ConfigureNotify )
+ return False;
+ X11SalFrame* frame = reinterpret_cast< X11SalFrame* >( arg );
+ XConfigureEvent* pEvent = &event->xconfigure;
+ if( pEvent->window != frame->GetShellWindow()
+ && pEvent->window != frame->GetWindow()
+ && pEvent->window != frame->GetForeignParent()
+ && pEvent->window != frame->GetStackingWindow())
+ { // ignored at top of HandleSizeEvent()
+ return False;
+ }
+ if( pEvent->window == frame->GetStackingWindow())
+ return False; // filtered later in HandleSizeEvent()
+ // at this point we know that there is another similar event in the queue
+ frame->setPendingSizeEvent();
+ return False; // but do not process the new event out of order
+}
+
+void X11SalFrame::setPendingSizeEvent()
+{
+ mPendingSizeEvent = true;
+}
+
+bool X11SalFrame::HandleSizeEvent( XConfigureEvent *pEvent )
+{
+ // NOTE: if you add more tests in this function, make sure to update size_event_predicate()
+ // so that it finds exactly the same events
+
+ if ( pEvent->window != GetShellWindow()
+ && pEvent->window != GetWindow()
+ && pEvent->window != GetForeignParent()
+ && pEvent->window != GetStackingWindow()
+ )
+ {
+ // could be as well a sys-child window (aka SalObject)
+ return true;
+ }
+
+ if( ( nStyle_ & SalFrameStyleFlags::PLUG ) && pEvent->window == GetShellWindow() )
+ {
+ // just update the children's positions
+ RestackChildren();
+ return true;
+ }
+
+ if( pEvent->window == GetForeignParent() )
+ XResizeWindow( GetXDisplay(),
+ GetWindow(),
+ pEvent->width,
+ pEvent->height );
+
+ ::Window hDummy;
+ XTranslateCoordinates( GetXDisplay(),
+ GetWindow(),
+ pDisplay_->GetRootWindow( pDisplay_->GetDefaultXScreen() ),
+ 0, 0,
+ &pEvent->x, &pEvent->y,
+ &hDummy );
+
+ if( pEvent->window == GetStackingWindow() )
+ {
+ if( maGeometry.nX != pEvent->x || maGeometry.nY != pEvent->y )
+ {
+ maGeometry.nX = pEvent->x;
+ maGeometry.nY = pEvent->y;
+ CallCallback( SalEvent::Move, nullptr );
+ }
+ return true;
+ }
+
+ // check size hints in first time SalFrame::Show
+ if( X11ShowState::Unknown == nShowState_ && bMapped_ )
+ nShowState_ = X11ShowState::Normal;
+
+ // Avoid a race condition where resizing this window to one size and shortly after that
+ // to another size generates first size event with the old size and only after that
+ // with the new size, temporarily making us think the old size is valid (bnc#674806).
+ // So if there is another size event for this window pending, ignore this one.
+ mPendingSizeEvent = false;
+ XEvent dummy;
+ XCheckIfEvent( GetXDisplay(), &dummy, size_event_predicate, reinterpret_cast< XPointer >( this ));
+ if( mPendingSizeEvent )
+ return true;
+
+ nWidth_ = pEvent->width;
+ nHeight_ = pEvent->height;
+
+ bool bMoved = ( pEvent->x != maGeometry.nX || pEvent->y != maGeometry.nY );
+ bool bSized = ( pEvent->width != static_cast<int>(maGeometry.nWidth) || pEvent->height != static_cast<int>(maGeometry.nHeight) );
+
+ maGeometry.nX = pEvent->x;
+ maGeometry.nY = pEvent->y;
+ maGeometry.nWidth = pEvent->width;
+ maGeometry.nHeight = pEvent->height;
+ updateScreenNumber();
+
+ // update children's position
+ RestackChildren();
+
+ if( bSized && ! bMoved )
+ CallCallback( SalEvent::Resize, nullptr );
+ else if( bMoved && ! bSized )
+ CallCallback( SalEvent::Move, nullptr );
+ else if( bMoved && bSized )
+ CallCallback( SalEvent::MoveResize, nullptr );
+
+ return true;
+}
+
+IMPL_LINK_NOARG(X11SalFrame, HandleAlwaysOnTopRaise, Timer *, void)
+{
+ if( bMapped_ )
+ ToTop( SalFrameToTop::NONE );
+}
+
+bool X11SalFrame::HandleReparentEvent( XReparentEvent *pEvent )
+{
+ Display *pDisplay = pEvent->display;
+ ::Window hWM_Parent;
+ ::Window hRoot, *Children, hDummy;
+ unsigned int nChildren;
+
+ static const char* pDisableStackingCheck = getenv( "SAL_DISABLE_STACKING_CHECK" );
+
+ GetGenericUnixSalData()->ErrorTrapPush();
+
+ /*
+ * don't rely on the new parent from the event.
+ * the event may be "out of date", that is the window manager
+ * window may not exist anymore. This can happen if someone
+ * shows a frame and hides it again quickly (not that it would
+ * be very sensible)
+ */
+ hWM_Parent = GetShellWindow();
+ do
+ {
+ Children = nullptr;
+ XQueryTree( pDisplay,
+ hWM_Parent,
+ &hRoot,
+ &hDummy,
+ &Children,
+ &nChildren );
+
+ bool bError = GetGenericUnixSalData()->ErrorTrapPop( false );
+ GetGenericUnixSalData()->ErrorTrapPush();
+
+ if( bError )
+ {
+ hWM_Parent = GetShellWindow();
+ break;
+ }
+ /* this sometimes happens if a Show(true) is
+ * immediately followed by Show(false) (which is braindead anyway)
+ */
+ if( hDummy == hWM_Parent )
+ hDummy = hRoot;
+ if( hDummy != hRoot )
+ hWM_Parent = hDummy;
+ if( Children )
+ XFree( Children );
+ } while( hDummy != hRoot );
+
+ if( GetStackingWindow() == None
+ && hWM_Parent != hPresentationWindow
+ && hWM_Parent != GetShellWindow()
+ && ( ! pDisableStackingCheck || ! *pDisableStackingCheck )
+ )
+ {
+ mhStackingWindow = hWM_Parent;
+ XSelectInput( pDisplay, GetStackingWindow(), StructureNotifyMask );
+ }
+
+ if( hWM_Parent == pDisplay_->GetRootWindow( pDisplay_->GetDefaultXScreen() )
+ || hWM_Parent == GetForeignParent()
+ || pEvent->parent == pDisplay_->GetRootWindow( pDisplay_->GetDefaultXScreen() )
+ || ( nStyle_ & SalFrameStyleFlags::FLOAT ) )
+ {
+ // Reparenting before Destroy
+ aPresentationReparentList.remove( GetStackingWindow() );
+ mhStackingWindow = None;
+ GetGenericUnixSalData()->ErrorTrapPop();
+ return false;
+ }
+
+ /*
+ * evil hack to show decorated windows on top
+ * of override redirect presentation windows:
+ * reparent the window manager window to the presentation window
+ * does not work with non-reparenting WMs
+ * in future this should not be necessary anymore with
+ * _NET_WM_STATE_FULLSCREEN available
+ */
+ if( hPresentationWindow != None
+ && hPresentationWindow != GetWindow()
+ && GetStackingWindow() != None
+ && GetStackingWindow() != GetDisplay()->GetRootWindow( m_nXScreen )
+ )
+ {
+ int x = 0, y = 0;
+ ::Window aChild;
+ XTranslateCoordinates( GetXDisplay(),
+ GetStackingWindow(),
+ GetDisplay()->GetRootWindow( m_nXScreen ),
+ 0, 0,
+ &x, &y,
+ &aChild
+ );
+ XReparentWindow( GetXDisplay(),
+ GetStackingWindow(),
+ hPresentationWindow,
+ x, y
+ );
+ aPresentationReparentList.push_back( GetStackingWindow() );
+ }
+
+ int nLeft = 0, nTop = 0;
+ XTranslateCoordinates( GetXDisplay(),
+ GetShellWindow(),
+ hWM_Parent,
+ 0, 0,
+ &nLeft,
+ &nTop,
+ &hDummy );
+ maGeometry.nLeftDecoration = nLeft > 0 ? nLeft-1 : 0;
+ maGeometry.nTopDecoration = nTop > 0 ? nTop-1 : 0;
+
+ /*
+ * decorations are not symmetric,
+ * so need real geometries here
+ * (this will fail with virtual roots ?)
+ */
+
+ // reset error occurred
+ GetGenericUnixSalData()->ErrorTrapPop();
+ GetGenericUnixSalData()->ErrorTrapPush();
+
+ int xp, yp, x, y;
+ unsigned int wp, w, hp, h, bw, d;
+ XGetGeometry( GetXDisplay(),
+ GetShellWindow(),
+ &hRoot,
+ &x, &y, &w, &h, &bw, &d );
+ XGetGeometry( GetXDisplay(),
+ hWM_Parent,
+ &hRoot,
+ &xp, &yp, &wp, &hp, &bw, &d );
+ bool bResized = false;
+ bool bError = GetGenericUnixSalData()->ErrorTrapPop( false );
+ GetGenericUnixSalData()->ErrorTrapPush();
+
+ if( ! bError )
+ {
+ maGeometry.nRightDecoration = wp - w - maGeometry.nLeftDecoration;
+ maGeometry.nBottomDecoration = hp - h - maGeometry.nTopDecoration;
+ /*
+ * note: this works because hWM_Parent is direct child of root,
+ * not necessarily parent of GetShellWindow()
+ */
+ maGeometry.nX = xp + nLeft;
+ maGeometry.nY = yp + nTop;
+ bResized = w != maGeometry.nWidth || h != maGeometry.nHeight;
+ maGeometry.nWidth = w;
+ maGeometry.nHeight = h;
+ }
+
+ // limit width and height if we are too large: #47757
+ // olwm and fvwm need this, it doesn't harm the rest
+
+ // #i81311# do this only for sizable frames
+ if( nStyle_ & SalFrameStyleFlags::SIZEABLE )
+ {
+ Size aScreenSize = GetDisplay()->GetScreenSize( m_nXScreen );
+ int nScreenWidth = aScreenSize.Width();
+ int nScreenHeight = aScreenSize.Height();
+ int nFrameWidth = maGeometry.nWidth + maGeometry.nLeftDecoration + maGeometry.nRightDecoration;
+ int nFrameHeight = maGeometry.nHeight + maGeometry.nTopDecoration + maGeometry.nBottomDecoration;
+
+ if ((nFrameWidth > nScreenWidth) || (nFrameHeight > nScreenHeight))
+ {
+ Size aSize(maGeometry.nWidth, maGeometry.nHeight);
+
+ if (nFrameWidth > nScreenWidth)
+ aSize.setWidth( nScreenWidth - maGeometry.nRightDecoration - maGeometry.nLeftDecoration );
+ if (nFrameHeight > nScreenHeight)
+ aSize.setHeight( nScreenHeight - maGeometry.nBottomDecoration - maGeometry.nTopDecoration );
+
+ SetSize( aSize );
+ bResized = false;
+ }
+ }
+ if( bResized )
+ CallCallback( SalEvent::Resize, nullptr );
+
+ GetGenericUnixSalData()->ErrorTrapPop();
+
+ return true;
+}
+
+bool X11SalFrame::HandleStateEvent( XPropertyEvent const *pEvent )
+{
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ unsigned char *prop = nullptr;
+
+ if( 0 != XGetWindowProperty( GetXDisplay(),
+ GetShellWindow(),
+ pEvent->atom, // property
+ 0, // long_offset (32bit)
+ 2, // long_length (32bit)
+ False, // delete
+ pEvent->atom, // req_type
+ &actual_type,
+ &actual_format,
+ &nitems,
+ &bytes_after,
+ &prop )
+ || ! prop
+ )
+ return false;
+
+ DBG_ASSERT( actual_type == pEvent->atom
+ && 32 == actual_format
+ && 2 == nitems
+ && 0 == bytes_after, "HandleStateEvent" );
+
+ if( *reinterpret_cast<unsigned long*>(prop) == NormalState )
+ nShowState_ = X11ShowState::Normal;
+ else if( *reinterpret_cast<unsigned long*>(prop) == IconicState )
+ nShowState_ = X11ShowState::Minimized;
+
+ XFree( prop );
+ return true;
+}
+
+bool X11SalFrame::HandleClientMessage( XClientMessageEvent *pEvent )
+{
+ const WMAdaptor& rWMAdaptor( *pDisplay_->getWMAdaptor() );
+
+#if !defined(__synchronous_extinput__)
+ if( pEvent->message_type == rWMAdaptor.getAtom( WMAdaptor::SAL_EXTTEXTEVENT ) )
+ {
+ HandleExtTextEvent (pEvent);
+ return true;
+ }
+#endif
+ else if( pEvent->message_type == rWMAdaptor.getAtom( WMAdaptor::SAL_QUITEVENT ) )
+ {
+ SAL_WARN( "vcl", "X11SalFrame::Dispatch Quit" );
+ Close(); // ???
+ return true;
+ }
+ else if( pEvent->message_type == rWMAdaptor.getAtom( WMAdaptor::WM_PROTOCOLS ) )
+ {
+ if( static_cast<Atom>(pEvent->data.l[0]) == rWMAdaptor.getAtom( WMAdaptor::NET_WM_PING ) )
+ rWMAdaptor.answerPing( this, pEvent );
+ else if( ! ( nStyle_ & SalFrameStyleFlags::PLUG )
+ && ! (( nStyle_ & SalFrameStyleFlags::FLOAT ) && (nStyle_ & SalFrameStyleFlags::OWNERDRAWDECORATION))
+ )
+ {
+ if( static_cast<Atom>(pEvent->data.l[0]) == rWMAdaptor.getAtom( WMAdaptor::WM_DELETE_WINDOW ) )
+ {
+ Close();
+ return true;
+ }
+ else if( static_cast<Atom>(pEvent->data.l[0]) == rWMAdaptor.getAtom( WMAdaptor::WM_TAKE_FOCUS ) )
+ {
+ // do nothing, we set the input focus in ToTop() if necessary
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.window", "got WM_TAKE_FOCUS on "
+ << ((nStyle_ &
+ SalFrameStyleFlags::OWNERDRAWDECORATION) ?
+ "ownerdraw" :
+ "NON OWNERDRAW" )
+ << " window.");
+#endif
+ }
+ }
+ }
+ else if( pEvent->message_type == rWMAdaptor.getAtom( WMAdaptor::XEMBED ) &&
+ pEvent->window == GetWindow() )
+ {
+ if( pEvent->data.l[1] == 1 || // XEMBED_WINDOW_ACTIVATE
+ pEvent->data.l[1] == 2 ) // XEMBED_WINDOW_DEACTIVATE
+ {
+ XFocusChangeEvent aEvent;
+ aEvent.type = (pEvent->data.l[1] == 1 ? FocusIn : FocusOut);
+ aEvent.serial = pEvent->serial;
+ aEvent.send_event = True;
+ aEvent.display = pEvent->display;
+ aEvent.window = pEvent->window;
+ aEvent.mode = NotifyNormal;
+ aEvent.detail = NotifyDetailNone;
+ HandleFocusEvent( &aEvent );
+ }
+ }
+ return false;
+}
+
+bool X11SalFrame::Dispatch( XEvent *pEvent )
+{
+ bool nRet = false;
+
+ if( -1 == nCaptured_ )
+ {
+ CaptureMouse( true );
+#ifdef DBG_UTIL
+ if( -1 != nCaptured_ )
+ pDisplay_->DbgPrintDisplayEvent("Captured", pEvent);
+#endif
+ }
+
+ if( pEvent->xany.window == GetShellWindow() || pEvent->xany.window == GetWindow() )
+ {
+ switch( pEvent->type )
+ {
+ case KeyPress:
+ nRet = HandleKeyEvent( &pEvent->xkey );
+ break;
+
+ case KeyRelease:
+ nRet = HandleKeyEvent( &pEvent->xkey );
+ break;
+
+ case ButtonPress:
+ // if we lose the focus in presentation mode
+ // there are good chances that we never get it back
+ // since the WM ignores us
+ if( IsOverrideRedirect() )
+ {
+ XSetInputFocus( GetXDisplay(), GetShellWindow(),
+ RevertToNone, CurrentTime );
+ }
+ [[fallthrough]];
+ case ButtonRelease:
+ case MotionNotify:
+ case EnterNotify:
+ case LeaveNotify:
+ nRet = HandleMouseEvent( pEvent );
+ break;
+
+ case FocusIn:
+ case FocusOut:
+ nRet = HandleFocusEvent( &pEvent->xfocus );
+ break;
+
+ case Expose:
+ case GraphicsExpose:
+ nRet = HandleExposeEvent( pEvent );
+ break;
+
+ case MapNotify:
+ if( pEvent->xmap.window == GetShellWindow() )
+ {
+ if( nShowState_ == X11ShowState::Hidden )
+ {
+ /*
+ * workaround for (at least) KWin 2.2.2
+ * which will map windows that were once transient
+ * even if they are withdrawn when the respective
+ * document is mapped.
+ */
+ if( ! (nStyle_ & SalFrameStyleFlags::PLUG) )
+ XUnmapWindow( GetXDisplay(), GetShellWindow() );
+ break;
+ }
+ bMapped_ = true;
+ bViewable_ = true;
+ nRet = true;
+ if ( mpInputContext != nullptr )
+ mpInputContext->Map( this );
+ CallCallback( SalEvent::Resize, nullptr );
+
+ bool bSetFocus = m_bSetFocusOnMap;
+
+ /*
+ * sometimes a message box/dialogue is brought up when a frame is not mapped
+ * the corresponding TRANSIENT_FOR hint is then set to the root window
+ * so that the dialogue shows in all cases. Correct it here if the
+ * frame is shown afterwards.
+ */
+ if( ! IsChildWindow()
+ && ! IsOverrideRedirect()
+ && ! IsFloatGrabWindow()
+ )
+ {
+ for (auto const& child : maChildren)
+ {
+ if( child->mbTransientForRoot )
+ pDisplay_->getWMAdaptor()->changeReferenceFrame( child, this );
+ }
+ }
+
+ if( hPresentationWindow != None && GetShellWindow() == hPresentationWindow )
+ XSetInputFocus( GetXDisplay(), GetShellWindow(), RevertToParent, CurrentTime );
+
+ if( bSetFocus )
+ {
+ XSetInputFocus( GetXDisplay(),
+ GetShellWindow(),
+ RevertToParent,
+ CurrentTime );
+ }
+
+ RestackChildren();
+ m_bSetFocusOnMap = false;
+ }
+ break;
+
+ case UnmapNotify:
+ if( pEvent->xunmap.window == GetShellWindow() )
+ {
+ bMapped_ = false;
+ bViewable_ = false;
+ nRet = true;
+ if ( mpInputContext != nullptr )
+ mpInputContext->Unmap();
+ CallCallback( SalEvent::Resize, nullptr );
+ }
+ break;
+
+ case ConfigureNotify:
+ if( pEvent->xconfigure.window == GetShellWindow()
+ || pEvent->xconfigure.window == GetWindow() )
+ nRet = HandleSizeEvent( &pEvent->xconfigure );
+ break;
+
+ case VisibilityNotify:
+ nVisibility_ = pEvent->xvisibility.state;
+ nRet = true;
+ if( bAlwaysOnTop_
+ && bMapped_
+ && ! GetDisplay()->getWMAdaptor()->isAlwaysOnTopOK()
+ && nVisibility_ != VisibilityUnobscured )
+ maAlwaysOnTopRaiseTimer.Start();
+ break;
+
+ case ReparentNotify:
+ nRet = HandleReparentEvent( &pEvent->xreparent );
+ break;
+
+ case MappingNotify:
+ break;
+
+ case ColormapNotify:
+ nRet = false;
+ break;
+
+ case PropertyNotify:
+ {
+ if( pEvent->xproperty.atom == pDisplay_->getWMAdaptor()->getAtom( WMAdaptor::WM_STATE ) )
+ nRet = HandleStateEvent( &pEvent->xproperty );
+ else
+ nRet = pDisplay_->getWMAdaptor()->handlePropertyNotify( this, &pEvent->xproperty );
+ break;
+ }
+
+ case ClientMessage:
+ nRet = HandleClientMessage( &pEvent->xclient );
+ break;
+ }
+ }
+ else
+ {
+ switch( pEvent->type )
+ {
+ case FocusIn:
+ case FocusOut:
+ if( ( nStyle_ & SalFrameStyleFlags::PLUG )
+ && ( pEvent->xfocus.window == GetShellWindow()
+ || pEvent->xfocus.window == GetForeignParent() )
+ )
+ {
+ nRet = HandleFocusEvent( &pEvent->xfocus );
+ }
+ break;
+
+ case ConfigureNotify:
+ if( pEvent->xconfigure.window == GetForeignParent() ||
+ pEvent->xconfigure.window == GetShellWindow() )
+ nRet = HandleSizeEvent( &pEvent->xconfigure );
+
+ if( pEvent->xconfigure.window == GetStackingWindow() )
+ nRet = HandleSizeEvent( &pEvent->xconfigure );
+
+ RestackChildren();
+ break;
+ }
+ }
+
+ return nRet;
+}
+
+void X11SalFrame::ResetClipRegion()
+{
+ m_vClipRectangles.clear();
+
+ const int dest_kind = ShapeBounding;
+ const int op = ShapeSet;
+ const int ordering = YSorted;
+
+ XWindowAttributes win_attrib;
+ XRectangle win_size;
+
+ ::Window aShapeWindow = mhShellWindow;
+
+ XGetWindowAttributes ( GetDisplay()->GetDisplay(),
+ aShapeWindow,
+ &win_attrib );
+
+ win_size.x = 0;
+ win_size.y = 0;
+ win_size.width = win_attrib.width;
+ win_size.height = win_attrib.height;
+
+ XShapeCombineRectangles ( GetDisplay()->GetDisplay(),
+ aShapeWindow,
+ dest_kind,
+ 0, 0, // x_off, y_off
+ &win_size, // list of rectangles
+ 1, // number of rectangles
+ op, ordering );
+}
+
+void X11SalFrame::BeginSetClipRegion( sal_uInt32 /*nRects*/ )
+{
+ m_vClipRectangles.clear();
+}
+
+void X11SalFrame::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ m_vClipRectangles.emplace_back( XRectangle { static_cast<short>(nX), static_cast<short>(nY),
+ static_cast<unsigned short>(nWidth), static_cast<unsigned short>(nHeight) } );
+}
+
+void X11SalFrame::EndSetClipRegion()
+{
+ const int dest_kind = ShapeBounding;
+ const int ordering = YSorted;
+ const int op = ShapeSet;
+
+ ::Window aShapeWindow = mhShellWindow;
+ XShapeCombineRectangles ( GetDisplay()->GetDisplay(),
+ aShapeWindow,
+ dest_kind,
+ 0, 0, // x_off, y_off
+ m_vClipRectangles.data(),
+ m_vClipRectangles.size(),
+ op, ordering );
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/window/salobj.cxx b/vcl/unx/generic/window/salobj.cxx
new file mode 100644
index 000000000..c24e138e5
--- /dev/null
+++ b/vcl/unx/generic/window/salobj.cxx
@@ -0,0 +1,506 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#if OSL_DEBUG_LEVEL > 1
+#include <stdio.h>
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/shape.h>
+
+
+#include <vcl/keycodes.hxx>
+#include <vcl/event.hxx>
+#include <sal/log.hxx>
+
+#include <unx/salinst.h>
+#include <unx/saldisp.hxx>
+#include <unx/salobj.h>
+
+#include <salframe.hxx>
+#include <salwtype.hxx>
+
+// SalInstance member to create and destroy a SalObject
+
+SalObject* X11SalInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow )
+{
+ return X11SalObject::CreateObject( pParent, pWindowData, bShow );
+}
+
+X11SalObject* X11SalObject::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool bShow )
+{
+ int error_base, event_base;
+ X11SalObject* pObject = new X11SalObject();
+ SystemEnvData* pObjData = const_cast<SystemEnvData*>(pObject->GetSystemData());
+
+ if ( ! XShapeQueryExtension( static_cast<Display*>(pObjData->pDisplay),
+ &event_base, &error_base ) )
+ {
+ delete pObject;
+ return nullptr;
+ }
+
+ pObject->mpParent = pParent;
+
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ const SystemEnvData* pEnv = pParent->GetSystemData();
+ Display* pDisp = pSalDisp->GetDisplay();
+ ::Window aObjectParent = static_cast<::Window>(pEnv->GetWindowHandle(pParent));
+ pObject->maParentWin = aObjectParent;
+
+ // find out on which screen that window is
+ XWindowAttributes aParentAttr;
+ XGetWindowAttributes( pDisp, aObjectParent, &aParentAttr );
+ SalX11Screen nXScreen( XScreenNumberOfScreen( aParentAttr.screen ) );
+ Visual* pVisual = (pWindowData && pWindowData->pVisual) ?
+ static_cast<Visual*>(pWindowData->pVisual) :
+ pSalDisp->GetVisual( nXScreen ).GetVisual();
+ // get visual info
+ VisualID aVisID = XVisualIDFromVisual( pVisual );
+ XVisualInfo aTemplate;
+ aTemplate.visualid = aVisID;
+ int nVisuals = 0;
+ XVisualInfo* pInfo = XGetVisualInfo( pDisp, VisualIDMask, &aTemplate, &nVisuals );
+ // only one VisualInfo structure can match the visual id
+ SAL_WARN_IF( nVisuals != 1, "vcl", "match count for visual id is not 1" );
+ unsigned int nDepth = pInfo->depth;
+ XFree( pInfo );
+ XSetWindowAttributes aAttribs;
+ aAttribs.event_mask = StructureNotifyMask
+ | ButtonPressMask
+ | ButtonReleaseMask
+ | PointerMotionMask
+ | EnterWindowMask
+ | LeaveWindowMask
+ | FocusChangeMask
+ | ExposureMask
+ ;
+
+ pObject->maPrimary =
+ XCreateSimpleWindow( pDisp,
+ aObjectParent,
+ 0, 0,
+ 1, 1, 0,
+ pSalDisp->GetColormap( nXScreen ).GetBlackPixel(),
+ pSalDisp->GetColormap( nXScreen ).GetWhitePixel()
+ );
+ if( aVisID == pSalDisp->GetVisual( nXScreen ).GetVisualId() )
+ {
+ pObject->maSecondary =
+ XCreateSimpleWindow( pDisp,
+ pObject->maPrimary,
+ 0, 0,
+ 1, 1, 0,
+ pSalDisp->GetColormap( nXScreen ).GetBlackPixel(),
+ pSalDisp->GetColormap( nXScreen ).GetWhitePixel()
+ );
+ }
+ else
+ {
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.window", "visual id of vcl "
+ << std::hex
+ << static_cast<unsigned int>
+ (pSalDisp->GetVisual( nXScreen ).GetVisualId())
+ << ", of visual "
+ << static_cast<unsigned int>
+ (aVisID));
+#endif
+ GetGenericUnixSalData()->ErrorTrapPush();
+
+ // create colormap for visual - there might not be one
+ pObject->maColormap = aAttribs.colormap = XCreateColormap(
+ pDisp,
+ pSalDisp->GetRootWindow( nXScreen ),
+ pVisual,
+ AllocNone );
+
+ pObject->maSecondary =
+ XCreateWindow( pDisp,
+ pSalDisp->GetRootWindow( nXScreen ),
+ 0, 0,
+ 1, 1, 0,
+ nDepth, InputOutput,
+ pVisual,
+ CWEventMask|CWColormap, &aAttribs );
+ XSync( pDisp, False );
+ if( GetGenericUnixSalData()->ErrorTrapPop( false ) )
+ {
+ pObject->maSecondary = None;
+ delete pObject;
+ return nullptr;
+ }
+ XReparentWindow( pDisp, pObject->maSecondary, pObject->maPrimary, 0, 0 );
+ }
+
+ GetGenericUnixSalData()->ErrorTrapPush();
+ if( bShow ) {
+ XMapWindow( pDisp, pObject->maSecondary );
+ XMapWindow( pDisp, pObject->maPrimary );
+ }
+
+ pObjData->pDisplay = pDisp;
+ pObjData->SetWindowHandle(pObject->maSecondary);
+ pObjData->pWidget = nullptr;
+ pObjData->pVisual = pVisual;
+
+ XSync(pDisp, False);
+ if( GetGenericUnixSalData()->ErrorTrapPop( false ) )
+ {
+ delete pObject;
+ return nullptr;
+ }
+
+ return pObject;
+}
+
+void X11SalInstance::DestroyObject( SalObject* pObject )
+{
+ delete pObject;
+}
+
+// SalClipRegion is a member of SalObject
+// definition of SalClipRegion my be found in vcl/inc/unx/salobj.h
+
+SalClipRegion::SalClipRegion()
+{
+ ClipRectangleList = nullptr;
+ numClipRectangles = 0;
+ maxClipRectangles = 0;
+}
+
+SalClipRegion::~SalClipRegion()
+{
+}
+
+void
+SalClipRegion::BeginSetClipRegion( sal_uInt32 nRects )
+{
+ ClipRectangleList.reset( new XRectangle[nRects] );
+ numClipRectangles = 0;
+ maxClipRectangles = nRects;
+}
+
+void
+SalClipRegion::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ if ( nWidth && nHeight && (numClipRectangles < maxClipRectangles) )
+ {
+ XRectangle& aRect = ClipRectangleList[numClipRectangles];
+
+ aRect.x = static_cast<short>(nX);
+ aRect.y = static_cast<short>(nY);
+ aRect.width = static_cast<unsigned short>(nWidth);
+ aRect.height= static_cast<unsigned short>(nHeight);
+
+ numClipRectangles++;
+ }
+}
+
+// SalObject Implementation
+X11SalObject::X11SalObject()
+ : mpParent(nullptr)
+ , maParentWin(0)
+ , maPrimary(0)
+ , maSecondary(0)
+ , maColormap(0)
+ , mbVisible(false)
+{
+ maSystemChildData.pDisplay = vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay();
+ maSystemChildData.SetWindowHandle(None);
+ maSystemChildData.pSalFrame = nullptr;
+ maSystemChildData.pWidget = nullptr;
+ maSystemChildData.pVisual = nullptr;
+ maSystemChildData.aShellWindow = 0;
+ maSystemChildData.toolkit = SystemEnvData::Toolkit::Gen;
+ maSystemChildData.platform = SystemEnvData::Platform::Xcb;
+
+ std::list< SalObject* >& rObjects = vcl_sal::getSalDisplay(GetGenericUnixSalData())->getSalObjects();
+ rObjects.push_back( this );
+}
+
+X11SalObject::~X11SalObject()
+{
+ std::list< SalObject* >& rObjects = vcl_sal::getSalDisplay(GetGenericUnixSalData())->getSalObjects();
+ rObjects.remove( this );
+
+ GetGenericUnixSalData()->ErrorTrapPush();
+ ::Window aObjectParent = maParentWin;
+ XSetWindowBackgroundPixmap(static_cast<Display*>(maSystemChildData.pDisplay), aObjectParent, None);
+ if ( maSecondary )
+ XDestroyWindow( static_cast<Display*>(maSystemChildData.pDisplay), maSecondary );
+ if ( maPrimary )
+ XDestroyWindow( static_cast<Display*>(maSystemChildData.pDisplay), maPrimary );
+ if ( maColormap )
+ XFreeColormap(static_cast<Display*>(maSystemChildData.pDisplay), maColormap);
+ XSync( static_cast<Display*>(maSystemChildData.pDisplay), False );
+ GetGenericUnixSalData()->ErrorTrapPop();
+}
+
+void
+X11SalObject::ResetClipRegion()
+{
+ maClipRegion.ResetClipRegion();
+
+ const int dest_kind = ShapeBounding;
+ const int op = ShapeSet;
+ const int ordering = YSorted;
+
+ XWindowAttributes win_attrib;
+ XRectangle win_size;
+
+ ::Window aShapeWindow = maPrimary;
+
+ XGetWindowAttributes ( static_cast<Display*>(maSystemChildData.pDisplay),
+ aShapeWindow,
+ &win_attrib );
+
+ win_size.x = 0;
+ win_size.y = 0;
+ win_size.width = win_attrib.width;
+ win_size.height = win_attrib.height;
+
+ XShapeCombineRectangles ( static_cast<Display*>(maSystemChildData.pDisplay),
+ aShapeWindow,
+ dest_kind,
+ 0, 0, // x_off, y_off
+ &win_size, // list of rectangles
+ 1, // number of rectangles
+ op, ordering );
+}
+
+void
+X11SalObject::BeginSetClipRegion( sal_uInt32 nRectCount )
+{
+ maClipRegion.BeginSetClipRegion ( nRectCount );
+}
+
+void
+X11SalObject::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ maClipRegion.UnionClipRegion ( nX, nY, nWidth, nHeight );
+}
+
+void
+X11SalObject::EndSetClipRegion()
+{
+ XRectangle *pRectangles = maClipRegion.EndSetClipRegion ();
+ const int nRectangles = maClipRegion.GetRectangleCount();
+
+ ::Window aShapeWindow = maPrimary;
+
+ XShapeCombineRectangles ( static_cast<Display*>(maSystemChildData.pDisplay),
+ aShapeWindow,
+ ShapeBounding,
+ 0, 0, // x_off, y_off
+ pRectangles,
+ nRectangles,
+ ShapeSet, YSorted );
+}
+
+void
+X11SalObject::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ if ( maPrimary && maSecondary && nWidth && nHeight )
+ {
+ XMoveResizeWindow( static_cast<Display*>(maSystemChildData.pDisplay),
+ maPrimary,
+ nX, nY, nWidth, nHeight );
+ XMoveResizeWindow( static_cast<Display*>(maSystemChildData.pDisplay),
+ maSecondary,
+ 0, 0, nWidth, nHeight );
+ }
+}
+
+void
+X11SalObject::Show( bool bVisible )
+{
+ if (!maSystemChildData.GetWindowHandle(mpParent))
+ return;
+
+ if ( bVisible ) {
+ XMapWindow( static_cast<Display*>(maSystemChildData.pDisplay),
+ maSecondary );
+ XMapWindow( static_cast<Display*>(maSystemChildData.pDisplay),
+ maPrimary );
+ } else {
+ XUnmapWindow( static_cast<Display*>(maSystemChildData.pDisplay),
+ maPrimary );
+ XUnmapWindow( static_cast<Display*>(maSystemChildData.pDisplay),
+ maSecondary );
+ }
+ mbVisible = bVisible;
+}
+
+void X11SalObject::GrabFocus()
+{
+ if( mbVisible )
+ XSetInputFocus( static_cast<Display*>(maSystemChildData.pDisplay),
+ maSystemChildData.GetWindowHandle(mpParent),
+ RevertToNone,
+ CurrentTime );
+}
+
+const SystemEnvData* X11SalObject::GetSystemData() const
+{
+ return &maSystemChildData;
+}
+
+static sal_uInt16 sal_GetCode( int state )
+{
+ sal_uInt16 nCode = 0;
+
+ if( state & Button1Mask )
+ nCode |= MOUSE_LEFT;
+ if( state & Button2Mask )
+ nCode |= MOUSE_MIDDLE;
+ if( state & Button3Mask )
+ nCode |= MOUSE_RIGHT;
+
+ if( state & ShiftMask )
+ nCode |= KEY_SHIFT;
+ if( state & ControlMask )
+ nCode |= KEY_MOD1;
+ if( state & Mod1Mask )
+ nCode |= KEY_MOD2;
+ if( state & Mod3Mask )
+ nCode |= KEY_MOD3;
+
+ return nCode;
+}
+
+bool X11SalObject::Dispatch( XEvent* pEvent )
+{
+ std::list< SalObject* >& rObjects = vcl_sal::getSalDisplay(GetGenericUnixSalData())->getSalObjects();
+
+ for (auto const& elem : rObjects)
+ {
+ X11SalObject* pObject = static_cast<X11SalObject*>(elem);
+ if( pEvent->xany.window == pObject->maPrimary ||
+ pEvent->xany.window == pObject->maSecondary )
+ {
+ if( pObject->IsMouseTransparent() && (
+ pEvent->type == ButtonPress ||
+ pEvent->type == ButtonRelease ||
+ pEvent->type == EnterNotify ||
+ pEvent->type == LeaveNotify ||
+ pEvent->type == MotionNotify
+ )
+ )
+ {
+ SalMouseEvent aEvt;
+ int dest_x, dest_y;
+ ::Window aChild = None;
+ XTranslateCoordinates( pEvent->xbutton.display,
+ pEvent->xbutton.root,
+ pObject->maParentWin,
+ pEvent->xbutton.x_root,
+ pEvent->xbutton.y_root,
+ &dest_x, &dest_y,
+ &aChild );
+ aEvt.mnX = dest_x;
+ aEvt.mnY = dest_y;
+ aEvt.mnTime = pEvent->xbutton.time;
+ aEvt.mnCode = sal_GetCode( pEvent->xbutton.state );
+ aEvt.mnButton = 0;
+ SalEvent nEvent = SalEvent::NONE;
+ if( pEvent->type == ButtonPress ||
+ pEvent->type == ButtonRelease )
+ {
+ switch( pEvent->xbutton.button )
+ {
+ case Button1: aEvt.mnButton = MOUSE_LEFT;break;
+ case Button2: aEvt.mnButton = MOUSE_MIDDLE;break;
+ case Button3: aEvt.mnButton = MOUSE_RIGHT;break;
+ }
+ nEvent = (pEvent->type == ButtonPress) ?
+ SalEvent::MouseButtonDown :
+ SalEvent::MouseButtonUp;
+ }
+ else if( pEvent->type == EnterNotify )
+ nEvent = SalEvent::MouseLeave;
+ else
+ nEvent = SalEvent::MouseMove;
+ pObject->mpParent->CallCallback( nEvent, &aEvt );
+ }
+ else
+ {
+ switch( pEvent->type )
+ {
+ case UnmapNotify:
+ pObject->mbVisible = false;
+ return true;
+ case MapNotify:
+ pObject->mbVisible = true;
+ return true;
+ case ButtonPress:
+ pObject->CallCallback( SalObjEvent::ToTop );
+ return true;
+ case FocusIn:
+ pObject->CallCallback( SalObjEvent::GetFocus );
+ return true;
+ case FocusOut:
+ pObject->CallCallback( SalObjEvent::LoseFocus );
+ return true;
+ default: break;
+ }
+ }
+ return false;
+ }
+ }
+ return false;
+}
+
+void X11SalObject::SetLeaveEnterBackgrounds(const css::uno::Sequence<css::uno::Any>& rLeaveArgs, const css::uno::Sequence<css::uno::Any>& rEnterArgs)
+{
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ Display* pDisp = pSalDisp->GetDisplay();
+ ::Window aObjectParent = maParentWin;
+
+ bool bFreePixmap = false;
+ Pixmap aPixmap = None;
+ if (rEnterArgs.getLength() == 3)
+ {
+ rEnterArgs[0] >>= bFreePixmap;
+ sal_Int64 pixmapHandle = None;
+ rEnterArgs[1] >>= pixmapHandle;
+ aPixmap = pixmapHandle;
+ }
+
+ XSetWindowBackgroundPixmap(pDisp, aObjectParent, aPixmap);
+ if (bFreePixmap)
+ XFreePixmap(pDisp, aPixmap);
+
+ bFreePixmap = false;
+ aPixmap = None;
+ if (rLeaveArgs.getLength() == 3)
+ {
+ rLeaveArgs[0] >>= bFreePixmap;
+ sal_Int64 pixmapHandle = None;
+ rLeaveArgs[1] >>= pixmapHandle;
+ aPixmap = pixmapHandle;
+ }
+
+ XSetWindowBackgroundPixmap(pDisp, maSecondary, aPixmap);
+ if (bFreePixmap)
+ XFreePixmap(pDisp, aPixmap);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/window/screensaverinhibitor.cxx b/vcl/unx/generic/window/screensaverinhibitor.cxx
new file mode 100644
index 000000000..36eac26c2
--- /dev/null
+++ b/vcl/unx/generic/window/screensaverinhibitor.cxx
@@ -0,0 +1,370 @@
+/* -*- 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 <functional>
+
+#include <unx/gensys.h>
+#include <unx/screensaverinhibitor.hxx>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#if !defined(__sun) && !defined(AIX)
+#include <X11/extensions/dpms.h>
+#endif
+
+#include <config_gio.h>
+
+#if ENABLE_GIO
+#include <gio/gio.h>
+
+#define FDO_DBUS_SERVICE "org.freedesktop.ScreenSaver"
+#define FDO_DBUS_PATH "/org/freedesktop/ScreenSaver"
+#define FDO_DBUS_INTERFACE "org.freedesktop.ScreenSaver"
+
+#define FDOPM_DBUS_SERVICE "org.freedesktop.PowerManagement.Inhibit"
+#define FDOPM_DBUS_PATH "/org/freedesktop/PowerManagement/Inhibit"
+#define FDOPM_DBUS_INTERFACE "org.freedesktop.PowerManagement.Inhibit"
+
+#define GSM_DBUS_SERVICE "org.gnome.SessionManager"
+#define GSM_DBUS_PATH "/org/gnome/SessionManager"
+#define GSM_DBUS_INTERFACE "org.gnome.SessionManager"
+
+// Mate <= 1.10 uses org.mate.SessionManager, > 1.10 will use org.gnome.SessionManager
+#define MSM_DBUS_SERVICE "org.mate.SessionManager"
+#define MSM_DBUS_PATH "/org/mate/SessionManager"
+#define MSM_DBUS_INTERFACE "org.mate.SessionManager"
+#endif
+
+#include <sal/log.hxx>
+
+void ScreenSaverInhibitor::inhibit( bool bInhibit, std::u16string_view sReason,
+ bool bIsX11, const std::optional<unsigned int>& xid, std::optional<Display*> pDisplay )
+{
+ const char* appname = SalGenericSystem::getFrameClassName();
+ const OString aReason = OUStringToOString( sReason, RTL_TEXTENCODING_UTF8 );
+
+ inhibitFDO( bInhibit, appname, aReason.getStr() );
+ inhibitFDOPM( bInhibit, appname, aReason.getStr() );
+
+ if ( !bIsX11 )
+ return;
+
+ if (pDisplay)
+ {
+ inhibitXScreenSaver( bInhibit, *pDisplay );
+ inhibitXAutoLock( bInhibit, *pDisplay );
+ inhibitDPMS( bInhibit, *pDisplay );
+ }
+
+ if (xid)
+ {
+ inhibitGSM( bInhibit, appname, aReason.getStr(), *xid );
+ inhibitMSM( bInhibit, appname, aReason.getStr(), *xid );
+ }
+}
+
+#if ENABLE_GIO
+static void dbusInhibit( bool bInhibit,
+ const gchar* service, const gchar* path, const gchar* interface,
+ const std::function<GVariant*( GDBusProxy*, GError*& )>& fInhibit,
+ const std::function<GVariant*( GDBusProxy*, const guint, GError*& )>& fUnInhibit,
+ std::optional<guint>& rCookie )
+{
+ if ( ( !bInhibit && !rCookie ) ||
+ ( bInhibit && rCookie ) )
+ {
+ return;
+ }
+
+ GError *error = nullptr;
+ GDBusConnection *session_connection = g_bus_get_sync( G_BUS_TYPE_SESSION, nullptr, &error );
+ if (session_connection == nullptr) {
+ SAL_WARN( "vcl.screensaverinhibitor", "failed to connect to dbus session bus" );
+
+ if (error != nullptr) {
+ SAL_WARN( "vcl.screensaverinhibitor", "Error: " << error->message );
+ g_error_free( error );
+ }
+
+ return;
+ }
+
+ GDBusProxy *proxy = g_dbus_proxy_new_sync( session_connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ nullptr,
+ service,
+ path,
+ interface,
+ nullptr,
+ nullptr );
+
+ g_object_unref( G_OBJECT( session_connection ) );
+
+ if (proxy == nullptr) {
+ SAL_INFO( "vcl.screensaverinhibitor", "could not get dbus proxy: " << service );
+ return;
+ }
+
+ GVariant *res = nullptr;
+
+ if ( bInhibit )
+ {
+ res = fInhibit( proxy, error );
+
+ if (res != nullptr)
+ {
+ guint nCookie;
+
+ g_variant_get(res, "(u)", &nCookie);
+ g_variant_unref(res);
+
+ rCookie = nCookie;
+ }
+ else
+ {
+ SAL_INFO( "vcl.screensaverinhibitor", service << ".Inhibit failed");
+ }
+ }
+ else
+ {
+ res = fUnInhibit( proxy, *rCookie, error );
+ rCookie.reset();
+
+ if (res != nullptr)
+ {
+ g_variant_unref(res);
+ }
+ else
+ {
+ SAL_INFO( "vcl.screensaverinhibitor", service << ".UnInhibit failed" );
+ }
+ }
+
+ if (error != nullptr)
+ {
+ SAL_INFO( "vcl.screensaverinhibitor", "Error: " << error->message );
+ g_error_free( error );
+ }
+
+ g_object_unref( G_OBJECT( proxy ) );
+}
+#endif // ENABLE_GIO
+
+void ScreenSaverInhibitor::inhibitFDO( bool bInhibit, const char* appname, const char* reason )
+{
+#if ENABLE_GIO
+ dbusInhibit( bInhibit,
+ FDO_DBUS_SERVICE, FDO_DBUS_PATH, FDO_DBUS_INTERFACE,
+ [appname, reason] ( GDBusProxy *proxy, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "Inhibit",
+ g_variant_new("(ss)", appname, reason),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error );
+ },
+ [] ( GDBusProxy *proxy, const guint nCookie, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "UnInhibit",
+ g_variant_new("(u)", nCookie),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error );
+ },
+ mnFDOCookie );
+#else
+ (void) this;
+ (void) bInhibit;
+ (void) appname;
+ (void) reason;
+#endif // ENABLE_GIO
+}
+
+void ScreenSaverInhibitor::inhibitFDOPM( bool bInhibit, const char* appname, const char* reason )
+{
+#if ENABLE_GIO
+ dbusInhibit( bInhibit,
+ FDOPM_DBUS_SERVICE, FDOPM_DBUS_PATH, FDOPM_DBUS_INTERFACE,
+ [appname, reason] ( GDBusProxy *proxy, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "Inhibit",
+ g_variant_new("(ss)", appname, reason),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error );
+ },
+ [] ( GDBusProxy *proxy, const guint nCookie, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "UnInhibit",
+ g_variant_new("(u)", nCookie),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error );
+ },
+ mnFDOPMCookie );
+#else
+ (void) this;
+ (void) bInhibit;
+ (void) appname;
+ (void) reason;
+#endif // ENABLE_GIO
+}
+
+void ScreenSaverInhibitor::inhibitGSM( bool bInhibit, const char* appname, const char* reason, const unsigned int xid )
+{
+#if ENABLE_GIO
+ dbusInhibit( bInhibit,
+ GSM_DBUS_SERVICE, GSM_DBUS_PATH, GSM_DBUS_INTERFACE,
+ [appname, reason, xid] ( GDBusProxy *proxy, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "Inhibit",
+ g_variant_new("(susu)",
+ appname,
+ xid,
+ reason,
+ 8 //Inhibit the session being marked as idle
+ ),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error );
+ },
+ [] ( GDBusProxy *proxy, const guint nCookie, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "Uninhibit",
+ g_variant_new("(u)", nCookie),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error );
+ },
+ mnGSMCookie );
+#else
+ (void) this;
+ (void) bInhibit;
+ (void) appname;
+ (void) reason;
+ (void) xid;
+#endif // ENABLE_GIO
+}
+
+void ScreenSaverInhibitor::inhibitMSM( bool bInhibit, const char* appname, const char* reason, const unsigned int xid )
+{
+#if ENABLE_GIO
+ dbusInhibit( bInhibit,
+ MSM_DBUS_SERVICE, MSM_DBUS_PATH, MSM_DBUS_INTERFACE,
+ [appname, reason, xid] ( GDBusProxy *proxy, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "Inhibit",
+ g_variant_new("(susu)",
+ appname,
+ xid,
+ reason,
+ 8 //Inhibit the session being marked as idle
+ ),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error );
+ },
+ [] ( GDBusProxy *proxy, const guint nCookie, GError*& error ) -> GVariant* {
+ return g_dbus_proxy_call_sync( proxy, "Uninhibit",
+ g_variant_new("(u)", nCookie),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error );
+ },
+ mnMSMCookie );
+#else
+ (void) this;
+ (void) bInhibit;
+ (void) appname;
+ (void) reason;
+ (void) xid;
+#endif // ENABLE_GIO
+}
+
+/**
+ * Disable screensavers using the XSetScreenSaver/XGetScreenSaver API.
+ *
+ * Worth noting: xscreensaver explicitly ignores this and does its own
+ * timeout handling.
+ */
+void ScreenSaverInhibitor::inhibitXScreenSaver( bool bInhibit, Display* pDisplay )
+{
+ int nTimeout, nInterval, bPreferBlanking, bAllowExposures;
+ XGetScreenSaver( pDisplay, &nTimeout, &nInterval,
+ &bPreferBlanking, &bAllowExposures );
+
+ // To disable/reenable we simply fiddle the timeout, whilst
+ // retaining all other properties.
+ if ( bInhibit && nTimeout)
+ {
+ mnXScreenSaverTimeout = nTimeout;
+ XResetScreenSaver( pDisplay );
+ XSetScreenSaver( pDisplay, 0, nInterval,
+ bPreferBlanking, bAllowExposures );
+ }
+ else if ( !bInhibit && mnXScreenSaverTimeout )
+ {
+ XSetScreenSaver( pDisplay, *mnXScreenSaverTimeout,
+ nInterval, bPreferBlanking,
+ bAllowExposures );
+ mnXScreenSaverTimeout.reset();
+ }
+}
+
+
+/* definitions from xautolock.c (pl15) */
+#define XAUTOLOCK_DISABLE 1
+#define XAUTOLOCK_ENABLE 2
+
+void ScreenSaverInhibitor::inhibitXAutoLock( bool bInhibit, Display* pDisplay )
+{
+ ::Window aRootWindow = RootWindowOfScreen( ScreenOfDisplay( pDisplay, 0 ) );
+
+ Atom nAtom = XInternAtom( pDisplay,
+ "XAUTOLOCK_MESSAGE",
+ False );
+
+ if ( nAtom == None )
+ {
+ return;
+ }
+
+ int nMessage = bInhibit ? XAUTOLOCK_DISABLE : XAUTOLOCK_ENABLE;
+
+ XChangeProperty( pDisplay,
+ aRootWindow,
+ nAtom,
+ XA_INTEGER,
+ 8, // format -- 8 bit quantity
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>( &nMessage ),
+ sizeof( nMessage ) );
+}
+
+void ScreenSaverInhibitor::inhibitDPMS( bool bInhibit, Display* pDisplay )
+{
+#if !defined(__sun) && !defined(AIX)
+ int dummy;
+ // This won't change while X11 is running, hence
+ // we can evaluate only once and store as static
+ static bool bDPMSExtensionAvailable = ( DPMSQueryExtension( pDisplay, &dummy, &dummy) != 0 );
+
+ if ( !bDPMSExtensionAvailable )
+ {
+ return;
+ }
+
+ if ( bInhibit )
+ {
+ CARD16 state; // unused by us
+ DPMSInfo( pDisplay, &state, &mbDPMSWasEnabled );
+
+ if ( mbDPMSWasEnabled )
+ {
+ DPMSGetTimeouts( pDisplay,
+ &mnDPMSStandbyTimeout,
+ &mnDPMSSuspendTimeout,
+ &mnDPMSOffTimeout );
+ DPMSSetTimeouts( pDisplay,
+ 0,
+ 0,
+ 0 );
+ }
+ }
+ else if ( !bInhibit && mbDPMSWasEnabled )
+ {
+ DPMSSetTimeouts( pDisplay,
+ mnDPMSStandbyTimeout,
+ mnDPMSSuspendTimeout,
+ mnDPMSOffTimeout );
+ }
+#endif // !defined(__sun) && !defined(AIX)
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/TODO b/vcl/unx/gtk3/a11y/TODO
new file mode 100644
index 000000000..1048bd96e
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/TODO
@@ -0,0 +1,49 @@
+cws 'atkbridge'
+#Issue number: i#47890#
+Submitted by: mmeeks
+
+Hacked up prototype of atk bridge
+
+
+Serious problems
+ + Threading/locking:
+ + incoming CORBA calls & the GDK lock
+ + how are these being processed & on what thread ?
+ + are we holding the GDK_THREADS lock ?
+ + can we even do that ?
+ + is it really necessary to be thread safe ?
+ + how does this work in combination with the (unsafe) GAIL code ?
+ + what should incoming CORBA calls be doing ?
+ + esp. since we can't tell if they're coming from
+ in-proc or not either [ though this is unlikely ]
+
+
+Test:
+ + in-line text editing, does the TEXT_CHANGED signal get it right,
+ + why not copy/paste/delete etc. ?
+ + check vs. writer & other bits ...
+ + AtkSelection
+ + AtkHyper*
+
+* At-poke
+ + implement non-gui mode - for to-console event logging
+ + logging
+ + more detail from remaining events
+ + add a Tree navigation thing instead (?)
+ + poke a sub-child (?)
+ + embed a tree widget inside the tree view ?
+ + AtkHyperText testing (?)
+
+
+Known bugs:
+ + AtkText
+ + selection interface - multiple selections ?
+ + word boundary issues
+ + copy AccessibleTextImpl.java's getAfterIndex eg.
+ + the 'getFoo' methods need to use UNO_QUERY_THROW &
+ throw an exception to avoid null pointer dereferences.
+ + AtkAttributeSet (etc.)
+ + AtkEditableText
+ + finish/test AtkTable
+ + HyperLink 'link_activated', HyperText 'link_selected' (?)
+ + tooltips create new toplevels with broken roles.
diff --git a/vcl/unx/gtk3/a11y/atkaction.cxx b/vcl/unx/gtk3/a11y/atkaction.cxx
new file mode 100644
index 000000000..0a39d3bdf
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkaction.cxx
@@ -0,0 +1,269 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleAction.hpp>
+#include <com/sun/star/accessibility/XAccessibleKeyBinding.hpp>
+
+#include <com/sun/star/awt/Key.hpp>
+#include <com/sun/star/awt/KeyModifier.hpp>
+
+#include <rtl/strbuf.hxx>
+#include <algorithm>
+#include <map>
+
+using namespace ::com::sun::star;
+
+// FIXME
+static const gchar *
+getAsConst( const OString& rString )
+{
+ static const int nMax = 10;
+ static OString aUgly[nMax];
+ static int nIdx = 0;
+ nIdx = (nIdx + 1) % nMax;
+ aUgly[nIdx] = rString;
+ return aUgly[ nIdx ].getStr();
+}
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleAction>
+ getAction( AtkAction *action )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( action );
+
+ if( pWrap )
+ {
+ if( !pWrap->mpAction.is() )
+ {
+ pWrap->mpAction.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpAction;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleAction>();
+}
+
+extern "C" {
+
+static gboolean
+action_wrapper_do_action (AtkAction *action,
+ gint i)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleAction> pAction
+ = getAction( action );
+ if( pAction.is() )
+ return pAction->doAccessibleAction( i );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in doAccessibleAction()" );
+ }
+
+ return FALSE;
+}
+
+static gint
+action_wrapper_get_n_actions (AtkAction *action)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleAction> pAction
+ = getAction( action );
+ if( pAction.is() )
+ return pAction->getAccessibleActionCount();
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleActionCount()" );
+ }
+
+ return 0;
+}
+
+static const gchar *
+action_wrapper_get_description (AtkAction *, gint)
+{
+ // GAIL implement this only for cells
+ g_warning( "Not implemented: get_description()" );
+ return "";
+}
+
+static const gchar *
+action_wrapper_get_localized_name (AtkAction *, gint)
+{
+ // GAIL doesn't implement this as well
+ g_warning( "Not implemented: get_localized_name()" );
+ return "";
+}
+
+static const gchar *
+action_wrapper_get_name (AtkAction *action,
+ gint i)
+{
+ static std::map< OUString, const gchar * > aNameMap {
+ { "click", "click" },
+ { "select", "click" } ,
+ { "togglePopup", "push" }
+ };
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleAction> pAction
+ = getAction( action );
+ if( pAction.is() )
+ {
+ std::map< OUString, const gchar * >::iterator iter;
+
+ OUString aDesc( pAction->getAccessibleActionDescription( i ) );
+
+ iter = aNameMap.find( aDesc );
+ if( iter != aNameMap.end() )
+ return iter->second;
+
+ std::pair< const OUString, const gchar * > aNewVal( aDesc,
+ g_strdup( OUStringToConstGChar(aDesc) ) );
+
+ if( aNameMap.insert( aNewVal ).second )
+ return aNewVal.second;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleActionDescription()" );
+ }
+
+ return "";
+}
+
+/*
+* GNOME Expects a string in the format:
+*
+* <mnemonic>;<full-path>;<accelerator>
+*
+* The keybindings in <full-path> should be separated by ":"
+*/
+
+static void
+appendKeyStrokes(OStringBuffer& rBuffer, const uno::Sequence< awt::KeyStroke >& rKeyStrokes)
+{
+ for( const auto& rKeyStroke : rKeyStrokes )
+ {
+ if( rKeyStroke.Modifiers & awt::KeyModifier::SHIFT )
+ rBuffer.append("<Shift>");
+ if( rKeyStroke.Modifiers & awt::KeyModifier::MOD1 )
+ rBuffer.append("<Control>");
+ if( rKeyStroke.Modifiers & awt::KeyModifier::MOD2 )
+ rBuffer.append("<Alt>");
+
+ if( ( rKeyStroke.KeyCode >= awt::Key::A ) && ( rKeyStroke.KeyCode <= awt::Key::Z ) )
+ rBuffer.append( static_cast<char>( 'a' + ( rKeyStroke.KeyCode - awt::Key::A ) ) );
+ else
+ {
+ char c = '\0';
+
+ switch( rKeyStroke.KeyCode )
+ {
+ case awt::Key::TAB: c = '\t'; break;
+ case awt::Key::SPACE: c = ' '; break;
+ case awt::Key::ADD: c = '+'; break;
+ case awt::Key::SUBTRACT: c = '-'; break;
+ case awt::Key::MULTIPLY: c = '*'; break;
+ case awt::Key::DIVIDE: c = '/'; break;
+ case awt::Key::POINT: c = '.'; break;
+ case awt::Key::COMMA: c = ','; break;
+ case awt::Key::LESS: c = '<'; break;
+ case awt::Key::GREATER: c = '>'; break;
+ case awt::Key::EQUAL: c = '='; break;
+ case 0:
+ break;
+ default:
+ g_warning( "Unmapped KeyCode: %d", rKeyStroke.KeyCode );
+ break;
+ }
+
+ if( c != '\0' )
+ rBuffer.append( c );
+ else
+ {
+ // The KeyCode approach did not work, probably a non ascii character
+ // let's hope that there is a character given in KeyChar.
+ rBuffer.append(OUStringToOString(OUStringChar(rKeyStroke.KeyChar), RTL_TEXTENCODING_UTF8));
+ }
+ }
+ }
+}
+
+static const gchar *
+action_wrapper_get_keybinding (AtkAction *action,
+ gint i)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleAction> pAction
+ = getAction( action );
+ if( pAction.is() )
+ {
+ uno::Reference< accessibility::XAccessibleKeyBinding > xBinding( pAction->getAccessibleActionKeyBinding( i ));
+
+ if( xBinding.is() )
+ {
+ OStringBuffer aRet;
+
+ sal_Int32 nmax = std::min( xBinding->getAccessibleKeyBindingCount(), sal_Int32(3) );
+ for( sal_Int32 n = 0; n < nmax; n++ )
+ {
+ appendKeyStrokes( aRet, xBinding->getAccessibleKeyBinding( n ) );
+
+ if( n < 2 )
+ aRet.append( ';' );
+ }
+
+ // !! FIXME !! remember keystroke in wrapper object ?
+ return getAsConst( aRet.makeStringAndClear() );
+ }
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in get_keybinding()" );
+ }
+
+ return "";
+}
+
+static gboolean
+action_wrapper_set_description (AtkAction *, gint, const gchar *)
+{
+ return FALSE;
+}
+
+} // extern "C"
+
+void
+actionIfaceInit (AtkActionIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->do_action = action_wrapper_do_action;
+ iface->get_n_actions = action_wrapper_get_n_actions;
+ iface->get_description = action_wrapper_get_description;
+ iface->get_keybinding = action_wrapper_get_keybinding;
+ iface->get_name = action_wrapper_get_name;
+ iface->get_localized_name = action_wrapper_get_localized_name;
+ iface->set_description = action_wrapper_set_description;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkbridge.cxx b/vcl/unx/gtk3/a11y/atkbridge.cxx
new file mode 100644
index 000000000..c7cb32ca3
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkbridge.cxx
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unx/gtk/atkbridge.hxx>
+
+#include "atkutil.hxx"
+
+bool InitAtkBridge()
+{
+ ooo_atk_util_ensure_event_listener();
+ return true;
+}
+
+void DeInitAtkBridge() {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkcomponent.cxx b/vcl/unx/gtk3/a11y/atkcomponent.cxx
new file mode 100644
index 000000000..154f0117f
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkcomponent.cxx
@@ -0,0 +1,431 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "atkwrapper.hxx"
+#include <com/sun/star/accessibility/XAccessibleComponent.hpp>
+#include <sal/log.hxx>
+#include <gtk/gtk.h>
+
+using namespace ::com::sun::star;
+
+static AtkObjectWrapper* getObjectWrapper(AtkComponent *pComponent)
+{
+ AtkObjectWrapper *pWrap = nullptr;
+ if (ATK_IS_OBJECT_WRAPPER(pComponent))
+ pWrap = ATK_OBJECT_WRAPPER(pComponent);
+ else if (GTK_IS_DRAWING_AREA(pComponent)) //when using a GtkDrawingArea as a custom widget in welded gtk3
+ {
+ GtkWidget* pDrawingArea = GTK_WIDGET(pComponent);
+ AtkObject* pAtkObject = gtk_widget_get_accessible(pDrawingArea);
+ pWrap = ATK_IS_OBJECT_WRAPPER(pAtkObject) ? ATK_OBJECT_WRAPPER(pAtkObject) : nullptr;
+ }
+ return pWrap;
+}
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleComponent>
+ getComponent(AtkObjectWrapper *pWrap)
+{
+ if (pWrap)
+ {
+ if (!pWrap->mpComponent.is())
+ pWrap->mpComponent.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ return pWrap->mpComponent;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleComponent>();
+}
+
+static awt::Point
+lcl_getLocationInWindow(AtkComponent* pAtkComponent,
+ css::uno::Reference<accessibility::XAccessibleComponent> const& xComponent)
+{
+ // calculate position in window by adding the component's position in the parent
+ // to the parent's position in the window (unless parent is a window itself)
+ awt::Point aPos = xComponent->getLocation();
+ AtkObject* pParent = atk_object_get_parent(ATK_OBJECT(pAtkComponent));
+ if (ATK_IS_COMPONENT(pParent) && pParent->role != AtkRole::ATK_ROLE_DIALOG
+ && pParent->role != AtkRole::ATK_ROLE_FILE_CHOOSER
+ && pParent->role != AtkRole::ATK_ROLE_FRAME
+ && pParent->role != AtkRole::ATK_ROLE_WINDOW)
+ {
+ int nX;
+ int nY;
+ atk_component_get_extents(ATK_COMPONENT(pParent), &nX, &nY, nullptr, nullptr, ATK_XY_WINDOW);
+ aPos.X += nX;
+ aPos.Y += nY;
+ }
+
+ return aPos;
+}
+
+/*****************************************************************************/
+
+static awt::Point
+translatePoint( AtkComponent* pAtkComponent,
+ css::uno::Reference<accessibility::XAccessibleComponent> const & pComponent,
+ gint x, gint y, AtkCoordType t)
+{
+ awt::Point aOrigin( 0, 0 );
+ if( t == ATK_XY_SCREEN )
+ aOrigin = pComponent->getLocationOnScreen();
+ else if (t == ATK_XY_WINDOW)
+ aOrigin = lcl_getLocationInWindow(pAtkComponent, pComponent);
+ return awt::Point( x - aOrigin.X, y - aOrigin.Y );
+}
+
+/*****************************************************************************/
+
+extern "C" {
+
+static gboolean
+component_wrapper_grab_focus (AtkComponent *component)
+{
+ AtkObjectWrapper* obj = getObjectWrapper(component);
+ //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
+ if (obj && obj->mpOrig)
+ return atk_component_grab_focus(ATK_COMPONENT(obj->mpOrig));
+
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
+ = getComponent(obj);
+ if( pComponent.is() )
+ {
+ pComponent->grabFocus();
+ return true;
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ g_warning( "Exception in grabFocus()" );
+ }
+
+ return FALSE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+component_wrapper_contains (AtkComponent *component,
+ gint x,
+ gint y,
+ AtkCoordType coord_type)
+{
+ AtkObjectWrapper* obj = getObjectWrapper(component);
+ //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
+ if (obj && obj->mpOrig)
+ return atk_component_contains(ATK_COMPONENT(obj->mpOrig), x, y, coord_type);
+
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
+ = getComponent(obj);
+ if( pComponent.is() )
+ return pComponent->containsPoint(
+ translatePoint(component, pComponent, x, y, coord_type));
+ }
+ catch( const uno::Exception & )
+ {
+ g_warning( "Exception in containsPoint()" );
+ }
+
+ return FALSE;
+}
+
+/*****************************************************************************/
+
+static AtkObject *
+component_wrapper_ref_accessible_at_point (AtkComponent *component,
+ gint x,
+ gint y,
+ AtkCoordType coord_type)
+{
+ AtkObjectWrapper* obj = getObjectWrapper(component);
+ //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
+ if (obj && obj->mpOrig)
+ return atk_component_ref_accessible_at_point(ATK_COMPONENT(obj->mpOrig), x, y, coord_type);
+
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
+ = getComponent(obj);
+
+ if( pComponent.is() )
+ {
+ uno::Reference< accessibility::XAccessible > xAccessible = pComponent->getAccessibleAtPoint(
+ translatePoint(component, pComponent, x, y, coord_type));
+ return atk_object_wrapper_ref( xAccessible );
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ g_warning( "Exception in getAccessibleAtPoint()" );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+static void
+component_wrapper_get_position (AtkComponent *component,
+ gint *x,
+ gint *y,
+ AtkCoordType coord_type)
+{
+ AtkObjectWrapper* obj = getObjectWrapper(component);
+ //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
+ if (obj && obj->mpOrig)
+ {
+ atk_component_get_extents(ATK_COMPONENT(obj->mpOrig), x, y, nullptr, nullptr, coord_type);
+ return;
+ }
+
+ *x = *y = -1;
+
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
+ = getComponent(obj);
+ if( pComponent.is() )
+ {
+ awt::Point aPos;
+
+ if( coord_type == ATK_XY_SCREEN )
+ aPos = pComponent->getLocationOnScreen();
+ else if (coord_type == ATK_XY_WINDOW)
+ aPos = lcl_getLocationInWindow(component, pComponent);
+#if ATK_CHECK_VERSION(2, 30, 0)
+ else if (coord_type == ATK_XY_PARENT)
+#else
+ // ATK_XY_PARENT added in ATK 2.30, so can't use the constant here
+ else
+#endif
+ aPos = pComponent->getLocation();
+#if ATK_CHECK_VERSION(2, 30, 0)
+ else
+ {
+ SAL_WARN("vcl.gtk",
+ "component_wrapper_get_position called with unknown AtkCoordType "
+ << coord_type);
+ return;
+ }
+#endif
+
+ *x = aPos.X;
+ *y = aPos.Y;
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ g_warning( "Exception in getLocation[OnScreen]()" );
+ }
+}
+
+/*****************************************************************************/
+
+static void
+component_wrapper_get_size (AtkComponent *component,
+ gint *width,
+ gint *height)
+{
+ AtkObjectWrapper* obj = getObjectWrapper(component);
+ //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
+ if (obj && obj->mpOrig)
+ {
+ atk_component_get_extents(ATK_COMPONENT(obj->mpOrig), nullptr, nullptr, width, height, ATK_XY_WINDOW);
+ return;
+ }
+
+ *width = *height = -1;
+
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleComponent> pComponent
+ = getComponent(obj);
+ if( pComponent.is() )
+ {
+ awt::Size aSize = pComponent->getSize();
+ *width = aSize.Width;
+ *height = aSize.Height;
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ g_warning( "Exception in getSize()" );
+ }
+}
+
+/*****************************************************************************/
+
+static void
+component_wrapper_get_extents (AtkComponent *component,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height,
+ AtkCoordType coord_type)
+{
+ component_wrapper_get_position( component, x, y, coord_type );
+ component_wrapper_get_size( component, width, height );
+}
+
+/*****************************************************************************/
+
+static gboolean
+component_wrapper_set_extents (AtkComponent *, gint, gint, gint, gint, AtkCoordType)
+{
+ g_warning( "AtkComponent::set_extents unimplementable" );
+ return FALSE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+component_wrapper_set_position (AtkComponent *, gint, gint, AtkCoordType)
+{
+ g_warning( "AtkComponent::set_position unimplementable" );
+ return FALSE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+component_wrapper_set_size (AtkComponent *, gint, gint)
+{
+ g_warning( "AtkComponent::set_size unimplementable" );
+ return FALSE;
+}
+
+/*****************************************************************************/
+
+static AtkLayer
+component_wrapper_get_layer (AtkComponent *component)
+{
+ AtkRole role = atk_object_get_role( ATK_OBJECT( component ) );
+ AtkLayer layer = ATK_LAYER_WIDGET;
+
+ switch (role)
+ {
+ case ATK_ROLE_POPUP_MENU:
+ case ATK_ROLE_MENU_ITEM:
+ case ATK_ROLE_CHECK_MENU_ITEM:
+ case ATK_ROLE_SEPARATOR:
+ case ATK_ROLE_LIST_ITEM:
+ layer = ATK_LAYER_POPUP;
+ break;
+ case ATK_ROLE_MENU:
+ {
+ AtkObject * parent = atk_object_get_parent( ATK_OBJECT( component ) );
+ if( atk_object_get_role( parent ) != ATK_ROLE_MENU_BAR )
+ layer = ATK_LAYER_POPUP;
+ }
+ break;
+
+ case ATK_ROLE_LIST:
+ {
+ AtkObject * parent = atk_object_get_parent( ATK_OBJECT( component ) );
+ if( atk_object_get_role( parent ) == ATK_ROLE_COMBO_BOX )
+ layer = ATK_LAYER_POPUP;
+ }
+ break;
+
+ default:
+ ;
+ }
+
+ return layer;
+}
+
+/*****************************************************************************/
+
+static gint
+component_wrapper_get_mdi_zorder (AtkComponent *)
+{
+ // only needed for ATK_LAYER_MDI (not used) or ATK_LAYER_WINDOW (inherited from GAIL)
+ return G_MININT;
+}
+
+/*****************************************************************************/
+
+// This code is mostly stolen from libgail ..
+
+static guint
+component_wrapper_add_focus_handler (AtkComponent *component,
+ AtkFocusHandler handler)
+{
+ GSignalMatchType match_type;
+ gulong ret;
+ guint signal_id;
+
+ match_type = GSignalMatchType(G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_FUNC);
+ signal_id = g_signal_lookup( "focus-event", ATK_TYPE_OBJECT );
+
+ ret = g_signal_handler_find( component, match_type, signal_id, 0, nullptr,
+ static_cast<gpointer>(&handler), nullptr);
+ if (!ret)
+ {
+ return g_signal_connect_closure_by_id (component,
+ signal_id, 0,
+ g_cclosure_new (
+ G_CALLBACK (handler), nullptr,
+ nullptr),
+ FALSE);
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+/*****************************************************************************/
+
+static void
+component_wrapper_remove_focus_handler (AtkComponent *component,
+ guint handler_id)
+{
+ g_signal_handler_disconnect (component, handler_id);
+}
+
+/*****************************************************************************/
+
+} // extern "C"
+
+void
+componentIfaceInit (AtkComponentIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->add_focus_handler = component_wrapper_add_focus_handler;
+ iface->contains = component_wrapper_contains;
+ iface->get_extents = component_wrapper_get_extents;
+ iface->get_layer = component_wrapper_get_layer;
+ iface->get_mdi_zorder = component_wrapper_get_mdi_zorder;
+ iface->get_position = component_wrapper_get_position;
+ iface->get_size = component_wrapper_get_size;
+ iface->grab_focus = component_wrapper_grab_focus;
+ iface->ref_accessible_at_point = component_wrapper_ref_accessible_at_point;
+ iface->remove_focus_handler = component_wrapper_remove_focus_handler;
+ iface->set_extents = component_wrapper_set_extents;
+ iface->set_position = component_wrapper_set_position;
+ iface->set_size = component_wrapper_set_size;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkeditabletext.cxx b/vcl/unx/gtk3/a11y/atkeditabletext.cxx
new file mode 100644
index 000000000..f240c3232
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkeditabletext.cxx
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "atkwrapper.hxx"
+#include "atktextattributes.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+
+#include <string.h>
+
+using namespace ::com::sun::star;
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ getEditableText( AtkEditableText *pEditableText )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pEditableText );
+ if( pWrap )
+ {
+ if( !pWrap->mpEditableText.is() )
+ {
+ pWrap->mpEditableText.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpEditableText;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleEditableText>();
+}
+
+/*****************************************************************************/
+
+extern "C" {
+
+static gboolean
+editable_text_wrapper_set_run_attributes( AtkEditableText *text,
+ AtkAttributeSet *attribute_set,
+ gint nStartOffset,
+ gint nEndOffset)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ pEditableText = getEditableText( text );
+ if( pEditableText.is() )
+ {
+ uno::Sequence< beans::PropertyValue > aAttributeList;
+
+ if( attribute_set_map_to_property_values( attribute_set, aAttributeList ) )
+ return pEditableText->setAttributes(nStartOffset, nEndOffset, aAttributeList);
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in setAttributes()" );
+ }
+
+ return FALSE;
+}
+
+static void
+editable_text_wrapper_set_text_contents( AtkEditableText *text,
+ const gchar *string )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ pEditableText = getEditableText( text );
+ if( pEditableText.is() )
+ {
+ OUString aString ( string, strlen(string), RTL_TEXTENCODING_UTF8 );
+ pEditableText->setText( aString );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in setText()" );
+ }
+}
+
+static void
+editable_text_wrapper_insert_text( AtkEditableText *text,
+ const gchar *string,
+ gint length,
+ gint *pos )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ pEditableText = getEditableText( text );
+ if( pEditableText.is() )
+ {
+ OUString aString ( string, length, RTL_TEXTENCODING_UTF8 );
+ if( pEditableText->insertText( aString, *pos ) )
+ *pos += length;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in insertText()" );
+ }
+}
+
+static void
+editable_text_wrapper_cut_text( AtkEditableText *text,
+ gint start,
+ gint end )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ pEditableText = getEditableText( text );
+ if( pEditableText.is() )
+ pEditableText->cutText( start, end );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in cutText()" );
+ }
+}
+
+static void
+editable_text_wrapper_delete_text( AtkEditableText *text,
+ gint start,
+ gint end )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ pEditableText = getEditableText( text );
+ if( pEditableText.is() )
+ pEditableText->deleteText( start, end );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in deleteText()" );
+ }
+}
+
+static void
+editable_text_wrapper_paste_text( AtkEditableText *text,
+ gint pos )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ pEditableText = getEditableText( text );
+ if( pEditableText.is() )
+ pEditableText->pasteText( pos );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in pasteText()" );
+ }
+}
+
+static void
+editable_text_wrapper_copy_text( AtkEditableText *text,
+ gint start,
+ gint end )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ pEditableText = getEditableText( text );
+ if( pEditableText.is() )
+ pEditableText->copyText( start, end );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in copyText()" );
+ }
+}
+
+} // extern "C"
+
+void
+editableTextIfaceInit (AtkEditableTextIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->set_text_contents = editable_text_wrapper_set_text_contents;
+ iface->insert_text = editable_text_wrapper_insert_text;
+ iface->copy_text = editable_text_wrapper_copy_text;
+ iface->cut_text = editable_text_wrapper_cut_text;
+ iface->delete_text = editable_text_wrapper_delete_text;
+ iface->paste_text = editable_text_wrapper_paste_text;
+ iface->set_run_attributes = editable_text_wrapper_set_run_attributes;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkfactory.cxx b/vcl/unx/gtk3/a11y/atkfactory.cxx
new file mode 100644
index 000000000..2fc407b7b
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkfactory.cxx
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unx/gtk/gtkframe.hxx>
+#include <vcl/window.hxx>
+#include "atkwrapper.hxx"
+#include "atkfactory.hxx"
+#include "atkregistry.hxx"
+
+using namespace ::com::sun::star;
+
+extern "C" {
+
+/*
+ * Instances of this dummy object class are returned whenever we have to
+ * create an AtkObject, but can't touch the OOo object anymore since it
+ * is already disposed.
+ */
+
+static AtkStateSet *
+noop_wrapper_ref_state_set( AtkObject * )
+{
+ AtkStateSet *state_set = atk_state_set_new();
+ atk_state_set_add_state( state_set, ATK_STATE_DEFUNCT );
+ return state_set;
+}
+
+static void
+atk_noop_object_wrapper_class_init(AtkNoOpObjectClass *klass)
+{
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS( klass );
+ atk_class->ref_state_set = noop_wrapper_ref_state_set;
+}
+
+static GType
+atk_noop_object_wrapper_get_type()
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ static const GTypeInfo typeInfo =
+ {
+ sizeof (AtkNoOpObjectClass),
+ nullptr,
+ nullptr,
+ reinterpret_cast<GClassInitFunc>(atk_noop_object_wrapper_class_init),
+ nullptr,
+ nullptr,
+ sizeof (AtkObjectWrapper),
+ 0,
+ nullptr,
+ nullptr
+ } ;
+
+ type = g_type_register_static (ATK_TYPE_OBJECT, "OOoAtkNoOpObj", &typeInfo, GTypeFlags(0)) ;
+ }
+ return type;
+}
+
+static AtkObject*
+atk_noop_object_wrapper_new()
+{
+ AtkObject *accessible;
+
+ accessible = static_cast<AtkObject *>(g_object_new (atk_noop_object_wrapper_get_type(), nullptr));
+ g_return_val_if_fail (accessible != nullptr, nullptr);
+
+ accessible->role = ATK_ROLE_INVALID;
+ accessible->layer = ATK_LAYER_INVALID;
+
+ return accessible;
+}
+
+/*
+ * The wrapper factory
+ */
+
+static GType
+wrapper_factory_get_accessible_type()
+{
+ return atk_object_wrapper_get_type();
+}
+
+static AtkObject*
+wrapper_factory_create_accessible( GObject *obj )
+{
+ GtkWidget* pEventBox = gtk_widget_get_parent(GTK_WIDGET(obj));
+
+ // gail_container_real_remove_gtk tries to re-instantiate an accessible
+ // for a widget that is about to vanish ..
+ if (!pEventBox)
+ return atk_noop_object_wrapper_new();
+
+ GtkWidget* pTopLevelGrid = gtk_widget_get_parent(pEventBox);
+ if (!pTopLevelGrid)
+ return atk_noop_object_wrapper_new();
+
+ GtkWidget* pTopLevel = gtk_widget_get_parent(pTopLevelGrid);
+ if (!pTopLevel)
+ return atk_noop_object_wrapper_new();
+
+ GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pTopLevel);
+ g_return_val_if_fail(pFrame != nullptr, atk_noop_object_wrapper_new());
+
+ vcl::Window* pFrameWindow = pFrame->GetWindow();
+ if( pFrameWindow )
+ {
+ vcl::Window* pWindow = pFrameWindow;
+
+ // skip accessible objects already exposed by the frame objects
+ if( WindowType::BORDERWINDOW == pWindow->GetType() )
+ pWindow = pFrameWindow->GetAccessibleChildWindow(0);
+
+ if( pWindow )
+ {
+ uno::Reference< accessibility::XAccessible > xAccessible = pWindow->GetAccessible();
+ if( xAccessible.is() )
+ {
+ AtkObject *accessible = ooo_wrapper_registry_get( xAccessible );
+
+ if( accessible )
+ g_object_ref( G_OBJECT(accessible) );
+ else
+ accessible = atk_object_wrapper_new( xAccessible, gtk_widget_get_accessible(pTopLevel) );
+
+ return accessible;
+ }
+ }
+ }
+
+ return atk_noop_object_wrapper_new();
+}
+
+AtkObject* ooo_fixed_get_accessible(GtkWidget *obj)
+{
+ return wrapper_factory_create_accessible(G_OBJECT(obj));
+}
+
+static void
+wrapper_factory_class_init( AtkObjectFactoryClass *klass )
+{
+ klass->create_accessible = wrapper_factory_create_accessible;
+ klass->get_accessible_type = wrapper_factory_get_accessible_type;
+}
+
+GType
+wrapper_factory_get_type()
+{
+ static GType t = 0;
+
+ if (!t) {
+ static const GTypeInfo tinfo =
+ {
+ sizeof (AtkObjectFactoryClass),
+ nullptr, nullptr, reinterpret_cast<GClassInitFunc>(wrapper_factory_class_init),
+ nullptr, nullptr, sizeof (AtkObjectFactory), 0, nullptr, nullptr
+ };
+
+ t = g_type_register_static (
+ ATK_TYPE_OBJECT_FACTORY, "OOoAtkObjectWrapperFactory",
+ &tinfo, GTypeFlags(0));
+ }
+
+ return t;
+}
+
+} // extern C
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkfactory.hxx b/vcl/unx/gtk3/a11y/atkfactory.hxx
new file mode 100644
index 000000000..66c52eab7
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkfactory.hxx
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <atk/atk.h>
+
+extern "C" {
+
+GType wrapper_factory_get_type();
+
+} // extern "C"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkhypertext.cxx b/vcl/unx/gtk3/a11y/atkhypertext.cxx
new file mode 100644
index 000000000..83f6817fc
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkhypertext.cxx
@@ -0,0 +1,277 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleHypertext.hpp>
+
+using namespace ::com::sun::star;
+
+// ---------------------- AtkHyperlink ----------------------
+
+namespace {
+
+struct HyperLink {
+ AtkHyperlink const atk_hyper_link;
+
+ uno::Reference< accessibility::XAccessibleHyperlink > xLink;
+};
+
+}
+
+static uno::Reference< accessibility::XAccessibleHyperlink > const &
+ getHyperlink( AtkHyperlink *pHyperlink )
+{
+ HyperLink *pLink = reinterpret_cast<HyperLink *>(pHyperlink);
+ return pLink->xLink;
+}
+
+static GObjectClass *hyper_parent_class = nullptr;
+
+extern "C" {
+
+static void
+hyper_link_finalize (GObject *obj)
+{
+ HyperLink *hl = reinterpret_cast<HyperLink *>(obj);
+ hl->xLink.clear();
+ hyper_parent_class->finalize (obj);
+}
+
+static gchar *
+hyper_link_get_uri( AtkHyperlink *pLink,
+ gint i )
+{
+ try {
+ uno::Any aAny = getHyperlink( pLink )->getAccessibleActionObject( i );
+ OUString aUri = aAny.get< OUString > ();
+ return OUStringToGChar(aUri);
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in hyper_link_get_uri" );
+ }
+ return nullptr;
+}
+
+static AtkObject *
+hyper_link_get_object( AtkHyperlink *,
+ gint )
+{
+ g_warning( "FIXME: hyper_link_get_object unimplemented" );
+ return nullptr;
+}
+static gint
+hyper_link_get_end_index( AtkHyperlink *pLink )
+{
+ try {
+ return getHyperlink( pLink )->getEndIndex();
+ }
+ catch(const uno::Exception&) {
+ }
+ return -1;
+}
+static gint
+hyper_link_get_start_index( AtkHyperlink *pLink )
+{
+ try {
+ return getHyperlink( pLink )->getStartIndex();
+ }
+ catch(const uno::Exception&) {
+ }
+ return -1;
+}
+static gboolean
+hyper_link_is_valid( AtkHyperlink *pLink )
+{
+ try {
+ return getHyperlink( pLink )->isValid();
+ }
+ catch(const uno::Exception&) {
+ }
+ return FALSE;
+}
+static gint
+hyper_link_get_n_anchors( AtkHyperlink *pLink )
+{
+ try {
+ return getHyperlink( pLink )->getAccessibleActionCount();
+ }
+ catch(const uno::Exception&) {
+ }
+ return 0;
+}
+
+static guint
+hyper_link_link_state( AtkHyperlink * )
+{
+ g_warning( "FIXME: hyper_link_link_state unimplemented" );
+ return 0;
+}
+static gboolean
+hyper_link_is_selected_link( AtkHyperlink * )
+{
+ g_warning( "FIXME: hyper_link_is_selected_link unimplemented" );
+ return FALSE;
+}
+
+static void
+hyper_link_class_init (AtkHyperlinkClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = hyper_link_finalize;
+
+ hyper_parent_class = static_cast<GObjectClass *>(g_type_class_peek_parent (klass));
+
+ klass->get_uri = hyper_link_get_uri;
+ klass->get_object = hyper_link_get_object;
+ klass->get_end_index = hyper_link_get_end_index;
+ klass->get_start_index = hyper_link_get_start_index;
+ klass->is_valid = hyper_link_is_valid;
+ klass->get_n_anchors = hyper_link_get_n_anchors;
+ klass->link_state = hyper_link_link_state;
+ klass->is_selected_link = hyper_link_is_selected_link;
+}
+
+static GType
+hyper_link_get_type()
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo tinfo = {
+ sizeof (AtkHyperlinkClass),
+ nullptr, /* base init */
+ nullptr, /* base finalize */
+ reinterpret_cast<GClassInitFunc>(hyper_link_class_init),
+ nullptr, /* class finalize */
+ nullptr, /* class data */
+ sizeof (HyperLink), /* instance size */
+ 0, /* nb preallocs */
+ nullptr, /* instance init */
+ nullptr /* value table */
+ };
+
+ static const GInterfaceInfo atk_action_info = {
+ reinterpret_cast<GInterfaceInitFunc>(actionIfaceInit),
+ nullptr,
+ nullptr
+ };
+
+ type = g_type_register_static (ATK_TYPE_HYPERLINK,
+ "OOoAtkObjHyperLink", &tinfo,
+ GTypeFlags(0));
+ g_type_add_interface_static (type, ATK_TYPE_ACTION,
+ &atk_action_info);
+ }
+
+ return type;
+}
+
+// ---------------------- AtkHyperText ----------------------
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleHypertext>
+ getHypertext( AtkHypertext *pHypertext )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pHypertext );
+ if( pWrap )
+ {
+ if( !pWrap->mpHypertext.is() )
+ {
+ pWrap->mpHypertext.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpHypertext;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleHypertext>();
+}
+
+static AtkHyperlink *
+hypertext_get_link( AtkHypertext *hypertext,
+ gint link_index)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleHypertext> pHypertext
+ = getHypertext( hypertext );
+ if( pHypertext.is() )
+ {
+ HyperLink *pLink = static_cast<HyperLink *>(g_object_new( hyper_link_get_type(), nullptr ));
+ pLink->xLink = pHypertext->getHyperLink( link_index );
+ if( !pLink->xLink.is() ) {
+ g_object_unref( G_OBJECT( pLink ) );
+ pLink = nullptr;
+ }
+ return ATK_HYPERLINK( pLink );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getHyperLink()" );
+ }
+
+ return nullptr;
+}
+
+static gint
+hypertext_get_n_links( AtkHypertext *hypertext )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleHypertext> pHypertext
+ = getHypertext( hypertext );
+ if( pHypertext.is() )
+ return pHypertext->getHyperLinkCount();
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getHyperLinkCount()" );
+ }
+
+ return 0;
+}
+
+static gint
+hypertext_get_link_index( AtkHypertext *hypertext,
+ gint index)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleHypertext> pHypertext
+ = getHypertext( hypertext );
+ if( pHypertext.is() )
+ return pHypertext->getHyperLinkIndex( index );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getHyperLinkIndex()" );
+ }
+
+ return 0;
+}
+
+} // extern "C"
+
+void
+hypertextIfaceInit (AtkHypertextIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->get_link = hypertext_get_link;
+ iface->get_n_links = hypertext_get_n_links;
+ iface->get_link_index = hypertext_get_link_index;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkimage.cxx b/vcl/unx/gtk3/a11y/atkimage.cxx
new file mode 100644
index 000000000..da4965b86
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkimage.cxx
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleImage.hpp>
+
+using namespace ::com::sun::star;
+
+// FIXME
+static const gchar *
+getAsConst( std::u16string_view rString )
+{
+ static const int nMax = 10;
+ static OString aUgly[nMax];
+ static int nIdx = 0;
+ nIdx = (nIdx + 1) % nMax;
+ aUgly[nIdx] = OUStringToOString( rString, RTL_TEXTENCODING_UTF8 );
+ return aUgly[ nIdx ].getStr();
+}
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleImage>
+ getImage( AtkImage *pImage )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pImage );
+ if( pWrap )
+ {
+ if( !pWrap->mpImage.is() )
+ {
+ pWrap->mpImage.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpImage;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleImage>();
+}
+
+extern "C" {
+
+static const gchar *
+image_get_image_description( AtkImage *image )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleImage> pImage
+ = getImage( image );
+ if( pImage.is() )
+ return getAsConst( pImage->getAccessibleImageDescription() );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleImageDescription()" );
+ }
+
+ return nullptr;
+}
+
+static void
+image_get_image_position( AtkImage *image,
+ gint *x,
+ gint *y,
+ AtkCoordType coord_type )
+{
+ *x = *y = -1;
+ if( ATK_IS_COMPONENT( image ) )
+ {
+ gint nWidth = -1;
+ gint nHeight = -1;
+ atk_component_get_extents(ATK_COMPONENT(image), x, y, &nWidth, &nHeight, coord_type);
+ }
+ else
+ g_warning( "FIXME: no image position information" );
+}
+
+static void
+image_get_image_size( AtkImage *image,
+ gint *width,
+ gint *height )
+{
+ *width = *height = -1;
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleImage> pImage
+ = getImage( image );
+ if( pImage.is() )
+ {
+ *width = pImage->getAccessibleImageWidth();
+ *height = pImage->getAccessibleImageHeight();
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleImageHeight() or Width" );
+ }
+}
+
+static gboolean
+image_set_image_description( AtkImage *, const gchar * )
+{
+ g_warning ("FIXME: no set image description");
+ return FALSE;
+}
+
+} // extern "C"
+
+void
+imageIfaceInit (AtkImageIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->set_image_description = image_set_image_description;
+ iface->get_image_description = image_get_image_description;
+ iface->get_image_position = image_get_image_position;
+ iface->get_image_size = image_get_image_size;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atklistener.cxx b/vcl/unx/gtk3/a11y/atklistener.cxx
new file mode 100644
index 000000000..f0c2504c9
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atklistener.cxx
@@ -0,0 +1,736 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifdef AIX
+#define _LINUX_SOURCE_COMPAT
+#include <sys/timer.h>
+#undef _LINUX_SOURCE_COMPAT
+#endif
+
+#include <com/sun/star/accessibility/TextSegment.hpp>
+#include <com/sun/star/accessibility/AccessibleEventId.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp>
+#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp>
+#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
+#include <com/sun/star/accessibility/XAccessibleContext3.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+
+#include "atklistener.hxx"
+#include "atkwrapper.hxx"
+#include <comphelper/sequence.hxx>
+#include <vcl/svapp.hxx>
+
+#include <sal/log.hxx>
+
+#define DEBUG_ATK_LISTENER 0
+
+#if DEBUG_ATK_LISTENER
+#include <iostream>
+#include <sstream>
+#endif
+
+using namespace com::sun::star;
+
+AtkListener::AtkListener( AtkObjectWrapper* pWrapper ) : mpWrapper( pWrapper )
+{
+ if( mpWrapper )
+ {
+ g_object_ref( mpWrapper );
+ updateChildList( mpWrapper->mpContext );
+ }
+}
+
+AtkListener::~AtkListener()
+{
+ if( mpWrapper )
+ g_object_unref( mpWrapper );
+}
+
+/*****************************************************************************/
+
+static AtkStateType mapState( const uno::Any &rAny )
+{
+ sal_Int16 nState = accessibility::AccessibleStateType::INVALID;
+ rAny >>= nState;
+ return mapAtkState( nState );
+}
+
+/*****************************************************************************/
+
+extern "C" {
+ // rhbz#1001768 - down to horrific problems releasing the solar mutex
+ // while destroying a Window - which occurs inside these notifications.
+ static gboolean
+ idle_defunc_state_change( AtkObject *atk_obj )
+ {
+ SolarMutexGuard aGuard;
+
+ // This is an equivalent to a state change to DEFUNC(T).
+ atk_object_notify_state_change( atk_obj, ATK_STATE_DEFUNCT, true );
+ if( atk_get_focus_object() == atk_obj )
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ atk_focus_tracker_notify( nullptr );
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+ g_object_unref( G_OBJECT( atk_obj ) );
+ return false;
+ }
+}
+
+// XEventListener implementation
+void AtkListener::disposing( const lang::EventObject& )
+{
+ if( !mpWrapper )
+ return;
+
+ AtkObject *atk_obj = ATK_OBJECT( mpWrapper );
+
+ // Release all interface references to avoid shutdown problems with
+ // global mutex
+ atk_object_wrapper_dispose( mpWrapper );
+
+ g_idle_add( reinterpret_cast<GSourceFunc>(idle_defunc_state_change),
+ g_object_ref( G_OBJECT( atk_obj ) ) );
+
+ // Release the wrapper object so that it can vanish ..
+ g_object_unref( mpWrapper );
+ mpWrapper = nullptr;
+}
+
+/*****************************************************************************/
+
+static AtkObject *getObjFromAny( const uno::Any &rAny )
+{
+ uno::Reference< accessibility::XAccessible > xAccessible;
+ rAny >>= xAccessible;
+ return xAccessible.is() ? atk_object_wrapper_ref( xAccessible ) : nullptr;
+}
+
+/*****************************************************************************/
+
+// Updates the child list held to provide the old IndexInParent on children_changed::remove
+void AtkListener::updateChildList(
+ css::uno::Reference<css::accessibility::XAccessibleContext> const &
+ pContext)
+{
+ m_aChildList.clear();
+
+ uno::Reference< accessibility::XAccessibleStateSet > xStateSet = pContext->getAccessibleStateSet();
+ if( !xStateSet.is()
+ || xStateSet->contains(accessibility::AccessibleStateType::DEFUNC)
+ || xStateSet->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS) )
+ return;
+
+ css::uno::Reference<css::accessibility::XAccessibleContext3> xContext3(pContext, css::uno::UNO_QUERY);
+ if (xContext3.is())
+ {
+ m_aChildList = comphelper::sequenceToContainer<std::vector<css::uno::Reference< css::accessibility::XAccessible >>>(xContext3->getAccessibleChildren());
+ }
+ else
+ {
+ sal_Int32 nChildren = pContext->getAccessibleChildCount();
+ m_aChildList.resize(nChildren);
+ for(sal_Int32 n = 0; n < nChildren; n++)
+ {
+ try
+ {
+ m_aChildList[n] = pContext->getAccessibleChild(n);
+ }
+ catch (lang::IndexOutOfBoundsException const&)
+ {
+ sal_Int32 nChildren2 = pContext->getAccessibleChildCount();
+ assert(nChildren2 <= n && "consistency?");
+ m_aChildList.resize(std::min(nChildren2, n));
+ break;
+ }
+ }
+ }
+}
+
+/*****************************************************************************/
+
+void AtkListener::handleChildAdded(
+ const uno::Reference< accessibility::XAccessibleContext >& rxParent,
+ const uno::Reference< accessibility::XAccessible>& rxAccessible)
+{
+ AtkObject * pChild = rxAccessible.is() ? atk_object_wrapper_ref( rxAccessible ) : nullptr;
+
+ if( pChild )
+ {
+ updateChildList(rxParent);
+
+ atk_object_wrapper_add_child( mpWrapper, pChild,
+ atk_object_get_index_in_parent( pChild ));
+
+ g_object_unref( pChild );
+ }
+}
+
+/*****************************************************************************/
+
+void AtkListener::handleChildRemoved(
+ const uno::Reference< accessibility::XAccessibleContext >& rxParent,
+ const uno::Reference< accessibility::XAccessible>& rxChild)
+{
+ sal_Int32 nIndex = -1;
+
+ // Locate the child in the children list
+ size_t n, nmax = m_aChildList.size();
+ for( n = 0; n < nmax; ++n )
+ {
+ if( rxChild == m_aChildList[n] )
+ {
+ nIndex = n;
+ break;
+ }
+ }
+
+ // FIXME: two problems here:
+ // a) we get child-removed events for objects that are no real children
+ // in the accessibility hierarchy or have been removed before due to
+ // some child removing batch.
+ // b) spi_atk_bridge_signal_listener ignores the given parameters
+ // for children_changed events and always asks the parent for the
+ // 0. child, which breaks somehow on vanishing list boxes.
+ // Ignoring "remove" events for objects not in the m_aChildList
+ // for now.
+ if( nIndex < 0 )
+ return;
+
+ uno::Reference<accessibility::XAccessibleEventBroadcaster> xBroadcaster(
+ rxChild->getAccessibleContext(), uno::UNO_QUERY);
+
+ if (xBroadcaster.is())
+ {
+ uno::Reference<accessibility::XAccessibleEventListener> xListener(this);
+ xBroadcaster->removeAccessibleEventListener(xListener);
+ }
+
+ updateChildList(rxParent);
+
+ AtkObject * pChild = atk_object_wrapper_ref( rxChild, false );
+ if( pChild )
+ {
+ atk_object_wrapper_remove_child( mpWrapper, pChild, nIndex );
+ g_object_unref( pChild );
+ }
+}
+
+/*****************************************************************************/
+
+void AtkListener::handleInvalidateChildren(
+ const uno::Reference< accessibility::XAccessibleContext >& rxParent)
+{
+ // Send notifications for all previous children
+ size_t n = m_aChildList.size();
+ while( n-- > 0 )
+ {
+ if( m_aChildList[n].is() )
+ {
+ AtkObject * pChild = atk_object_wrapper_ref( m_aChildList[n], false );
+ if( pChild )
+ {
+ atk_object_wrapper_remove_child( mpWrapper, pChild, n );
+ g_object_unref( pChild );
+ }
+ }
+ }
+
+ updateChildList(rxParent);
+
+ // Send notifications for all new children
+ size_t nmax = m_aChildList.size();
+ for( n = 0; n < nmax; ++n )
+ {
+ if( m_aChildList[n].is() )
+ {
+ AtkObject * pChild = atk_object_wrapper_ref( m_aChildList[n] );
+
+ if( pChild )
+ {
+ atk_object_wrapper_add_child( mpWrapper, pChild, n );
+ g_object_unref( pChild );
+ }
+ }
+ }
+}
+
+/*****************************************************************************/
+
+static uno::Reference< accessibility::XAccessibleContext >
+getAccessibleContextFromSource( const uno::Reference< uno::XInterface >& rxSource )
+{
+ uno::Reference< accessibility::XAccessibleContext > xContext(rxSource, uno::UNO_QUERY);
+ if( ! xContext.is() )
+ {
+ g_warning( "ERROR: Event source does not implement XAccessibleContext" );
+
+ // Second try - query for XAccessible, which should give us access to
+ // XAccessibleContext.
+ uno::Reference< accessibility::XAccessible > xAccessible(rxSource, uno::UNO_QUERY);
+ if( xAccessible.is() )
+ xContext = xAccessible->getAccessibleContext();
+ }
+
+ return xContext;
+}
+
+#if DEBUG_ATK_LISTENER
+
+namespace {
+
+void printNotifyEvent( const accessibility::AccessibleEventObject& rEvent )
+{
+ static std::vector<const char*> aLabels = {
+ 0,
+ "NAME_CHANGED", // 01
+ "DESCRIPTION_CHANGED", // 02
+ "ACTION_CHANGED", // 03
+ "STATE_CHANGED", // 04
+ "ACTIVE_DESCENDANT_CHANGED", // 05
+ "BOUNDRECT_CHANGED", // 06
+ "CHILD", // 07
+ "INVALIDATE_ALL_CHILDREN", // 08
+ "SELECTION_CHANGED", // 09
+ "VISIBLE_DATA_CHANGED", // 10
+ "VALUE_CHANGED", // 11
+ "CONTENT_FLOWS_FROM_RELATION_CHANGED", // 12
+ "CONTENT_FLOWS_TO_RELATION_CHANGED", // 13
+ "CONTROLLED_BY_RELATION_CHANGED", // 14
+ "CONTROLLER_FOR_RELATION_CHANGED", // 15
+ "LABEL_FOR_RELATION_CHANGED", // 16
+ "LABELED_BY_RELATION_CHANGED", // 17
+ "MEMBER_OF_RELATION_CHANGED", // 18
+ "SUB_WINDOW_OF_RELATION_CHANGED", // 19
+ "CARET_CHANGED", // 20
+ "TEXT_SELECTION_CHANGED", // 21
+ "TEXT_CHANGED", // 22
+ "TEXT_ATTRIBUTE_CHANGED", // 23
+ "HYPERTEXT_CHANGED", // 24
+ "TABLE_CAPTION_CHANGED", // 25
+ "TABLE_COLUMN_DESCRIPTION_CHANGED", // 26
+ "TABLE_COLUMN_HEADER_CHANGED", // 27
+ "TABLE_MODEL_CHANGED", // 28
+ "TABLE_ROW_DESCRIPTION_CHANGED", // 29
+ "TABLE_ROW_HEADER_CHANGED", // 30
+ "TABLE_SUMMARY_CHANGED", // 31
+ "LISTBOX_ENTRY_EXPANDED", // 32
+ "LISTBOX_ENTRY_COLLAPSED", // 33
+ "ACTIVE_DESCENDANT_CHANGED_NOFOCUS", // 34
+ "SELECTION_CHANGED_ADD", // 35
+ "SELECTION_CHANGED_REMOVE", // 36
+ "SELECTION_CHANGED_WITHIN", // 37
+ "PAGE_CHANGED", // 38
+ "SECTION_CHANGED", // 39
+ "COLUMN_CHANGED", // 40
+ "ROLE_CHANGED", // 41
+ };
+
+ static std::vector<const char*> aStates = {
+ "INVALID", // 00
+ "ACTIVE", // 01
+ "ARMED", // 02
+ "BUSY", // 03
+ "CHECKED", // 04
+ "DEFUNC", // 05
+ "EDITABLE", // 06
+ "ENABLED", // 07
+ "EXPANDABLE", // 08
+ "EXPANDED", // 09
+ "FOCUSABLE", // 10
+ "FOCUSED", // 11
+ "HORIZONTAL", // 12
+ "ICONIFIED", // 13
+ "INDETERMINATE", // 14
+ "MANAGES_DESCENDANTS", // 15
+ "MODAL", // 16
+ "MULTI_LINE", // 17
+ "MULTI_SELECTABLE", // 18
+ "OPAQUE", // 19
+ "PRESSED", // 20
+ "RESIZABLE", // 21
+ "SELECTABLE", // 22
+ "SELECTED", // 23
+ "SENSITIVE", // 24
+ "SHOWING", // 25
+ "SINGLE_LINE", // 26
+ "STALE", // 27
+ "TRANSIENT", // 28
+ "VERTICAL", // 29
+ "VISIBLE", // 30
+ "MOVEABLE", // 31
+ "DEFAULT", // 32
+ "OFFSCREEN", // 33
+ "COLLAPSE", // 34
+ };
+
+ auto getOrUnknown = [](const std::vector<const char*>& rCont, size_t nIndex) -> std::string
+ {
+ return (nIndex < rCont.size()) ? rCont[nIndex] : "<unknown>";
+ };
+
+ std::ostringstream os;
+ os << "--" << std::endl;
+ os << "* event = " << getOrUnknown(aLabels, rEvent.EventId) << std::endl;
+
+ switch (rEvent.EventId)
+ {
+ case accessibility::AccessibleEventId::STATE_CHANGED:
+ {
+ sal_Int16 nState;
+ if (rEvent.OldValue >>= nState)
+ os << " * old state = " << getOrUnknown(aStates, nState);
+ if (rEvent.NewValue >>= nState)
+ os << " * new state = " << getOrUnknown(aStates, nState);
+
+ os << std::endl;
+ break;
+ }
+ default:
+ ;
+ }
+
+ std::cout << os.str();
+}
+
+}
+
+#endif
+
+void AtkListener::notifyEvent( const accessibility::AccessibleEventObject& aEvent )
+{
+ if( !mpWrapper )
+ return;
+
+ AtkObject *atk_obj = ATK_OBJECT( mpWrapper );
+
+ switch( aEvent.EventId )
+ {
+ // AtkObject signals:
+ // Hierarchy signals
+ case accessibility::AccessibleEventId::CHILD:
+ {
+ uno::Reference< accessibility::XAccessibleContext > xParent;
+ uno::Reference< accessibility::XAccessible > xChild;
+
+ xParent = getAccessibleContextFromSource(aEvent.Source);
+ g_return_if_fail( xParent.is() );
+
+ if( aEvent.OldValue >>= xChild )
+ handleChildRemoved(xParent, xChild);
+
+ if( aEvent.NewValue >>= xChild )
+ handleChildAdded(xParent, xChild);
+ break;
+ }
+
+ case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN:
+ {
+ uno::Reference< accessibility::XAccessibleContext > xParent = getAccessibleContextFromSource(aEvent.Source);
+ g_return_if_fail( xParent.is() );
+
+ handleInvalidateChildren(xParent);
+ break;
+ }
+
+ case accessibility::AccessibleEventId::NAME_CHANGED:
+ {
+ OUString aName;
+ if( aEvent.NewValue >>= aName )
+ {
+ atk_object_set_name(atk_obj,
+ OUStringToOString(aName, RTL_TEXTENCODING_UTF8).getStr());
+ }
+ break;
+ }
+
+ case accessibility::AccessibleEventId::DESCRIPTION_CHANGED:
+ {
+ OUString aDescription;
+ if( aEvent.NewValue >>= aDescription )
+ {
+ atk_object_set_description(atk_obj,
+ OUStringToOString(aDescription, RTL_TEXTENCODING_UTF8).getStr());
+ }
+ break;
+ }
+
+ case accessibility::AccessibleEventId::STATE_CHANGED:
+ {
+ AtkStateType eOldState = mapState( aEvent.OldValue );
+ AtkStateType eNewState = mapState( aEvent.NewValue );
+
+ bool bState = eNewState != ATK_STATE_INVALID;
+ AtkStateType eRealState = bState ? eNewState : eOldState;
+
+ atk_object_notify_state_change( atk_obj, eRealState, bState );
+ break;
+ }
+
+ case accessibility::AccessibleEventId::BOUNDRECT_CHANGED:
+
+#ifdef HAS_ATKRECTANGLE
+ if( ATK_IS_COMPONENT( atk_obj ) )
+ {
+ AtkRectangle rect;
+
+ atk_component_get_extents( ATK_COMPONENT( atk_obj ),
+ &rect.x,
+ &rect.y,
+ &rect.width,
+ &rect.height,
+ ATK_XY_SCREEN );
+
+ g_signal_emit_by_name( atk_obj, "bounds_changed", &rect );
+ }
+ else
+ g_warning( "bounds_changed event for object not implementing AtkComponent\n");
+#endif
+
+ break;
+
+ case accessibility::AccessibleEventId::VISIBLE_DATA_CHANGED:
+ g_signal_emit_by_name( atk_obj, "visible-data-changed" );
+ break;
+
+ case accessibility::AccessibleEventId::ACTIVE_DESCENDANT_CHANGED:
+ {
+ AtkObject *pChild = getObjFromAny( aEvent.NewValue );
+ if( pChild )
+ {
+ g_signal_emit_by_name( atk_obj, "active-descendant-changed", pChild );
+ g_object_unref( pChild );
+ }
+ break;
+ }
+
+ //ACTIVE_DESCENDANT_CHANGED_NOFOCUS (sic) appears to have been added
+ //as a workaround or an aid for the ia2 winaccessibility implementation
+ //so ignore it silently without warning here
+ case accessibility::AccessibleEventId::ACTIVE_DESCENDANT_CHANGED_NOFOCUS:
+ break;
+
+ // #i92103#
+ case accessibility::AccessibleEventId::LISTBOX_ENTRY_EXPANDED:
+ {
+ AtkObject *pChild = getObjFromAny( aEvent.NewValue );
+ if( pChild )
+ {
+ atk_object_notify_state_change( pChild, ATK_STATE_EXPANDED, true );
+ g_object_unref( pChild );
+ }
+ break;
+ }
+
+ case accessibility::AccessibleEventId::LISTBOX_ENTRY_COLLAPSED:
+ {
+ AtkObject *pChild = getObjFromAny( aEvent.NewValue );
+ if( pChild )
+ {
+ atk_object_notify_state_change( pChild, ATK_STATE_EXPANDED, false );
+ g_object_unref( pChild );
+ }
+ break;
+ }
+
+ // AtkAction signals ...
+ case accessibility::AccessibleEventId::ACTION_CHANGED:
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-actions");
+ break;
+
+ // AtkText
+ case accessibility::AccessibleEventId::CARET_CHANGED:
+ {
+ sal_Int32 nPos=0;
+ aEvent.NewValue >>= nPos;
+ g_signal_emit_by_name( atk_obj, "text_caret_moved", nPos );
+ break;
+ }
+ case accessibility::AccessibleEventId::TEXT_CHANGED:
+ {
+ // cf. comphelper/source/misc/accessibletexthelper.cxx (implInitTextChangedEvent)
+ accessibility::TextSegment aDeletedText;
+ accessibility::TextSegment aInsertedText;
+
+ if( aEvent.OldValue >>= aDeletedText )
+ {
+ const OString aDeletedTextUtf8 = OUStringToOString(aDeletedText.SegmentText, RTL_TEXTENCODING_UTF8);
+ g_signal_emit_by_name( atk_obj, "text-remove",
+ static_cast<gint>(aDeletedText.SegmentStart),
+ static_cast<gint>(aDeletedText.SegmentEnd - aDeletedText.SegmentStart),
+ g_strdup(aDeletedTextUtf8.getStr()));
+
+ }
+ if( aEvent.NewValue >>= aInsertedText )
+ {
+ const OString aInsertedTextUtf8 = OUStringToOString(aInsertedText.SegmentText, RTL_TEXTENCODING_UTF8);
+ g_signal_emit_by_name( atk_obj, "text-insert",
+ static_cast<gint>(aInsertedText.SegmentStart),
+ static_cast<gint>(aInsertedText.SegmentEnd - aInsertedText.SegmentStart),
+ g_strdup(aInsertedTextUtf8.getStr()));
+ }
+ break;
+ }
+
+ case accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED:
+ {
+ g_signal_emit_by_name( atk_obj, "text-selection-changed" );
+ break;
+ }
+
+ case accessibility::AccessibleEventId::TEXT_ATTRIBUTE_CHANGED:
+ g_signal_emit_by_name( atk_obj, "text-attributes-changed" );
+ break;
+
+ // AtkValue
+ case accessibility::AccessibleEventId::VALUE_CHANGED:
+ g_object_notify( G_OBJECT( atk_obj ), "accessible-value" );
+ break;
+
+ case accessibility::AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED:
+ case accessibility::AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED:
+ case accessibility::AccessibleEventId::CONTROLLED_BY_RELATION_CHANGED:
+ case accessibility::AccessibleEventId::CONTROLLER_FOR_RELATION_CHANGED:
+ case accessibility::AccessibleEventId::LABEL_FOR_RELATION_CHANGED:
+ case accessibility::AccessibleEventId::LABELED_BY_RELATION_CHANGED:
+ case accessibility::AccessibleEventId::MEMBER_OF_RELATION_CHANGED:
+ case accessibility::AccessibleEventId::SUB_WINDOW_OF_RELATION_CHANGED:
+ // FIXME: ask Bill how Atk copes with this little lot ...
+ break;
+
+ // AtkTable
+ case accessibility::AccessibleEventId::TABLE_MODEL_CHANGED:
+ {
+ accessibility::AccessibleTableModelChange aChange;
+ aEvent.NewValue >>= aChange;
+
+ sal_Int32 nRowsChanged = aChange.LastRow - aChange.FirstRow + 1;
+ sal_Int32 nColumnsChanged = aChange.LastColumn - aChange.FirstColumn + 1;
+
+ switch( aChange.Type )
+ {
+ case accessibility::AccessibleTableModelChangeType::COLUMNS_INSERTED:
+ g_signal_emit_by_name(G_OBJECT(atk_obj), "column-inserted",
+ aChange.FirstColumn, nColumnsChanged);
+ break;
+ case accessibility::AccessibleTableModelChangeType::COLUMNS_REMOVED:
+ g_signal_emit_by_name(G_OBJECT(atk_obj), "column-deleted",
+ aChange.FirstColumn, nColumnsChanged);
+ break;
+ case accessibility::AccessibleTableModelChangeType::ROWS_INSERTED:
+ g_signal_emit_by_name(G_OBJECT(atk_obj), "row-inserted",
+ aChange.FirstRow, nRowsChanged);
+ break;
+ case accessibility::AccessibleTableModelChangeType::ROWS_REMOVED:
+ g_signal_emit_by_name(G_OBJECT(atk_obj), "row-deleted",
+ aChange.FirstRow, nRowsChanged);
+ break;
+ case accessibility::AccessibleTableModelChangeType::UPDATE:
+ // This is not really a model change, is it ?
+ break;
+ default:
+ g_warning( "TESTME: unusual table model change %d\n", aChange.Type );
+ break;
+ }
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "model-changed" );
+ break;
+ }
+
+ case accessibility::AccessibleEventId::TABLE_COLUMN_HEADER_CHANGED:
+ {
+ accessibility::AccessibleTableModelChange aChange;
+ aEvent.NewValue >>= aChange;
+
+ AtkPropertyValues values;
+ memset(&values, 0, sizeof(AtkPropertyValues));
+ g_value_init (&values.new_value, G_TYPE_INT);
+ values.property_name = "accessible-table-column-header";
+
+ for (sal_Int32 nChangedColumn = aChange.FirstColumn; nChangedColumn <= aChange.LastColumn; ++nChangedColumn)
+ {
+ g_value_set_int (&values.new_value, nChangedColumn);
+ g_signal_emit_by_name(G_OBJECT(atk_obj), "property_change::accessible-table-column-header", &values, nullptr);
+ }
+ break;
+ }
+
+ case accessibility::AccessibleEventId::TABLE_CAPTION_CHANGED:
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-caption");
+ break;
+
+ case accessibility::AccessibleEventId::TABLE_COLUMN_DESCRIPTION_CHANGED:
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-column-description");
+ break;
+
+ case accessibility::AccessibleEventId::TABLE_ROW_DESCRIPTION_CHANGED:
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-row-description");
+ break;
+
+ case accessibility::AccessibleEventId::TABLE_ROW_HEADER_CHANGED:
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-row-header");
+ break;
+
+ case accessibility::AccessibleEventId::TABLE_SUMMARY_CHANGED:
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-summary");
+ break;
+
+ case accessibility::AccessibleEventId::SELECTION_CHANGED:
+ case accessibility::AccessibleEventId::SELECTION_CHANGED_ADD:
+ case accessibility::AccessibleEventId::SELECTION_CHANGED_REMOVE:
+ case accessibility::AccessibleEventId::SELECTION_CHANGED_WITHIN:
+ if (ATK_IS_SELECTION(atk_obj))
+ g_signal_emit_by_name(G_OBJECT(atk_obj), "selection_changed");
+ else
+ {
+ // e.g. tdf#122353, when such dialogs become native the problem will go away anyway
+ SAL_INFO("vcl.gtk", "selection change from obj which doesn't support XAccessibleSelection");
+ }
+ break;
+
+ case accessibility::AccessibleEventId::HYPERTEXT_CHANGED:
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-hypertext-offset");
+ break;
+
+ case accessibility::AccessibleEventId::ROLE_CHANGED:
+ {
+ uno::Reference< accessibility::XAccessibleContext > xContext = getAccessibleContextFromSource( aEvent.Source );
+ atk_object_wrapper_set_role( mpWrapper, xContext->getAccessibleRole() );
+ break;
+ }
+
+ case accessibility::AccessibleEventId::PAGE_CHANGED:
+ {
+ /* // If we implemented AtkDocument then I imagine this is what this
+ // handler should look like
+ sal_Int32 nPos=0;
+ aEvent.NewValue >>= nPos;
+ g_signal_emit_by_name( G_OBJECT( atk_obj ), "page_changed", nPos );
+ */
+ break;
+ }
+
+ default:
+ SAL_WARN("vcl.gtk", "Unknown event notification: " << aEvent.EventId);
+ break;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atklistener.hxx b/vcl/unx/gtk3/a11y/atklistener.hxx
new file mode 100644
index 000000000..7dcd78509
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atklistener.hxx
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/accessibility/XAccessibleEventListener.hpp>
+#include <cppuhelper/implbase.hxx>
+
+#include <vector>
+
+#include "atkwrapper.hxx"
+
+class AtkListener : public ::cppu::WeakImplHelper< css::accessibility::XAccessibleEventListener >
+{
+public:
+ explicit AtkListener(AtkObjectWrapper * pWrapper);
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+
+ // XAccessibleEventListener
+ virtual void SAL_CALL notifyEvent( const css::accessibility::AccessibleEventObject& aEvent ) override;
+
+private:
+
+ AtkObjectWrapper *mpWrapper;
+ std::vector< css::uno::Reference< css::accessibility::XAccessible > >
+ m_aChildList;
+
+ virtual ~AtkListener() override;
+
+ // Updates the child list held to provide the old IndexInParent on children_changed::remove
+ void updateChildList(
+ css::uno::Reference<css::accessibility::XAccessibleContext> const &
+ pContext);
+
+ // Process CHILD_EVENT notifications with a new child added
+ void handleChildAdded(
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& rxParent,
+ const css::uno::Reference< css::accessibility::XAccessible>& rxChild);
+
+ // Process CHILD_EVENT notifications with a child removed
+ void handleChildRemoved(
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& rxParent,
+ const css::uno::Reference< css::accessibility::XAccessible>& rxChild);
+
+ // Process INVALIDATE_ALL_CHILDREN notification
+ void handleInvalidateChildren(
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& rxParent);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkregistry.cxx b/vcl/unx/gtk3/a11y/atkregistry.cxx
new file mode 100644
index 000000000..ff96378c4
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkregistry.cxx
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "atkregistry.hxx"
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::uno;
+
+static GHashTable *uno_to_gobject = nullptr;
+
+/*****************************************************************************/
+
+AtkObject *
+ooo_wrapper_registry_get(const Reference< XAccessible >& rxAccessible)
+{
+ if( uno_to_gobject )
+ {
+ gpointer cached =
+ g_hash_table_lookup(uno_to_gobject, static_cast<gpointer>(rxAccessible.get()));
+
+ if( cached )
+ return ATK_OBJECT( cached );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+void
+ooo_wrapper_registry_add(const Reference< XAccessible >& rxAccessible, AtkObject *obj)
+{
+ if( !uno_to_gobject )
+ uno_to_gobject = g_hash_table_new (nullptr, nullptr);
+
+ g_hash_table_insert( uno_to_gobject, static_cast<gpointer>(rxAccessible.get()), obj );
+}
+
+/*****************************************************************************/
+
+void
+ooo_wrapper_registry_remove(
+ css::uno::Reference<css::accessibility::XAccessible> const & pAccessible)
+{
+ if( uno_to_gobject )
+ g_hash_table_remove(
+ uno_to_gobject, static_cast<gpointer>(pAccessible.get()) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkregistry.hxx b/vcl/unx/gtk3/a11y/atkregistry.hxx
new file mode 100644
index 000000000..b26b83840
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkregistry.hxx
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/accessibility/XAccessible.hpp>
+#include <atk/atk.h>
+
+AtkObject*
+ooo_wrapper_registry_get(const css::uno::Reference<css::accessibility::XAccessible>& rxAccessible);
+
+void ooo_wrapper_registry_add(
+ const css::uno::Reference<css::accessibility::XAccessible>& rxAccessible, AtkObject* obj);
+
+void ooo_wrapper_registry_remove(
+ css::uno::Reference<css::accessibility::XAccessible> const& pAccessible);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkselection.cxx b/vcl/unx/gtk3/a11y/atkselection.cxx
new file mode 100644
index 000000000..22d515f1b
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkselection.cxx
@@ -0,0 +1,196 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
+
+using namespace ::com::sun::star;
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleSelection>
+ getSelection( AtkSelection *pSelection )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pSelection );
+ if( pWrap )
+ {
+ if( !pWrap->mpSelection.is() )
+ {
+ pWrap->mpSelection.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpSelection;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleSelection>();
+}
+
+extern "C" {
+
+static gboolean
+selection_add_selection( AtkSelection *selection,
+ gint i )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
+ = getSelection( selection );
+ if( pSelection.is() )
+ {
+ pSelection->selectAccessibleChild( i );
+ return true;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in selectAccessibleChild()" );
+ }
+
+ return FALSE;
+}
+
+static gboolean
+selection_clear_selection( AtkSelection *selection )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
+ = getSelection( selection );
+ if( pSelection.is() )
+ {
+ pSelection->clearAccessibleSelection();
+ return true;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in selectAccessibleChild()" );
+ }
+
+ return FALSE;
+}
+
+static AtkObject*
+selection_ref_selection( AtkSelection *selection,
+ gint i )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
+ = getSelection( selection );
+ if( pSelection.is() )
+ return atk_object_wrapper_ref( pSelection->getSelectedAccessibleChild( i ) );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectedAccessibleChild()" );
+ }
+
+ return nullptr;
+}
+
+static gint
+selection_get_selection_count( AtkSelection *selection)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
+ = getSelection( selection );
+ if( pSelection.is() )
+ return pSelection->getSelectedAccessibleChildCount();
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectedAccessibleChildCount()" );
+ }
+
+ return -1;
+}
+
+static gboolean
+selection_is_child_selected( AtkSelection *selection,
+ gint i)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
+ = getSelection( selection );
+ if( pSelection.is() )
+ return pSelection->isAccessibleChildSelected( i );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectedAccessibleChildCount()" );
+ }
+
+ return FALSE;
+}
+
+static gboolean
+selection_remove_selection( AtkSelection *selection,
+ gint i )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
+ = getSelection( selection );
+ if( pSelection.is() )
+ {
+ css::uno::Reference<css::accessibility::XAccessible> xAcc = pSelection->getSelectedAccessibleChild(i);
+ if (!xAcc.is())
+ return false;
+
+ css::uno::Reference<css::accessibility::XAccessibleContext> xAccContext = xAcc->getAccessibleContext();
+ const sal_Int32 nChildIndex = xAccContext->getAccessibleIndexInParent();
+ pSelection->deselectAccessibleChild(nChildIndex);
+ return true;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectedAccessibleChildCount()" );
+ }
+
+ return FALSE;
+}
+
+static gboolean
+selection_select_all_selection( AtkSelection *selection)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleSelection> pSelection
+ = getSelection( selection );
+ if( pSelection.is() )
+ {
+ pSelection->selectAllAccessibleChildren();
+ return true;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectedAccessibleChildCount()" );
+ }
+
+ return FALSE;
+}
+
+} // extern "C"
+
+void
+selectionIfaceInit( AtkSelectionIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->add_selection = selection_add_selection;
+ iface->clear_selection = selection_clear_selection;
+ iface->ref_selection = selection_ref_selection;
+ iface->get_selection_count = selection_get_selection_count;
+ iface->is_child_selected = selection_is_child_selected;
+ iface->remove_selection = selection_remove_selection;
+ iface->select_all_selection = selection_select_all_selection;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atktable.cxx b/vcl/unx/gtk3/a11y/atktable.cxx
new file mode 100644
index 000000000..11df8a73c
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atktable.cxx
@@ -0,0 +1,584 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleTable.hpp>
+#include <comphelper/sequence.hxx>
+
+using namespace ::com::sun::star;
+
+static AtkObject *
+atk_object_wrapper_conditional_ref( const uno::Reference< accessibility::XAccessible >& rxAccessible )
+{
+ if( rxAccessible.is() )
+ return atk_object_wrapper_ref( rxAccessible );
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+// FIXME
+static const gchar *
+getAsConst( std::u16string_view rString )
+{
+ static const int nMax = 10;
+ static OString aUgly[nMax];
+ static int nIdx = 0;
+ nIdx = (nIdx + 1) % nMax;
+ aUgly[nIdx] = OUStringToOString( rString, RTL_TEXTENCODING_UTF8 );
+ return aUgly[ nIdx ].getStr();
+}
+
+/*****************************************************************************/
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleTable>
+ getTable( AtkTable *pTable )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pTable );
+ if( pWrap )
+ {
+ if( !pWrap->mpTable.is() )
+ {
+ pWrap->mpTable.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpTable;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleTable>();
+}
+
+/*****************************************************************************/
+
+extern "C" {
+
+static AtkObject*
+table_wrapper_ref_at (AtkTable *table,
+ gint row,
+ gint column)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable = getTable( table );
+ if( pTable.is() )
+ return atk_object_wrapper_conditional_ref( pTable->getAccessibleCellAt( row, column ) );
+ }
+
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleCellAt()" );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_index_at (AtkTable *table,
+ gint row,
+ gint column)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->getAccessibleIndex( row, column );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleIndex()" );
+ }
+
+ return -1;
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_column_at_index (AtkTable *table,
+ gint nIndex)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->getAccessibleColumn( nIndex );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleColumn()" );
+ }
+
+ return -1;
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_row_at_index( AtkTable *table,
+ gint nIndex )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->getAccessibleRow( nIndex );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleRow()" );
+ }
+
+ return -1;
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_n_columns( AtkTable *table )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->getAccessibleColumnCount();
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleColumnCount()" );
+ }
+
+ return -1;
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_n_rows( AtkTable *table )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->getAccessibleRowCount();
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleRowCount()" );
+ }
+
+ return -1;
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_column_extent_at( AtkTable *table,
+ gint row,
+ gint column )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->getAccessibleColumnExtentAt( row, column );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleColumnExtentAt()" );
+ }
+
+ return -1;
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_row_extent_at( AtkTable *table,
+ gint row,
+ gint column )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->getAccessibleRowExtentAt( row, column );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleRowExtentAt()" );
+ }
+
+ return -1;
+}
+
+/*****************************************************************************/
+
+static AtkObject *
+table_wrapper_get_caption( AtkTable *table )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return atk_object_wrapper_conditional_ref( pTable->getAccessibleCaption() );
+ }
+
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleCaption()" );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+static const gchar *
+table_wrapper_get_row_description( AtkTable *table,
+ gint row )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return getAsConst( pTable->getAccessibleRowDescription( row ) );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleRowDescription()" );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+static const gchar *
+table_wrapper_get_column_description( AtkTable *table,
+ gint column )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return getAsConst( pTable->getAccessibleColumnDescription( column ) );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleColumnDescription()" );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+static AtkObject *
+table_wrapper_get_row_header( AtkTable *table,
+ gint row )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ {
+ uno::Reference< accessibility::XAccessibleTable > xRowHeaders( pTable->getAccessibleRowHeaders() );
+ if( xRowHeaders.is() )
+ return atk_object_wrapper_conditional_ref( xRowHeaders->getAccessibleCellAt( row, 0 ) );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleRowHeaders()" );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+static AtkObject *
+table_wrapper_get_column_header( AtkTable *table,
+ gint column )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ {
+ uno::Reference< accessibility::XAccessibleTable > xColumnHeaders( pTable->getAccessibleColumnHeaders() );
+ if( xColumnHeaders.is() )
+ return atk_object_wrapper_conditional_ref( xColumnHeaders->getAccessibleCellAt( 0, column ) );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleColumnHeaders()" );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+static AtkObject *
+table_wrapper_get_summary( AtkTable *table )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ {
+ return atk_object_wrapper_conditional_ref( pTable->getAccessibleSummary() );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleSummary()" );
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+static gint
+convertToGIntArray( const uno::Sequence< ::sal_Int32 >& aSequence, gint **pSelected )
+{
+ if( aSequence.hasElements() )
+ {
+ *pSelected = g_new( gint, aSequence.getLength() );
+
+ *pSelected = comphelper::sequenceToArray(*pSelected, aSequence);
+ }
+
+ return aSequence.getLength();
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_selected_columns( AtkTable *table,
+ gint **pSelected )
+{
+ *pSelected = nullptr;
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return convertToGIntArray( pTable->getSelectedAccessibleColumns(), pSelected );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectedAccessibleColumns()" );
+ }
+
+ return 0;
+}
+
+/*****************************************************************************/
+
+static gint
+table_wrapper_get_selected_rows( AtkTable *table,
+ gint **pSelected )
+{
+ *pSelected = nullptr;
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return convertToGIntArray( pTable->getSelectedAccessibleRows(), pSelected );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectedAccessibleRows()" );
+ }
+
+ return 0;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_is_column_selected( AtkTable *table,
+ gint column )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->isAccessibleColumnSelected( column );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in isAccessibleColumnSelected()" );
+ }
+
+ return 0;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_is_row_selected( AtkTable *table,
+ gint row )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->isAccessibleRowSelected( row );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in isAccessibleRowSelected()" );
+ }
+
+ return FALSE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_is_selected( AtkTable *table,
+ gint row,
+ gint column )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTable> pTable
+ = getTable( table );
+ if( pTable.is() )
+ return pTable->isAccessibleSelected( row, column );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in isAccessibleSelected()" );
+ }
+
+ return FALSE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_add_row_selection( AtkTable *, gint )
+{
+ g_warning( "FIXME: no simple analogue for add_row_selection" );
+ return 0;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_remove_row_selection( AtkTable *, gint )
+{
+ g_warning( "FIXME: no simple analogue for remove_row_selection" );
+ return 0;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_add_column_selection( AtkTable *, gint )
+{
+ g_warning( "FIXME: no simple analogue for add_column_selection" );
+ return 0;
+}
+
+/*****************************************************************************/
+
+static gboolean
+table_wrapper_remove_column_selection( AtkTable *, gint )
+{
+ g_warning( "FIXME: no simple analogue for remove_column_selection" );
+ return 0;
+}
+
+/*****************************************************************************/
+
+static void
+table_wrapper_set_caption( AtkTable *, AtkObject * )
+{ // meaningless helper
+}
+
+/*****************************************************************************/
+
+static void
+table_wrapper_set_column_description( AtkTable *, gint, const gchar * )
+{ // meaningless helper
+}
+
+/*****************************************************************************/
+
+static void
+table_wrapper_set_column_header( AtkTable *, gint, AtkObject * )
+{ // meaningless helper
+}
+
+/*****************************************************************************/
+
+static void
+table_wrapper_set_row_description( AtkTable *, gint, const gchar * )
+{ // meaningless helper
+}
+
+/*****************************************************************************/
+
+static void
+table_wrapper_set_row_header( AtkTable *, gint, AtkObject * )
+{ // meaningless helper
+}
+
+/*****************************************************************************/
+
+static void
+table_wrapper_set_summary( AtkTable *, AtkObject * )
+{ // meaningless helper
+}
+
+/*****************************************************************************/
+
+} // extern "C"
+
+void
+tableIfaceInit (AtkTableIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->ref_at = table_wrapper_ref_at;
+ iface->get_n_rows = table_wrapper_get_n_rows;
+ iface->get_n_columns = table_wrapper_get_n_columns;
+ iface->get_index_at = table_wrapper_get_index_at;
+ iface->get_column_at_index = table_wrapper_get_column_at_index;
+ iface->get_row_at_index = table_wrapper_get_row_at_index;
+ iface->is_row_selected = table_wrapper_is_row_selected;
+ iface->is_selected = table_wrapper_is_selected;
+ iface->get_selected_rows = table_wrapper_get_selected_rows;
+ iface->add_row_selection = table_wrapper_add_row_selection;
+ iface->remove_row_selection = table_wrapper_remove_row_selection;
+ iface->add_column_selection = table_wrapper_add_column_selection;
+ iface->remove_column_selection = table_wrapper_remove_column_selection;
+ iface->get_selected_columns = table_wrapper_get_selected_columns;
+ iface->is_column_selected = table_wrapper_is_column_selected;
+ iface->get_column_extent_at = table_wrapper_get_column_extent_at;
+ iface->get_row_extent_at = table_wrapper_get_row_extent_at;
+ iface->get_row_header = table_wrapper_get_row_header;
+ iface->set_row_header = table_wrapper_set_row_header;
+ iface->get_column_header = table_wrapper_get_column_header;
+ iface->set_column_header = table_wrapper_set_column_header;
+ iface->get_caption = table_wrapper_get_caption;
+ iface->set_caption = table_wrapper_set_caption;
+ iface->get_summary = table_wrapper_get_summary;
+ iface->set_summary = table_wrapper_set_summary;
+ iface->get_row_description = table_wrapper_get_row_description;
+ iface->set_row_description = table_wrapper_set_row_description;
+ iface->get_column_description = table_wrapper_get_column_description;
+ iface->set_column_description = table_wrapper_set_column_description;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atktext.cxx b/vcl/unx/gtk3/a11y/atktext.cxx
new file mode 100644
index 000000000..b89853809
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atktext.cxx
@@ -0,0 +1,879 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "atkwrapper.hxx"
+#include "atktextattributes.hxx"
+#include <algorithm>
+
+#include <osl/diagnose.h>
+
+#include <com/sun/star/accessibility/AccessibleScrollType.hpp>
+#include <com/sun/star/accessibility/AccessibleTextType.hpp>
+#include <com/sun/star/accessibility/TextSegment.hpp>
+#include <com/sun/star/accessibility/XAccessibleMultiLineText.hpp>
+#include <com/sun/star/accessibility/XAccessibleText.hpp>
+#include <com/sun/star/accessibility/XAccessibleTextAttributes.hpp>
+#include <com/sun/star/accessibility/XAccessibleTextMarkup.hpp>
+#include <com/sun/star/lang/NoSupportException.hpp>
+#include <com/sun/star/text/TextMarkupType.hpp>
+
+using namespace ::com::sun::star;
+
+static sal_Int16
+text_type_from_boundary(AtkTextBoundary boundary_type)
+{
+ switch(boundary_type)
+ {
+ case ATK_TEXT_BOUNDARY_CHAR:
+ return accessibility::AccessibleTextType::CHARACTER;
+ case ATK_TEXT_BOUNDARY_WORD_START:
+ case ATK_TEXT_BOUNDARY_WORD_END:
+ return accessibility::AccessibleTextType::WORD;
+ case ATK_TEXT_BOUNDARY_SENTENCE_START:
+ case ATK_TEXT_BOUNDARY_SENTENCE_END:
+ return accessibility::AccessibleTextType::SENTENCE;
+ case ATK_TEXT_BOUNDARY_LINE_START:
+ case ATK_TEXT_BOUNDARY_LINE_END:
+ return accessibility::AccessibleTextType::LINE;
+ default:
+ return -1;
+ }
+}
+
+/*****************************************************************************/
+
+#if ATK_CHECK_VERSION(2,32,0)
+static accessibility::AccessibleScrollType
+scroll_type_from_scroll_type(AtkScrollType type)
+{
+ switch(type)
+ {
+ case ATK_SCROLL_TOP_LEFT:
+ return accessibility::AccessibleScrollType_SCROLL_TOP_LEFT;
+ case ATK_SCROLL_BOTTOM_RIGHT:
+ return accessibility::AccessibleScrollType_SCROLL_BOTTOM_RIGHT;
+ case ATK_SCROLL_TOP_EDGE:
+ return accessibility::AccessibleScrollType_SCROLL_TOP_EDGE;
+ case ATK_SCROLL_BOTTOM_EDGE:
+ return accessibility::AccessibleScrollType_SCROLL_BOTTOM_EDGE;
+ case ATK_SCROLL_LEFT_EDGE:
+ return accessibility::AccessibleScrollType_SCROLL_LEFT_EDGE;
+ case ATK_SCROLL_RIGHT_EDGE:
+ return accessibility::AccessibleScrollType_SCROLL_RIGHT_EDGE;
+ case ATK_SCROLL_ANYWHERE:
+ return accessibility::AccessibleScrollType_SCROLL_ANYWHERE;
+ default:
+ throw lang::NoSupportException();
+ }
+}
+#endif
+
+/*****************************************************************************/
+
+static gchar *
+adjust_boundaries( css::uno::Reference<css::accessibility::XAccessibleText> const & pText,
+ accessibility::TextSegment const & rTextSegment,
+ AtkTextBoundary boundary_type,
+ gint * start_offset, gint * end_offset )
+{
+ accessibility::TextSegment aTextSegment;
+ OUString aString;
+ gint start = 0, end = 0;
+
+ if( !rTextSegment.SegmentText.isEmpty() )
+ {
+ switch(boundary_type)
+ {
+ case ATK_TEXT_BOUNDARY_CHAR:
+ case ATK_TEXT_BOUNDARY_LINE_START:
+ case ATK_TEXT_BOUNDARY_LINE_END:
+ case ATK_TEXT_BOUNDARY_SENTENCE_START:
+ start = rTextSegment.SegmentStart;
+ end = rTextSegment.SegmentEnd;
+ aString = rTextSegment.SegmentText;
+ break;
+
+ // the OOo break iterator behaves as SENTENCE_START
+ case ATK_TEXT_BOUNDARY_SENTENCE_END:
+ start = rTextSegment.SegmentStart;
+ end = rTextSegment.SegmentEnd;
+
+ if( start > 0 )
+ --start;
+ if( end > 0 && end < pText->getCharacterCount() - 1 )
+ --end;
+
+ aString = pText->getTextRange(start, end);
+ break;
+
+ case ATK_TEXT_BOUNDARY_WORD_START:
+ start = rTextSegment.SegmentStart;
+
+ // Determine the start index of the next segment
+ aTextSegment = pText->getTextBehindIndex(rTextSegment.SegmentEnd,
+ text_type_from_boundary(boundary_type));
+ if( !aTextSegment.SegmentText.isEmpty() )
+ end = aTextSegment.SegmentStart;
+ else
+ end = pText->getCharacterCount();
+
+ aString = pText->getTextRange(start, end);
+ break;
+
+ case ATK_TEXT_BOUNDARY_WORD_END:
+ end = rTextSegment.SegmentEnd;
+
+ // Determine the end index of the previous segment
+ aTextSegment = pText->getTextBeforeIndex(rTextSegment.SegmentStart,
+ text_type_from_boundary(boundary_type));
+ if( !aTextSegment.SegmentText.isEmpty() )
+ start = aTextSegment.SegmentEnd;
+ else
+ start = 0;
+
+ aString = pText->getTextRange(start, end);
+ break;
+
+ default:
+ return nullptr;
+ }
+ }
+
+ *start_offset = start;
+ *end_offset = end;
+
+ return OUStringToGChar(aString);
+}
+
+/*****************************************************************************/
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleText>
+ getText( AtkText *pText )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
+ if( pWrap )
+ {
+ if( !pWrap->mpText.is() )
+ {
+ pWrap->mpText.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpText;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleText>();
+}
+
+/*****************************************************************************/
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleTextMarkup>
+ getTextMarkup( AtkText *pText )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
+ if( pWrap )
+ {
+ if( !pWrap->mpTextMarkup.is() )
+ {
+ pWrap->mpTextMarkup.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpTextMarkup;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleTextMarkup>();
+}
+
+/*****************************************************************************/
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
+ getTextAttributes( AtkText *pText )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
+ if( pWrap )
+ {
+ if( !pWrap->mpTextAttributes.is() )
+ {
+ pWrap->mpTextAttributes.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpTextAttributes;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleTextAttributes>();
+}
+
+/*****************************************************************************/
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleMultiLineText>
+ getMultiLineText( AtkText *pText )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
+ if( pWrap )
+ {
+ if( !pWrap->mpMultiLineText.is() )
+ {
+ pWrap->mpMultiLineText.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpMultiLineText;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleMultiLineText>();
+}
+
+/*****************************************************************************/
+
+extern "C" {
+
+static gchar *
+text_wrapper_get_text (AtkText *text,
+ gint start_offset,
+ gint end_offset)
+{
+ gchar * ret = nullptr;
+
+ g_return_val_if_fail( (end_offset == -1) || (end_offset >= start_offset), nullptr );
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ {
+ OUString aText;
+ sal_Int32 n = pText->getCharacterCount();
+
+ if( start_offset < n )
+ {
+ if( -1 == end_offset )
+ aText = pText->getTextRange(start_offset, n - start_offset);
+ else
+ aText = pText->getTextRange(start_offset, end_offset);
+ }
+
+ ret = g_strdup( OUStringToOString(aText, RTL_TEXTENCODING_UTF8 ).getStr() );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getText()" );
+ }
+
+ return ret;
+}
+
+static gchar *
+text_wrapper_get_text_after_offset (AtkText *text,
+ gint offset,
+ AtkTextBoundary boundary_type,
+ gint *start_offset,
+ gint *end_offset)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ {
+ accessibility::TextSegment aTextSegment = pText->getTextBehindIndex(offset, text_type_from_boundary(boundary_type));
+ return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in get_text_after_offset()" );
+ }
+
+ return nullptr;
+}
+
+static gchar *
+text_wrapper_get_text_at_offset (AtkText *text,
+ gint offset,
+ AtkTextBoundary boundary_type,
+ gint *start_offset,
+ gint *end_offset)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ {
+ /* If the user presses the 'End' key, the caret will be placed behind the last character,
+ * which is the same index as the first character of the next line. In atk the magic offset
+ * '-2' is used to cover this special case.
+ */
+ if (
+ -2 == offset &&
+ (ATK_TEXT_BOUNDARY_LINE_START == boundary_type ||
+ ATK_TEXT_BOUNDARY_LINE_END == boundary_type)
+ )
+ {
+ css::uno::Reference<
+ css::accessibility::XAccessibleMultiLineText> pMultiLineText
+ = getMultiLineText( text );
+ if( pMultiLineText.is() )
+ {
+ accessibility::TextSegment aTextSegment = pMultiLineText->getTextAtLineWithCaret();
+ return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
+ }
+ }
+
+ accessibility::TextSegment aTextSegment = pText->getTextAtIndex(offset, text_type_from_boundary(boundary_type));
+ return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in get_text_at_offset()" );
+ }
+
+ return nullptr;
+}
+
+static gunichar
+text_wrapper_get_character_at_offset (AtkText *text,
+ gint offset)
+{
+ gint start, end;
+ gunichar uc = 0;
+
+ gchar * char_as_string =
+ text_wrapper_get_text_at_offset(text, offset, ATK_TEXT_BOUNDARY_CHAR,
+ &start, &end);
+ if( char_as_string )
+ {
+ uc = g_utf8_get_char( char_as_string );
+ g_free( char_as_string );
+ }
+
+ return uc;
+}
+
+static gchar *
+text_wrapper_get_text_before_offset (AtkText *text,
+ gint offset,
+ AtkTextBoundary boundary_type,
+ gint *start_offset,
+ gint *end_offset)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ {
+ accessibility::TextSegment aTextSegment = pText->getTextBeforeIndex(offset, text_type_from_boundary(boundary_type));
+ return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in text_before_offset()" );
+ }
+
+ return nullptr;
+}
+
+static gint
+text_wrapper_get_caret_offset (AtkText *text)
+{
+ gint offset = -1;
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ offset = pText->getCaretPosition();
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getCaretPosition()" );
+ }
+
+ return offset;
+}
+
+static gboolean
+text_wrapper_set_caret_offset (AtkText *text,
+ gint offset)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ return pText->setCaretPosition( offset );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in setCaretPosition()" );
+ }
+
+ return FALSE;
+}
+
+// #i92232#
+static AtkAttributeSet*
+handle_text_markup_as_run_attribute( css::uno::Reference<css::accessibility::XAccessibleTextMarkup> const & pTextMarkup,
+ const gint nTextMarkupType,
+ const gint offset,
+ AtkAttributeSet* pSet,
+ gint *start_offset,
+ gint *end_offset )
+{
+ const gint nTextMarkupCount( pTextMarkup->getTextMarkupCount( nTextMarkupType ) );
+ if ( nTextMarkupCount > 0 )
+ {
+ for ( gint nTextMarkupIndex = 0;
+ nTextMarkupIndex < nTextMarkupCount;
+ ++nTextMarkupIndex )
+ {
+ accessibility::TextSegment aTextSegment =
+ pTextMarkup->getTextMarkup( nTextMarkupIndex, nTextMarkupType );
+ const gint nStartOffsetTextMarkup = aTextSegment.SegmentStart;
+ const gint nEndOffsetTextMarkup = aTextSegment.SegmentEnd;
+ if ( nStartOffsetTextMarkup <= offset )
+ {
+ if ( offset < nEndOffsetTextMarkup )
+ {
+ // text markup at <offset>
+ *start_offset = ::std::max( *start_offset,
+ nStartOffsetTextMarkup );
+ *end_offset = ::std::min( *end_offset,
+ nEndOffsetTextMarkup );
+ switch ( nTextMarkupType )
+ {
+ case css::text::TextMarkupType::SPELLCHECK:
+ {
+ pSet = attribute_set_prepend_misspelled( pSet );
+ }
+ break;
+ case css::text::TextMarkupType::TRACK_CHANGE_INSERTION:
+ {
+ pSet = attribute_set_prepend_tracked_change_insertion( pSet );
+ }
+ break;
+ case css::text::TextMarkupType::TRACK_CHANGE_DELETION:
+ {
+ pSet = attribute_set_prepend_tracked_change_deletion( pSet );
+ }
+ break;
+ case css::text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE:
+ {
+ pSet = attribute_set_prepend_tracked_change_formatchange( pSet );
+ }
+ break;
+ default:
+ {
+ OSL_ASSERT( false );
+ }
+ }
+ break; // no further iteration needed.
+ }
+ else
+ {
+ *start_offset = ::std::max( *start_offset,
+ nEndOffsetTextMarkup );
+ // continue iteration.
+ }
+ }
+ else
+ {
+ *end_offset = ::std::min( *end_offset,
+ nStartOffsetTextMarkup );
+ break; // no further iteration.
+ }
+ } // eof iteration over text markups
+ }
+
+ return pSet;
+}
+
+static AtkAttributeSet *
+text_wrapper_get_run_attributes( AtkText *text,
+ gint offset,
+ gint *start_offset,
+ gint *end_offset)
+{
+ AtkAttributeSet *pSet = nullptr;
+
+ try {
+ bool bOffsetsAreValid = false;
+
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is())
+ {
+ uno::Sequence< beans::PropertyValue > aAttributeList;
+
+ css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
+ pTextAttributes = getTextAttributes( text );
+ if(pTextAttributes.is()) // Text attributes are available for paragraphs only
+ {
+ aAttributeList = pTextAttributes->getRunAttributes( offset, uno::Sequence< OUString > () );
+ }
+ else // For other text objects use character attributes
+ {
+ aAttributeList = pText->getCharacterAttributes( offset, uno::Sequence< OUString > () );
+ }
+
+ pSet = attribute_set_new_from_property_values( aAttributeList, true, text );
+ // #i100938#
+ // - always provide start_offset and end_offset
+ {
+ accessibility::TextSegment aTextSegment =
+ pText->getTextAtIndex(offset, accessibility::AccessibleTextType::ATTRIBUTE_RUN);
+
+ *start_offset = aTextSegment.SegmentStart;
+ // #i100938#
+ // Do _not_ increment the end_offset provide by <accessibility::TextSegment> instance
+ *end_offset = aTextSegment.SegmentEnd;
+ bOffsetsAreValid = true;
+ }
+ }
+
+ // Special handling for misspelled text
+ // #i92232#
+ // - add special handling for tracked changes and refactor the
+ // corresponding code for handling misspelled text.
+ css::uno::Reference<css::accessibility::XAccessibleTextMarkup>
+ pTextMarkup = getTextMarkup( text );
+ if( pTextMarkup.is() )
+ {
+ // Get attribute run here if it hasn't been done before
+ if (!bOffsetsAreValid && pText.is())
+ {
+ accessibility::TextSegment aAttributeTextSegment =
+ pText->getTextAtIndex(offset, accessibility::AccessibleTextType::ATTRIBUTE_RUN);
+ *start_offset = aAttributeTextSegment.SegmentStart;
+ *end_offset = aAttributeTextSegment.SegmentEnd;
+ }
+ // handle misspelled text
+ pSet = handle_text_markup_as_run_attribute(
+ pTextMarkup,
+ css::text::TextMarkupType::SPELLCHECK,
+ offset, pSet, start_offset, end_offset );
+ // handle tracked changes
+ pSet = handle_text_markup_as_run_attribute(
+ pTextMarkup,
+ css::text::TextMarkupType::TRACK_CHANGE_INSERTION,
+ offset, pSet, start_offset, end_offset );
+ pSet = handle_text_markup_as_run_attribute(
+ pTextMarkup,
+ css::text::TextMarkupType::TRACK_CHANGE_DELETION,
+ offset, pSet, start_offset, end_offset );
+ pSet = handle_text_markup_as_run_attribute(
+ pTextMarkup,
+ css::text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE,
+ offset, pSet, start_offset, end_offset );
+ }
+ }
+ catch(const uno::Exception&){
+
+ g_warning( "Exception in get_run_attributes()" );
+
+ if( pSet )
+ {
+ atk_attribute_set_free( pSet );
+ pSet = nullptr;
+ }
+ }
+
+ return pSet;
+}
+
+/*****************************************************************************/
+
+static AtkAttributeSet *
+text_wrapper_get_default_attributes( AtkText *text )
+{
+ AtkAttributeSet *pSet = nullptr;
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
+ pTextAttributes = getTextAttributes( text );
+ if( pTextAttributes.is() )
+ {
+ uno::Sequence< beans::PropertyValue > aAttributeList =
+ pTextAttributes->getDefaultAttributes( uno::Sequence< OUString > () );
+
+ pSet = attribute_set_new_from_property_values( aAttributeList, false, text );
+ }
+ }
+ catch(const uno::Exception&) {
+
+ g_warning( "Exception in get_default_attributes()" );
+
+ if( pSet )
+ {
+ atk_attribute_set_free( pSet );
+ pSet = nullptr;
+ }
+ }
+
+ return pSet;
+}
+
+/*****************************************************************************/
+
+static void
+text_wrapper_get_character_extents( AtkText *text,
+ gint offset,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height,
+ AtkCoordType coords )
+{
+ *x = *y = *width = *height = -1;
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ {
+ awt::Rectangle aRect = pText->getCharacterBounds( offset );
+
+ gint origin_x = 0;
+ gint origin_y = 0;
+
+ if (coords == ATK_XY_SCREEN || coords == ATK_XY_WINDOW)
+ {
+ g_return_if_fail( ATK_IS_COMPONENT( text ) );
+ gint nWidth = -1;
+ gint nHeight = -1;
+ atk_component_get_extents(ATK_COMPONENT(text), &origin_x, &origin_y, &nWidth, &nHeight, coords);
+ }
+
+ *x = aRect.X + origin_x;
+ *y = aRect.Y + origin_y;
+ *width = aRect.Width;
+ *height = aRect.Height;
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getCharacterBounds" );
+ }
+}
+
+static gint
+text_wrapper_get_character_count (AtkText *text)
+{
+ gint rv = 0;
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ rv = pText->getCharacterCount();
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getCharacterCount" );
+ }
+
+ return rv;
+}
+
+static gint
+text_wrapper_get_offset_at_point (AtkText *text,
+ gint x,
+ gint y,
+ AtkCoordType coords)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ {
+ gint origin_x = 0;
+ gint origin_y = 0;
+
+ if (coords == ATK_XY_SCREEN || coords == ATK_XY_WINDOW)
+ {
+ g_return_val_if_fail( ATK_IS_COMPONENT( text ), -1 );
+ gint nWidth = -1;
+ gint nHeight = -1;
+ atk_component_get_extents(ATK_COMPONENT(text), &origin_x, &origin_y, &nWidth, &nHeight, coords);
+ }
+
+ return pText->getIndexAtPoint( awt::Point(x - origin_x, y - origin_y) );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getIndexAtPoint" );
+ }
+
+ return -1;
+}
+
+// FIXME: the whole series of selections API is problematic ...
+
+static gint
+text_wrapper_get_n_selections (AtkText *text)
+{
+ gint rv = 0;
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ rv = ( pText->getSelectionEnd() > pText->getSelectionStart() ) ? 1 : 0;
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectionEnd() or getSelectionStart()" );
+ }
+
+ return rv;
+}
+
+static gchar *
+text_wrapper_get_selection (AtkText *text,
+ gint selection_num,
+ gint *start_offset,
+ gint *end_offset)
+{
+ g_return_val_if_fail( selection_num == 0, FALSE );
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ {
+ *start_offset = pText->getSelectionStart();
+ *end_offset = pText->getSelectionEnd();
+
+ return OUStringToGChar( pText->getSelectedText() );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getSelectionEnd(), getSelectionStart() or getSelectedText()" );
+ }
+
+ return nullptr;
+}
+
+static gboolean
+text_wrapper_add_selection (AtkText *text,
+ gint start_offset,
+ gint end_offset)
+{
+ // FIXME: can we try to be more compatible by expanding an
+ // existing adjacent selection ?
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ return pText->setSelection( start_offset, end_offset ); // ?
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in setSelection()" );
+ }
+
+ return FALSE;
+}
+
+static gboolean
+text_wrapper_remove_selection (AtkText *text,
+ gint selection_num)
+{
+ g_return_val_if_fail( selection_num == 0, FALSE );
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ return pText->setSelection( 0, 0 ); // ?
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in setSelection()" );
+ }
+
+ return FALSE;
+}
+
+static gboolean
+text_wrapper_set_selection (AtkText *text,
+ gint selection_num,
+ gint start_offset,
+ gint end_offset)
+{
+ g_return_val_if_fail( selection_num == 0, FALSE );
+
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+ if( pText.is() )
+ return pText->setSelection( start_offset, end_offset );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in setSelection()" );
+ }
+
+ return FALSE;
+}
+
+#if ATK_CHECK_VERSION(2,32,0)
+static gboolean
+text_wrapper_scroll_substring_to(AtkText *text,
+ gint start_offset,
+ gint end_offset,
+ AtkScrollType scroll_type)
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleText> pText
+ = getText( text );
+
+ if( pText.is() )
+ return pText->scrollSubstringTo( start_offset, end_offset,
+ scroll_type_from_scroll_type( scroll_type ) );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in scrollSubstringTo()" );
+ }
+
+ return FALSE;
+}
+#endif
+
+} // extern "C"
+
+void
+textIfaceInit (AtkTextIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->get_text = text_wrapper_get_text;
+ iface->get_character_at_offset = text_wrapper_get_character_at_offset;
+ iface->get_text_before_offset = text_wrapper_get_text_before_offset;
+ iface->get_text_at_offset = text_wrapper_get_text_at_offset;
+ iface->get_text_after_offset = text_wrapper_get_text_after_offset;
+ iface->get_caret_offset = text_wrapper_get_caret_offset;
+ iface->set_caret_offset = text_wrapper_set_caret_offset;
+ iface->get_character_count = text_wrapper_get_character_count;
+ iface->get_n_selections = text_wrapper_get_n_selections;
+ iface->get_selection = text_wrapper_get_selection;
+ iface->add_selection = text_wrapper_add_selection;
+ iface->remove_selection = text_wrapper_remove_selection;
+ iface->set_selection = text_wrapper_set_selection;
+ iface->get_run_attributes = text_wrapper_get_run_attributes;
+ iface->get_default_attributes = text_wrapper_get_default_attributes;
+ iface->get_character_extents = text_wrapper_get_character_extents;
+ iface->get_offset_at_point = text_wrapper_get_offset_at_point;
+#if ATK_CHECK_VERSION(2,32,0)
+ iface->scroll_substring_to = text_wrapper_scroll_substring_to;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atktextattributes.cxx b/vcl/unx/gtk3/a11y/atktextattributes.cxx
new file mode 100644
index 000000000..25b43f480
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atktextattributes.cxx
@@ -0,0 +1,1388 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "atktextattributes.hxx"
+
+#include <com/sun/star/awt/FontSlant.hpp>
+#include <com/sun/star/awt/FontStrikeout.hpp>
+#include <com/sun/star/awt/FontUnderline.hpp>
+
+#include <com/sun/star/style/CaseMap.hpp>
+#include <com/sun/star/style/LineSpacing.hpp>
+#include <com/sun/star/style/LineSpacingMode.hpp>
+#include <com/sun/star/style/ParagraphAdjust.hpp>
+#include <com/sun/star/style/TabAlign.hpp>
+#include <com/sun/star/style/TabStop.hpp>
+
+#include <com/sun/star/text/WritingMode2.hpp>
+
+#include "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleComponent.hpp>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <tools/UnitConversion.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <stdio.h>
+#include <string.h>
+
+using namespace ::com::sun::star;
+
+typedef gchar* (* AtkTextAttrFunc) ( const uno::Any& rAny );
+typedef bool (* TextPropertyValueFunc) ( uno::Any& rAny, const gchar * value );
+
+#define STRNCMP_PARAM( s ) s,sizeof( s )-1
+
+/*****************************************************************************/
+
+static AtkTextAttribute atk_text_attribute_paragraph_style = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_font_effect = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_decoration = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_line_height = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_rotation = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_shadow = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_tab_interval = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_tab_stops = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_writing_mode = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_vertical_align = ATK_TEXT_ATTR_INVALID;
+static AtkTextAttribute atk_text_attribute_misspelled = ATK_TEXT_ATTR_INVALID;
+// #i92232#
+static AtkTextAttribute atk_text_attribute_tracked_change = ATK_TEXT_ATTR_INVALID;
+// #i92233#
+static AtkTextAttribute atk_text_attribute_mm_to_pixel_ratio = ATK_TEXT_ATTR_INVALID;
+
+/*****************************************************************************/
+
+/**
+ * !! IMPORTANT NOTE !! : when adding items to this list, KEEP THE LIST SORTED
+ * and re-arrange the enum values accordingly.
+ */
+
+namespace {
+
+enum ExportedAttribute
+{
+ TEXT_ATTRIBUTE_BACKGROUND_COLOR = 0,
+ TEXT_ATTRIBUTE_CASEMAP,
+ TEXT_ATTRIBUTE_FOREGROUND_COLOR,
+ TEXT_ATTRIBUTE_CONTOURED,
+ TEXT_ATTRIBUTE_CHAR_ESCAPEMENT,
+ TEXT_ATTRIBUTE_BLINKING,
+ TEXT_ATTRIBUTE_FONT_NAME,
+ TEXT_ATTRIBUTE_HEIGHT,
+ TEXT_ATTRIBUTE_HIDDEN,
+ TEXT_ATTRIBUTE_KERNING,
+ TEXT_ATTRIBUTE_LOCALE,
+ TEXT_ATTRIBUTE_POSTURE,
+ TEXT_ATTRIBUTE_RELIEF,
+ TEXT_ATTRIBUTE_ROTATION,
+ TEXT_ATTRIBUTE_SCALE,
+ TEXT_ATTRIBUTE_SHADOWED,
+ TEXT_ATTRIBUTE_STRIKETHROUGH,
+ TEXT_ATTRIBUTE_UNDERLINE,
+ TEXT_ATTRIBUTE_WEIGHT,
+ // #i92233#
+ TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO,
+ TEXT_ATTRIBUTE_JUSTIFICATION,
+ TEXT_ATTRIBUTE_BOTTOM_MARGIN,
+ TEXT_ATTRIBUTE_FIRST_LINE_INDENT,
+ TEXT_ATTRIBUTE_LEFT_MARGIN,
+ TEXT_ATTRIBUTE_LINE_SPACING,
+ TEXT_ATTRIBUTE_RIGHT_MARGIN,
+ TEXT_ATTRIBUTE_STYLE_NAME,
+ TEXT_ATTRIBUTE_TAB_STOPS,
+ TEXT_ATTRIBUTE_TOP_MARGIN,
+ TEXT_ATTRIBUTE_WRITING_MODE,
+ TEXT_ATTRIBUTE_LAST
+};
+
+}
+
+static const char * ExportedTextAttributes[TEXT_ATTRIBUTE_LAST] =
+{
+ "CharBackColor", // TEXT_ATTRIBUTE_BACKGROUND_COLOR
+ "CharCaseMap", // TEXT_ATTRIBUTE_CASEMAP
+ "CharColor", // TEXT_ATTRIBUTE_FOREGROUND_COLOR
+ "CharContoured", // TEXT_ATTRIBUTE_CONTOURED
+ "CharEscapement", // TEXT_ATTRIBUTE_CHAR_ESCAPEMENT
+ "CharFlash", // TEXT_ATTRIBUTE_BLINKING
+ "CharFontName", // TEXT_ATTRIBUTE_FONT_NAME
+ "CharHeight", // TEXT_ATTRIBUTE_HEIGHT
+ "CharHidden", // TEXT_ATTRIBUTE_HIDDEN
+ "CharKerning", // TEXT_ATTRIBUTE_KERNING
+ "CharLocale", // TEXT_ATTRIBUTE_LOCALE
+ "CharPosture", // TEXT_ATTRIBUTE_POSTURE
+ "CharRelief", // TEXT_ATTRIBUTE_RELIEF
+ "CharRotation", // TEXT_ATTRIBUTE_ROTATION
+ "CharScaleWidth", // TEXT_ATTRIBUTE_SCALE
+ "CharShadowed", // TEXT_ATTRIBUTE_SHADOWED
+ "CharStrikeout", // TEXT_ATTRIBUTE_STRIKETHROUGH
+ "CharUnderline", // TEXT_ATTRIBUTE_UNDERLINE
+ "CharWeight", // TEXT_ATTRIBUTE_WEIGHT
+ // #i92233#
+ "MMToPixelRatio", // TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO
+ "ParaAdjust", // TEXT_ATTRIBUTE_JUSTIFICATION
+ "ParaBottomMargin", // TEXT_ATTRIBUTE_BOTTOM_MARGIN
+ "ParaFirstLineIndent", // TEXT_ATTRIBUTE_FIRST_LINE_INDENT
+ "ParaLeftMargin", // TEXT_ATTRIBUTE_LEFT_MARGIN
+ "ParaLineSpacing", // TEXT_ATTRIBUTE_LINE_SPACING
+ "ParaRightMargin", // TEXT_ATTRIBUTE_RIGHT_MARGIN
+ "ParaStyleName", // TEXT_ATTRIBUTE_STYLE_NAME
+ "ParaTabStops", // TEXT_ATTRIBUTE_TAB_STOPS
+ "ParaTopMargin", // TEXT_ATTRIBUTE_TOP_MARGIN
+ "WritingMode" // TEXT_ATTRIBUTE_WRITING_MODE
+};
+
+/*****************************************************************************/
+
+static gchar*
+get_value( const uno::Sequence< beans::PropertyValue >& rAttributeList,
+ sal_Int32 nIndex, AtkTextAttrFunc func )
+{
+ if( nIndex != -1 )
+ return func(rAttributeList[nIndex].Value);
+
+ return nullptr;
+}
+
+#define get_bool_value( list, index ) get_value( list, index, Bool2String )
+#define get_height_value( list, index ) get_value( list, index, Float2String )
+#define get_justification_value( list, index ) get_value( list, index, Adjust2Justification )
+#define get_cmm_value( list, index ) get_value( list, index, CMM2UnitString )
+#define get_scale_width( list, index ) get_value( list, index, Scale2String )
+#define get_strikethrough_value( list, index ) get_value( list, index, Strikeout2String )
+#define get_string_value( list, index ) get_value( list, index, GetString )
+#define get_style_value( list, index ) get_value( list, index, FontSlant2Style )
+#define get_underline_value( list, index ) get_value( list, index, Underline2String )
+#define get_variant_value( list, index ) get_value( list, index, CaseMap2String )
+#define get_weight_value( list, index ) get_value( list, index, Weight2String )
+#define get_language_string( list, index ) get_value( list, index, Locale2String )
+
+/*****************************************************************************/
+
+static bool
+InvalidValue( uno::Any&, const gchar * )
+{
+ return false;
+}
+
+/*****************************************************************************/
+
+static gchar*
+Float2String(const uno::Any& rAny)
+{
+ return g_strdup_printf( "%g", rAny.get<float>() );
+}
+
+static bool
+String2Float( uno::Any& rAny, const gchar * value )
+{
+ float fval;
+
+ if( 1 != sscanf( value, "%g", &fval ) )
+ return false;
+
+ rAny <<= fval;
+ return true;
+}
+
+/*****************************************************************************/
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleComponent>
+ getComponent( AtkText *pText )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
+ if( pWrap )
+ {
+ if( !pWrap->mpComponent.is() )
+ {
+ pWrap->mpComponent.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpComponent;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleComponent>();
+}
+
+static gchar*
+get_color_value(const uno::Sequence< beans::PropertyValue >& rAttributeList,
+ const sal_Int32 * pIndexArray,
+ ExportedAttribute attr,
+ AtkText * text)
+{
+ sal_Int32 nColor = -1; // AUTOMATIC
+ sal_Int32 nIndex = pIndexArray[attr];
+
+ if( nIndex != -1 )
+ nColor = rAttributeList[nIndex].Value.get<sal_Int32>();
+
+ /*
+ * Check for color value for 100% alpha white, which means
+ * "automatic". Grab the RGB value from XAccessibleComponent
+ * in this case.
+ */
+
+ if( (nColor == -1) && text )
+ {
+ try
+ {
+ css::uno::Reference<css::accessibility::XAccessibleComponent>
+ pComponent = getComponent( text );
+ if( pComponent.is() )
+ {
+ switch( attr )
+ {
+ case TEXT_ATTRIBUTE_BACKGROUND_COLOR:
+ nColor = pComponent->getBackground();
+ break;
+ case TEXT_ATTRIBUTE_FOREGROUND_COLOR:
+ nColor = pComponent->getForeground();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ catch(const uno::Exception&) {
+ g_warning( "Exception in get[Fore|Back]groundColor()" );
+ }
+ }
+
+ if( nColor != -1 )
+ {
+ sal_uInt8 blue = nColor & 0xFF;
+ sal_uInt8 green = (nColor >> 8) & 0xFF;
+ sal_uInt8 red = (nColor >> 16) & 0xFF;
+
+ return g_strdup_printf( "%u,%u,%u", red, green, blue );
+ }
+
+ return nullptr;
+}
+
+static bool
+String2Color( uno::Any& rAny, const gchar * value )
+{
+ int red, green, blue;
+
+ if( 3 != sscanf( value, "%d,%d,%d", &red, &green, &blue ) )
+ return false;
+
+ sal_Int32 nColor = static_cast<sal_Int32>(blue) | ( static_cast<sal_Int32>(green) << 8 ) | ( static_cast<sal_Int32>(red) << 16 );
+ rAny <<= nColor;
+ return true;
+}
+
+/*****************************************************************************/
+
+static gchar*
+FontSlant2Style(const uno::Any& rAny)
+{
+ const gchar * value = nullptr;
+
+ awt::FontSlant aFontSlant;
+ if(!(rAny >>= aFontSlant))
+ return nullptr;
+
+ switch( aFontSlant )
+ {
+ case awt::FontSlant_NONE:
+ value = "normal";
+ break;
+
+ case awt::FontSlant_OBLIQUE:
+ value = "oblique";
+ break;
+
+ case awt::FontSlant_ITALIC:
+ value = "italic";
+ break;
+
+ case awt::FontSlant_REVERSE_OBLIQUE:
+ value = "reverse oblique";
+ break;
+
+ case awt::FontSlant_REVERSE_ITALIC:
+ value = "reverse italic";
+ break;
+
+ default:
+ break;
+ }
+
+ if( value )
+ return g_strdup( value );
+
+ return nullptr;
+}
+
+static bool
+Style2FontSlant( uno::Any& rAny, const gchar * value )
+{
+ awt::FontSlant aFontSlant;
+
+ if( strncmp( value, STRNCMP_PARAM( "normal" ) ) == 0 )
+ aFontSlant = awt::FontSlant_NONE;
+ else if( strncmp( value, STRNCMP_PARAM( "oblique" ) ) == 0 )
+ aFontSlant = awt::FontSlant_OBLIQUE;
+ else if( strncmp( value, STRNCMP_PARAM( "italic" ) ) == 0 )
+ aFontSlant = awt::FontSlant_ITALIC;
+ else if( strncmp( value, STRNCMP_PARAM( "reverse oblique" ) ) == 0 )
+ aFontSlant = awt::FontSlant_REVERSE_OBLIQUE;
+ else if( strncmp( value, STRNCMP_PARAM( "reverse italic" ) ) == 0 )
+ aFontSlant = awt::FontSlant_REVERSE_ITALIC;
+ else
+ return false;
+
+ rAny <<= aFontSlant;
+ return true;
+}
+
+/*****************************************************************************/
+
+static gchar*
+Weight2String(const uno::Any& rAny)
+{
+ return g_strdup_printf( "%g", rAny.get<float>() * 4 );
+}
+
+static bool
+String2Weight( uno::Any& rAny, const gchar * value )
+{
+ float weight;
+
+ if( 1 != sscanf( value, "%g", &weight ) )
+ return false;
+
+ rAny <<= weight / 4;
+ return true;
+}
+
+/*****************************************************************************/
+
+static gchar*
+Adjust2Justification(const uno::Any& rAny)
+{
+ const gchar * value = nullptr;
+
+ switch( static_cast<style::ParagraphAdjust>(rAny.get<short>()) )
+ {
+ case style::ParagraphAdjust_LEFT:
+ value = "left";
+ break;
+
+ case style::ParagraphAdjust_RIGHT:
+ value = "right";
+ break;
+
+ case style::ParagraphAdjust_BLOCK:
+ case style::ParagraphAdjust_STRETCH:
+ value = "fill";
+ break;
+
+ case style::ParagraphAdjust_CENTER:
+ value = "center";
+ break;
+
+ default:
+ break;
+ }
+
+ if( value )
+ return g_strdup( value );
+
+ return nullptr;
+}
+
+static bool
+Justification2Adjust( uno::Any& rAny, const gchar * value )
+{
+ style::ParagraphAdjust nParagraphAdjust;
+
+ if( strncmp( value, STRNCMP_PARAM( "left" ) ) == 0 )
+ nParagraphAdjust = style::ParagraphAdjust_LEFT;
+ else if( strncmp( value, STRNCMP_PARAM( "right" ) ) == 0 )
+ nParagraphAdjust = style::ParagraphAdjust_RIGHT;
+ else if( strncmp( value, STRNCMP_PARAM( "fill" ) ) == 0 )
+ nParagraphAdjust = style::ParagraphAdjust_BLOCK;
+ else if( strncmp( value, STRNCMP_PARAM( "center" ) ) == 0 )
+ nParagraphAdjust = style::ParagraphAdjust_CENTER;
+ else
+ return false;
+
+ rAny <<= static_cast<short>(nParagraphAdjust);
+ return true;
+}
+
+/*****************************************************************************/
+
+const gchar * const font_strikethrough[] = {
+ "none", // FontStrikeout::NONE
+ "single", // FontStrikeout::SINGLE
+ "double", // FontStrikeout::DOUBLE
+ nullptr, // FontStrikeout::DONTKNOW
+ "bold", // FontStrikeout::BOLD
+ "with /", // FontStrikeout::SLASH
+ "with X" // FontStrikeout::X
+};
+
+static gchar*
+Strikeout2String(const uno::Any& rAny)
+{
+ sal_Int16 n = rAny.get<sal_Int16>();
+
+ if( n >= 0 && o3tl::make_unsigned(n) < SAL_N_ELEMENTS(font_strikethrough) )
+ return g_strdup( font_strikethrough[n] );
+
+ return nullptr;
+}
+
+static bool
+String2Strikeout( uno::Any& rAny, const gchar * value )
+{
+ for( sal_Int16 n=0; n < sal_Int16(SAL_N_ELEMENTS(font_strikethrough)); ++n )
+ {
+ if( ( nullptr != font_strikethrough[n] ) &&
+ 0 == strncmp( value, font_strikethrough[n], strlen( font_strikethrough[n] ) ) )
+ {
+ rAny <<= n;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*****************************************************************************/
+
+static gchar*
+Underline2String(const uno::Any& rAny)
+{
+ const gchar * value = nullptr;
+
+ switch( rAny.get<sal_Int16>() )
+ {
+ case awt::FontUnderline::NONE:
+ value = "none";
+ break;
+
+ case awt::FontUnderline::SINGLE:
+ value = "single";
+ break;
+
+ case awt::FontUnderline::DOUBLE:
+ value = "double";
+ break;
+
+ default:
+ break;
+ }
+
+ if( value )
+ return g_strdup( value );
+
+ return nullptr;
+}
+
+static bool
+String2Underline( uno::Any& rAny, const gchar * value )
+{
+ short nUnderline;
+
+ if( strncmp( value, STRNCMP_PARAM( "none" ) ) == 0 )
+ nUnderline = awt::FontUnderline::NONE;
+ else if( strncmp( value, STRNCMP_PARAM( "single" ) ) == 0 )
+ nUnderline = awt::FontUnderline::SINGLE;
+ else if( strncmp( value, STRNCMP_PARAM( "double" ) ) == 0 )
+ nUnderline = awt::FontUnderline::DOUBLE;
+ else
+ return false;
+
+ rAny <<= nUnderline;
+ return true;
+}
+
+/*****************************************************************************/
+
+static gchar*
+GetString(const uno::Any& rAny)
+{
+ OString aFontName = OUStringToOString( rAny.get< OUString > (), RTL_TEXTENCODING_UTF8 );
+
+ if( !aFontName.isEmpty() )
+ return g_strdup( aFontName.getStr() );
+
+ return nullptr;
+}
+
+static bool
+SetString( uno::Any& rAny, const gchar * value )
+{
+ OString aFontName( value );
+
+ if( !aFontName.isEmpty() )
+ {
+ rAny <<= OStringToOUString( aFontName, RTL_TEXTENCODING_UTF8 );
+ return true;
+ }
+
+ return false;
+}
+
+/*****************************************************************************/
+
+// @see http://developer.gnome.org/doc/API/2.0/atk/AtkText.html#AtkTextAttribute
+
+// CMM = 100th of mm
+static gchar*
+CMM2UnitString(const uno::Any& rAny)
+{
+ double fValue = rAny.get<sal_Int32>();
+ fValue = fValue * 0.01;
+
+ return g_strdup_printf( "%gmm", fValue );
+}
+
+static bool
+UnitString2CMM( uno::Any& rAny, const gchar * value )
+{
+ float fValue = 0.0; // pb: don't use double here because of warning on linux
+
+ if( 1 != sscanf( value, "%gmm", &fValue ) )
+ return false;
+
+ fValue = fValue * 100;
+
+ rAny <<= static_cast<sal_Int32>(fValue);
+ return true;
+}
+
+/*****************************************************************************/
+
+static const gchar * bool_values[] = { "true", "false" };
+
+static gchar *
+Bool2String( const uno::Any& rAny )
+{
+ int n = 1;
+
+ if( rAny.get<bool>() )
+ n = 0;
+
+ return g_strdup( bool_values[n] );
+}
+
+static bool
+String2Bool( uno::Any& rAny, const gchar * value )
+{
+ bool bValue;
+
+ if( strncmp( value, STRNCMP_PARAM( "true" ) ) == 0 )
+ bValue = true;
+ else if( strncmp( value, STRNCMP_PARAM( "false" ) ) == 0 )
+ bValue = false;
+ else
+ return false;
+
+ rAny <<= bValue;
+ return true;
+}
+
+/*****************************************************************************/
+
+static gchar*
+Scale2String( const uno::Any& rAny )
+{
+ return g_strdup_printf( "%g", static_cast<double>(rAny.get< sal_Int16 > ()) / 100 );
+}
+
+static bool
+String2Scale( uno::Any& rAny, const gchar * value )
+{
+ double dval;
+
+ if( 1 != sscanf( value, "%lg", &dval ) )
+ return false;
+
+ rAny <<= static_cast<sal_Int16>(dval * 100);
+ return true;
+}
+
+/*****************************************************************************/
+
+static gchar *
+CaseMap2String( const uno::Any& rAny )
+{
+ const gchar * value;
+
+ switch( rAny.get<short>() )
+ {
+ case style::CaseMap::SMALLCAPS:
+ value = "small_caps";
+ break;
+
+ default:
+ value = "normal";
+ break;
+ }
+
+ return g_strdup(value);
+}
+
+static bool
+String2CaseMap( uno::Any& rAny, const gchar * value )
+{
+ short nCaseMap;
+
+ if( strncmp( value, STRNCMP_PARAM( "normal" ) ) == 0 )
+ nCaseMap = style::CaseMap::NONE;
+ else if( strncmp( value, STRNCMP_PARAM( "small_caps" ) ) == 0 )
+ nCaseMap = style::CaseMap::SMALLCAPS;
+ else
+ return false;
+
+ rAny <<= nCaseMap;
+ return true;
+}
+
+/*****************************************************************************/
+
+const gchar * const font_stretch[] = {
+ "ultra_condensed",
+ "extra_condensed",
+ "condensed",
+ "semi_condensed",
+ "normal",
+ "semi_expanded",
+ "expanded",
+ "extra_expanded",
+ "ultra_expanded"
+};
+
+static gchar*
+Kerning2Stretch(const uno::Any& rAny)
+{
+ sal_Int16 n = rAny.get<sal_Int16>();
+ int i = 4;
+
+ // No good idea for a mapping - just return the basic info
+ if( n < 0 )
+ i=2;
+ else if( n > 0 )
+ i=6;
+
+ return g_strdup(font_stretch[i]);
+}
+
+/*****************************************************************************/
+
+static gchar*
+Locale2String(const uno::Any& rAny)
+{
+ /* FIXME-BCP47: support language tags? And why is country lowercase? */
+ lang::Locale aLocale = rAny.get<lang::Locale> ();
+ LanguageTag aLanguageTag( aLocale);
+ return g_strdup_printf( "%s-%s",
+ OUStringToOString( aLanguageTag.getLanguage(), RTL_TEXTENCODING_ASCII_US).getStr(),
+ OUStringToOString( aLanguageTag.getCountry(), RTL_TEXTENCODING_ASCII_US).toAsciiLowerCase().getStr() );
+}
+
+static bool
+String2Locale( uno::Any& rAny, const gchar * value )
+{
+ /* FIXME-BCP47: support language tags? */
+ bool ret = false;
+
+ gchar ** str_array = g_strsplit_set( value, "-.@", -1 );
+ if( str_array[0] != nullptr )
+ {
+ ret = true;
+
+ lang::Locale aLocale;
+
+ aLocale.Language = OUString::createFromAscii(str_array[0]);
+ if( str_array[1] != nullptr )
+ {
+ gchar * country = g_ascii_strup(str_array[1], -1);
+ aLocale.Country = OUString::createFromAscii(country);
+ g_free(country);
+ }
+
+ rAny <<= aLocale;
+ }
+
+ g_strfreev(str_array);
+ return ret;
+}
+
+/*****************************************************************************/
+
+// @see http://www.w3.org/TR/2002/WD-css3-fonts-20020802/#font-effect-prop
+static const gchar * relief[] = { "none", "emboss", "engrave" };
+const gchar * const outline = "outline";
+
+static gchar *
+get_font_effect(const uno::Sequence< beans::PropertyValue >& rAttributeList,
+ sal_Int32 nContourIndex, sal_Int32 nReliefIndex)
+{
+ if( nContourIndex != -1 )
+ {
+ if( rAttributeList[nContourIndex].Value.get<bool>() )
+ return g_strdup(outline);
+ }
+
+ if( nReliefIndex != -1 )
+ {
+ sal_Int16 n = rAttributeList[nReliefIndex].Value.get<sal_Int16>();
+ if( n < 3)
+ return g_strdup(relief[n]);
+ }
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+// @see http://www.w3.org/TR/REC-CSS2/text.html#lining-striking-props
+
+enum
+{
+ DECORATION_NONE = 0,
+ DECORATION_BLINK,
+ DECORATION_UNDERLINE,
+ DECORATION_LINE_THROUGH
+};
+
+static const gchar * decorations[] = { "none", "blink", "underline", "line-through" };
+
+static gchar *
+get_text_decoration(const uno::Sequence< beans::PropertyValue >& rAttributeList,
+ sal_Int32 nBlinkIndex, sal_Int32 nUnderlineIndex,
+ sal_Int16 nStrikeoutIndex)
+{
+ gchar * value_list[4] = { nullptr, nullptr, nullptr, nullptr };
+ gint count = 0;
+
+ // no property value found
+ if( ( nBlinkIndex == -1 ) && (nUnderlineIndex == -1 ) && (nStrikeoutIndex == -1))
+ return nullptr;
+
+ if( nBlinkIndex != -1 )
+ {
+ if( rAttributeList[nBlinkIndex].Value.get<bool>() )
+ value_list[count++] = const_cast <gchar *> (decorations[DECORATION_BLINK]);
+ }
+ if( nUnderlineIndex != -1 )
+ {
+ sal_Int16 n = rAttributeList[nUnderlineIndex].Value.get<sal_Int16> ();
+ if( n != awt::FontUnderline::NONE )
+ value_list[count++] = const_cast <gchar *> (decorations[DECORATION_UNDERLINE]);
+ }
+ if( nStrikeoutIndex != -1 )
+ {
+ sal_Int16 n = rAttributeList[nStrikeoutIndex].Value.get<sal_Int16> ();
+ if( n != awt::FontStrikeout::NONE && n != awt::FontStrikeout::DONTKNOW )
+ value_list[count++] = const_cast <gchar *> (decorations[DECORATION_LINE_THROUGH]);
+ }
+
+ if( count == 0 )
+ value_list[count++] = const_cast <gchar *> (decorations[DECORATION_NONE]);
+
+ return g_strjoinv(" ", value_list);
+}
+
+/*****************************************************************************/
+
+// @see http://www.w3.org/TR/REC-CSS2/text.html#propdef-text-shadow
+
+static const gchar * shadow_values[] = { "none", "black" };
+
+static gchar *
+Bool2Shadow( const uno::Any& rAny )
+{
+ int n = 0;
+
+ if( rAny.get<bool>() )
+ n = 1;
+
+ return g_strdup( shadow_values[n] );
+}
+
+/*****************************************************************************/
+
+static gchar *
+Short2Degree( const uno::Any& rAny )
+{
+ float f = rAny.get<sal_Int16>() / 10.0;
+ return g_strdup_printf( "%g", f );
+}
+
+/*****************************************************************************/
+
+const gchar * const directions[] = { "ltr", "rtl", "rtl", "ltr", "none" };
+
+static gchar *
+WritingMode2Direction( const uno::Any& rAny )
+{
+ sal_Int16 n = rAny.get<sal_Int16>();
+
+ if( 0 <= n && n <= text::WritingMode2::PAGE )
+ return g_strdup(directions[n]);
+
+ return nullptr;
+}
+
+// @see http://www.w3.org/TR/2001/WD-css3-text-20010517/#PrimaryTextAdvanceDirection
+
+const gchar * const writing_modes[] = { "lr-tb", "rl-tb", "tb-rl", "tb-lr", "none" };
+static gchar *
+WritingMode2String( const uno::Any& rAny )
+{
+ sal_Int16 n = rAny.get<sal_Int16>();
+
+ if( 0 <= n && n <= text::WritingMode2::PAGE )
+ return g_strdup(writing_modes[n]);
+
+ return nullptr;
+}
+
+/*****************************************************************************/
+
+const char * const baseline_values[] = { "baseline", "sub", "super" };
+
+// @see http://www.w3.org/TR/REC-CSS2/visudet.html#propdef-vertical-align
+static gchar *
+Escapement2VerticalAlign( const uno::Any& rAny )
+{
+ sal_Int16 n = rAny.get<sal_Int16>();
+ gchar * ret = nullptr;
+
+ // Values are in %, 101% means "automatic"
+ if( n == 0 )
+ ret = g_strdup(baseline_values[0]);
+ else if( n == 101 )
+ ret = g_strdup(baseline_values[2]);
+ else if( n == -101 )
+ ret = g_strdup(baseline_values[1]);
+ else
+ ret = g_strdup_printf( "%d%%", n );
+
+ return ret;
+}
+
+/*****************************************************************************/
+
+// @see http://www.w3.org/TR/REC-CSS2/visudet.html#propdef-line-height
+static gchar *
+LineSpacing2LineHeight( const uno::Any& rAny )
+{
+ style::LineSpacing ls;
+ gchar * ret = nullptr;
+
+ if( rAny >>= ls )
+ {
+ if( ls.Mode == style::LineSpacingMode::PROP )
+ ret = g_strdup_printf( "%d%%", ls.Height );
+ else if( ls.Mode == style::LineSpacingMode::FIX )
+ ret = g_strdup_printf("%.3gpt", convertMm100ToPoint<double>(ls.Height));
+ }
+
+ return ret;
+}
+
+/*****************************************************************************/
+
+// @see http://www.w3.org/People/howcome/t/970224HTMLERB-CSS/WD-tabs-970117.html
+static gchar *
+TabStopList2String( const uno::Any& rAny, bool default_tabs )
+{
+ uno::Sequence< style::TabStop > theTabStops;
+ gchar * ret = nullptr;
+
+ if( rAny >>= theTabStops)
+ {
+ sal_Unicode lastFillChar = ' ';
+
+ for( const auto& rTabStop : std::as_const(theTabStops) )
+ {
+ bool is_default_tab = (style::TabAlign_DEFAULT == rTabStop.Alignment);
+
+ if( is_default_tab != default_tabs )
+ continue;
+
+ double fValue = rTabStop.Position;
+ fValue = fValue * 0.01;
+
+ const gchar * tab_align = "";
+ switch( rTabStop.Alignment )
+ {
+ case style::TabAlign_LEFT :
+ tab_align = "left ";
+ break;
+ case style::TabAlign_CENTER :
+ tab_align = "center ";
+ break;
+ case style::TabAlign_RIGHT :
+ tab_align = "right ";
+ break;
+ case style::TabAlign_DECIMAL :
+ tab_align = "decimal ";
+ break;
+ default:
+ break;
+ }
+
+ const gchar * lead_char = "";
+
+ if( rTabStop.FillChar != lastFillChar )
+ {
+ lastFillChar = rTabStop.FillChar;
+ switch (lastFillChar)
+ {
+ case ' ':
+ lead_char = "blank ";
+ break;
+
+ case '.':
+ lead_char = "dotted ";
+ break;
+
+ case '-':
+ lead_char = "dashed ";
+ break;
+
+ case '_':
+ lead_char = "lined ";
+ break;
+
+ default:
+ lead_char = "custom ";
+ break;
+ }
+ }
+
+ gchar * tab_str = g_strdup_printf( "%s%s%gmm", lead_char, tab_align, fValue );
+
+ if( ret )
+ {
+ gchar * old_tab_str = ret;
+ ret = g_strconcat(old_tab_str, " ", tab_str, nullptr);
+ g_free( tab_str );
+ g_free( old_tab_str );
+ }
+ else
+ ret = tab_str;
+ }
+ }
+
+ return ret;
+}
+
+static gchar *
+TabStops2String( const uno::Any& rAny )
+{
+ return TabStopList2String(rAny, false);
+}
+
+static gchar *
+DefaultTabStops2String( const uno::Any& rAny )
+{
+ return TabStopList2String(rAny, true);
+}
+
+/*****************************************************************************/
+
+extern "C" {
+
+static int
+attr_compare(const void *p1,const void *p2)
+{
+ const rtl_uString * pustr = static_cast<const rtl_uString *>(p1);
+ const char * pc = *static_cast<const char * const *>(p2);
+
+ return rtl_ustr_ascii_compare_WithLength(pustr->buffer, pustr->length, pc);
+}
+
+}
+
+static void
+find_exported_attributes( sal_Int32 *pArray,
+ const css::uno::Sequence< css::beans::PropertyValue >& rAttributeList )
+{
+ for( sal_Int32 i = 0; i < rAttributeList.getLength(); i++ )
+ {
+ const char ** pAttr = static_cast<const char **>(bsearch(rAttributeList[i].Name.pData,
+ ExportedTextAttributes, TEXT_ATTRIBUTE_LAST, sizeof(const char *),
+ attr_compare));
+
+ if( pAttr )
+ {
+ sal_Int32 nIndex = pAttr - ExportedTextAttributes;
+ pArray[nIndex] = i;
+ }
+ }
+}
+
+/*****************************************************************************/
+
+static AtkAttributeSet*
+attribute_set_prepend( AtkAttributeSet* attribute_set,
+ AtkTextAttribute attribute,
+ gchar * value )
+{
+ if( value )
+ {
+ AtkAttribute *at = static_cast<AtkAttribute *>(g_malloc( sizeof (AtkAttribute) ));
+ at->name = g_strdup( atk_text_attribute_get_name( attribute ) );
+ at->value = value;
+
+ return g_slist_prepend(attribute_set, at);
+ }
+
+ return attribute_set;
+}
+
+/*****************************************************************************/
+
+AtkAttributeSet*
+attribute_set_new_from_property_values(
+ const uno::Sequence< beans::PropertyValue >& rAttributeList,
+ bool run_attributes_only,
+ AtkText *text)
+{
+ AtkAttributeSet* attribute_set = nullptr;
+
+ sal_Int32 aIndexList[TEXT_ATTRIBUTE_LAST] = { -1 };
+
+ // Initialize index array with -1
+ for(sal_Int32 & rn : aIndexList)
+ rn = -1;
+
+ find_exported_attributes(aIndexList, rAttributeList);
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_BG_COLOR,
+ get_color_value(rAttributeList, aIndexList, TEXT_ATTRIBUTE_BACKGROUND_COLOR, run_attributes_only ? nullptr : text ) );
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_FG_COLOR,
+ get_color_value(rAttributeList, aIndexList, TEXT_ATTRIBUTE_FOREGROUND_COLOR, run_attributes_only ? nullptr : text) );
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_INVISIBLE,
+ get_bool_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_HIDDEN]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_UNDERLINE,
+ get_underline_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_UNDERLINE]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STRIKETHROUGH,
+ get_strikethrough_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_STRIKETHROUGH]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_SIZE,
+ get_height_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_HEIGHT]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_WEIGHT,
+ get_weight_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WEIGHT]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_FAMILY_NAME,
+ get_string_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_FONT_NAME]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_VARIANT,
+ get_variant_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CASEMAP]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STYLE,
+ get_style_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_POSTURE]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_SCALE,
+ get_scale_width(rAttributeList, aIndexList[TEXT_ATTRIBUTE_SCALE]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_LANGUAGE,
+ get_language_string(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LOCALE]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_DIRECTION,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WRITING_MODE], WritingMode2Direction));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STRETCH,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_KERNING], Kerning2Stretch));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_font_effect )
+ atk_text_attribute_font_effect = atk_text_attribute_register("font-effect");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_font_effect,
+ get_font_effect(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CONTOURED], aIndexList[TEXT_ATTRIBUTE_RELIEF]));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_decoration )
+ atk_text_attribute_decoration = atk_text_attribute_register("text-decoration");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_decoration,
+ get_text_decoration(rAttributeList, aIndexList[TEXT_ATTRIBUTE_BLINKING],
+ aIndexList[TEXT_ATTRIBUTE_UNDERLINE], aIndexList[TEXT_ATTRIBUTE_STRIKETHROUGH]));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_rotation )
+ atk_text_attribute_rotation = atk_text_attribute_register("text-rotation");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_rotation,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_ROTATION], Short2Degree));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_shadow )
+ atk_text_attribute_shadow = atk_text_attribute_register("text-shadow");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_shadow,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_SHADOWED], Bool2Shadow));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_writing_mode )
+ atk_text_attribute_writing_mode = atk_text_attribute_register("writing-mode");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_writing_mode,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WRITING_MODE], WritingMode2String));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_vertical_align )
+ atk_text_attribute_vertical_align = atk_text_attribute_register("vertical-align");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_vertical_align,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CHAR_ESCAPEMENT], Escapement2VerticalAlign));
+
+ if( run_attributes_only )
+ return attribute_set;
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_LEFT_MARGIN,
+ get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LEFT_MARGIN]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_RIGHT_MARGIN,
+ get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_RIGHT_MARGIN]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_INDENT,
+ get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_FIRST_LINE_INDENT]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_PIXELS_ABOVE_LINES,
+ get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TOP_MARGIN]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_PIXELS_BELOW_LINES,
+ get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_BOTTOM_MARGIN]));
+
+ attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_JUSTIFICATION,
+ get_justification_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_JUSTIFICATION]));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_paragraph_style )
+ atk_text_attribute_paragraph_style = atk_text_attribute_register("paragraph-style");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_paragraph_style,
+ get_string_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_STYLE_NAME]));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_line_height )
+ atk_text_attribute_line_height = atk_text_attribute_register("line-height");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_line_height,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LINE_SPACING], LineSpacing2LineHeight));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tab_interval )
+ atk_text_attribute_tab_interval = atk_text_attribute_register("tab-interval");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_tab_interval,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TAB_STOPS], DefaultTabStops2String));
+
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tab_stops )
+ atk_text_attribute_tab_stops = atk_text_attribute_register("tab-stops");
+
+ attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_tab_stops,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TAB_STOPS], TabStops2String));
+
+ // #i92233#
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_mm_to_pixel_ratio )
+ atk_text_attribute_mm_to_pixel_ratio = atk_text_attribute_register("mm-to-pixel-ratio");
+
+ attribute_set = attribute_set_prepend( attribute_set, atk_text_attribute_mm_to_pixel_ratio,
+ get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO], Float2String));
+
+ return attribute_set;
+}
+
+AtkAttributeSet*
+attribute_set_new_from_extended_attributes(
+ const css::uno::Reference< css::accessibility::XAccessibleExtendedAttributes >& rExtendedAttributes )
+{
+ AtkAttributeSet *pSet = nullptr;
+
+ // extended attributes is a string of colon-separated pairs of property and value,
+ // with pairs separated by semicolons. Example: "heading-level:2;weight:bold;"
+ uno::Any anyVal = rExtendedAttributes->getExtendedAttributes();
+ OUString sExtendedAttrs;
+ anyVal >>= sExtendedAttrs;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OUString sProperty = sExtendedAttrs.getToken( 0, ';', nIndex );
+
+ sal_Int32 nColonPos = 0;
+ OString sPropertyName = OUStringToOString( o3tl::getToken(sProperty, 0, ':', nColonPos ),
+ RTL_TEXTENCODING_UTF8 );
+ OString sPropertyValue = OUStringToOString( o3tl::getToken(sProperty, 0, ':', nColonPos ),
+ RTL_TEXTENCODING_UTF8 );
+
+ pSet = attribute_set_prepend( pSet,
+ atk_text_attribute_register( sPropertyName.getStr() ),
+ g_strdup_printf( "%s", sPropertyValue.getStr() ) );
+ }
+ while ( nIndex >= 0 && nIndex < sExtendedAttrs.getLength() );
+
+ return pSet;
+}
+
+AtkAttributeSet* attribute_set_prepend_misspelled( AtkAttributeSet* attribute_set )
+{
+ if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_misspelled )
+ atk_text_attribute_misspelled = atk_text_attribute_register( "text-spelling" );
+
+ attribute_set = attribute_set_prepend( attribute_set, atk_text_attribute_misspelled,
+ g_strdup_printf( "misspelled" ) );
+
+ return attribute_set;
+}
+
+// #i92232#
+AtkAttributeSet* attribute_set_prepend_tracked_change_insertion( AtkAttributeSet* attribute_set )
+{
+ if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change )
+ {
+ atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" );
+ }
+
+ attribute_set = attribute_set_prepend( attribute_set,
+ atk_text_attribute_tracked_change,
+ g_strdup_printf( "insertion" ) );
+
+ return attribute_set;
+}
+
+AtkAttributeSet* attribute_set_prepend_tracked_change_deletion( AtkAttributeSet* attribute_set )
+{
+ if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change )
+ {
+ atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" );
+ }
+
+ attribute_set = attribute_set_prepend( attribute_set,
+ atk_text_attribute_tracked_change,
+ g_strdup_printf( "deletion" ) );
+
+ return attribute_set;
+}
+
+AtkAttributeSet* attribute_set_prepend_tracked_change_formatchange( AtkAttributeSet* attribute_set )
+{
+ if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change )
+ {
+ atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" );
+ }
+
+ attribute_set = attribute_set_prepend( attribute_set,
+ atk_text_attribute_tracked_change,
+ g_strdup_printf( "attribute-change" ) );
+
+ return attribute_set;
+}
+
+/*****************************************************************************/
+
+namespace {
+
+struct AtkTextAttrMapping
+{
+ const char * name;
+ TextPropertyValueFunc const toPropertyValue;
+};
+
+}
+
+const AtkTextAttrMapping g_TextAttrMap[] =
+{
+ { "", InvalidValue }, // ATK_TEXT_ATTR_INVALID = 0
+ { "ParaLeftMargin", UnitString2CMM }, // ATK_TEXT_ATTR_LEFT_MARGIN
+ { "ParaRightMargin", UnitString2CMM }, // ATK_TEXT_ATTR_RIGHT_MARGIN
+ { "ParaFirstLineIndent", UnitString2CMM }, // ATK_TEXT_ATTR_INDENT
+ { "CharHidden", String2Bool }, // ATK_TEXT_ATTR_INVISIBLE
+ { "", InvalidValue }, // ATK_TEXT_ATTR_EDITABLE
+ { "ParaTopMargin", UnitString2CMM }, // ATK_TEXT_ATTR_PIXELS_ABOVE_LINES
+ { "ParaBottomMargin", UnitString2CMM }, // ATK_TEXT_ATTR_PIXELS_BELOW_LINES
+ { "", InvalidValue }, // ATK_TEXT_ATTR_PIXELS_INSIDE_WRAP
+ { "", InvalidValue }, // ATK_TEXT_ATTR_BG_FULL_HEIGHT
+ { "", InvalidValue }, // ATK_TEXT_ATTR_RISE
+ { "CharUnderline", String2Underline }, // ATK_TEXT_ATTR_UNDERLINE
+ { "CharStrikeout", String2Strikeout }, // ATK_TEXT_ATTR_STRIKETHROUGH
+ { "CharHeight", String2Float }, // ATK_TEXT_ATTR_SIZE
+ { "CharScaleWidth", String2Scale }, // ATK_TEXT_ATTR_SCALE
+ { "CharWeight", String2Weight }, // ATK_TEXT_ATTR_WEIGHT
+ { "CharLocale", String2Locale }, // ATK_TEXT_ATTR_LANGUAGE
+ { "CharFontName", SetString }, // ATK_TEXT_ATTR_FAMILY_NAME
+ { "CharBackColor", String2Color }, // ATK_TEXT_ATTR_BG_COLOR
+ { "CharColor", String2Color }, // ATK_TEXT_ATTR_FG_COLOR
+ { "", InvalidValue }, // ATK_TEXT_ATTR_BG_STIPPLE
+ { "", InvalidValue }, // ATK_TEXT_ATTR_FG_STIPPLE
+ { "", InvalidValue }, // ATK_TEXT_ATTR_WRAP_MODE
+ { "", InvalidValue }, // ATK_TEXT_ATTR_DIRECTION
+ { "ParaAdjust", Justification2Adjust }, // ATK_TEXT_ATTR_JUSTIFICATION
+ { "", InvalidValue }, // ATK_TEXT_ATTR_STRETCH
+ { "CharCaseMap", String2CaseMap }, // ATK_TEXT_ATTR_VARIANT
+ { "CharPosture", Style2FontSlant } // ATK_TEXT_ATTR_STYLE
+};
+
+/*****************************************************************************/
+
+bool
+attribute_set_map_to_property_values(
+ AtkAttributeSet* attribute_set,
+ uno::Sequence< beans::PropertyValue >& rValueList )
+{
+ // Ensure enough space ..
+ uno::Sequence< beans::PropertyValue > aAttributeList (SAL_N_ELEMENTS(g_TextAttrMap));
+ auto pAttributeList = aAttributeList.getArray();
+
+ sal_Int32 nIndex = 0;
+ for( GSList * item = attribute_set; item != nullptr; item = g_slist_next( item ) )
+ {
+ AtkAttribute* attribute = reinterpret_cast<AtkAttribute *>(item);
+
+ AtkTextAttribute text_attr = atk_text_attribute_for_name( attribute->name );
+ if( text_attr < SAL_N_ELEMENTS(g_TextAttrMap) )
+ {
+ if( g_TextAttrMap[text_attr].name[0] != '\0' )
+ {
+ if( ! g_TextAttrMap[text_attr].toPropertyValue( pAttributeList[nIndex].Value, attribute->value) )
+ return false;
+
+ pAttributeList[nIndex].Name = OUString::createFromAscii( g_TextAttrMap[text_attr].name );
+ pAttributeList[nIndex].State = beans::PropertyState_DIRECT_VALUE;
+ ++nIndex;
+ }
+ }
+ else
+ {
+ // Unsupported text attribute
+ return false;
+ }
+ }
+
+ aAttributeList.realloc( nIndex );
+ rValueList = aAttributeList;
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atktextattributes.hxx b/vcl/unx/gtk3/a11y/atktextattributes.hxx
new file mode 100644
index 000000000..716ec45bd
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atktextattributes.hxx
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/accessibility/XAccessibleExtendedAttributes.hpp>
+
+#include <atk/atk.h>
+
+AtkAttributeSet* attribute_set_new_from_property_values(
+ const css::uno::Sequence<css::beans::PropertyValue>& rAttributeList, bool run_attributes_only,
+ AtkText* text);
+
+AtkAttributeSet* attribute_set_new_from_extended_attributes(
+ const css::uno::Reference<css::accessibility::XAccessibleExtendedAttributes>&
+ rExtendedAttributes);
+
+bool attribute_set_map_to_property_values(
+ AtkAttributeSet* attribute_set, css::uno::Sequence<css::beans::PropertyValue>& rValueList);
+
+AtkAttributeSet* attribute_set_prepend_misspelled(AtkAttributeSet* attribute_set);
+// #i92232#
+AtkAttributeSet* attribute_set_prepend_tracked_change_insertion(AtkAttributeSet* attribute_set);
+AtkAttributeSet* attribute_set_prepend_tracked_change_deletion(AtkAttributeSet* attribute_set);
+AtkAttributeSet* attribute_set_prepend_tracked_change_formatchange(AtkAttributeSet* attribute_set);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkutil.cxx b/vcl/unx/gtk3/a11y/atkutil.cxx
new file mode 100644
index 000000000..a2c8b46b4
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkutil.cxx
@@ -0,0 +1,637 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifdef AIX
+#define _LINUX_SOURCE_COMPAT
+#include <sys/timer.h>
+#undef _LINUX_SOURCE_COMPAT
+#endif
+
+#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
+#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
+#include <com/sun/star/accessibility/AccessibleEventId.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/XAccessibleText.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/weakref.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/toolbox.hxx>
+
+#include <unx/gtk/gtkdata.hxx>
+#include "atkwrapper.hxx"
+#include "atkutil.hxx"
+
+#include <cassert>
+#include <set>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+ uno::WeakReference< accessibility::XAccessible > theNextFocusObject;
+}
+
+static guint focus_notify_handler = 0;
+
+/*****************************************************************************/
+
+extern "C" {
+
+static gboolean
+atk_wrapper_focus_idle_handler (gpointer data)
+{
+ SolarMutexGuard aGuard;
+
+ focus_notify_handler = 0;
+
+ uno::Reference< accessibility::XAccessible > xAccessible = theNextFocusObject;
+ if( xAccessible.get() == static_cast < accessibility::XAccessible * > (data) )
+ {
+ AtkObject *atk_obj = xAccessible.is() ? atk_object_wrapper_ref( xAccessible ) : nullptr;
+ // Gail does not notify focus changes to NULL, so do we ..
+ if( atk_obj )
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ atk_focus_tracker_notify(atk_obj);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ // #i93269#
+ // emit text_caret_moved event for <XAccessibleText> object,
+ // if cursor is inside the <XAccessibleText> object.
+ // also emit state-changed:focused event under the same condition.
+ {
+ AtkObjectWrapper* wrapper_obj = ATK_OBJECT_WRAPPER (atk_obj);
+ if( wrapper_obj && !wrapper_obj->mpText.is() )
+ {
+ wrapper_obj->mpText.set(wrapper_obj->mpContext, css::uno::UNO_QUERY);
+ if ( wrapper_obj->mpText.is() )
+ {
+ gint caretPos = -1;
+
+ try {
+ caretPos = wrapper_obj->mpText->getCaretPosition();
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getCaretPosition()" );
+ }
+
+ if ( caretPos != -1 )
+ {
+ atk_object_notify_state_change( atk_obj, ATK_STATE_FOCUSED, true );
+ g_signal_emit_by_name( atk_obj, "text_caret_moved", caretPos );
+ }
+ }
+ }
+ }
+ g_object_unref(atk_obj);
+ }
+ }
+
+ return false;
+}
+
+} // extern "C"
+
+/*****************************************************************************/
+
+static void
+atk_wrapper_focus_tracker_notify_when_idle( const uno::Reference< accessibility::XAccessible > &xAccessible )
+{
+ if( focus_notify_handler )
+ g_source_remove(focus_notify_handler);
+
+ theNextFocusObject = xAccessible;
+
+ focus_notify_handler = g_idle_add (atk_wrapper_focus_idle_handler, xAccessible.get());
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::disposing( const lang::EventObject& aEvent )
+{
+
+ // Unref the object here, but do not remove as listener since the object
+ // might no longer be in a state that safely allows this.
+ if( aEvent.Source.is() )
+ m_aRefList.erase(aEvent.Source);
+
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::notifyEvent( const accessibility::AccessibleEventObject& aEvent )
+{
+ try {
+ switch( aEvent.EventId )
+ {
+ case accessibility::AccessibleEventId::STATE_CHANGED:
+ {
+ sal_Int16 nState = accessibility::AccessibleStateType::INVALID;
+ aEvent.NewValue >>= nState;
+
+ if( accessibility::AccessibleStateType::FOCUSED == nState )
+ atk_wrapper_focus_tracker_notify_when_idle( getAccessible(aEvent) );
+
+ break;
+ }
+
+ case accessibility::AccessibleEventId::CHILD:
+ {
+ uno::Reference< accessibility::XAccessible > xChild;
+ if( (aEvent.OldValue >>= xChild) && xChild.is() )
+ detachRecursive(xChild);
+
+ if( (aEvent.NewValue >>= xChild) && xChild.is() )
+ attachRecursive(xChild);
+
+ break;
+ }
+
+ case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN:
+ SAL_INFO("vcl.a11y", "Invalidate all children called");
+ break;
+
+ default:
+ break;
+ }
+ }
+ catch( const lang::IndexOutOfBoundsException& )
+ {
+ g_warning("DocumentFocusListener: Focused object has invalid index in parent");
+ }
+}
+
+/*****************************************************************************/
+
+uno::Reference< accessibility::XAccessible > DocumentFocusListener::getAccessible(const lang::EventObject& aEvent )
+{
+ uno::Reference< accessibility::XAccessible > xAccessible(aEvent.Source, uno::UNO_QUERY);
+
+ if( xAccessible.is() )
+ return xAccessible;
+
+ uno::Reference< accessibility::XAccessibleContext > xContext(aEvent.Source, uno::UNO_QUERY);
+
+ if( xContext.is() )
+ {
+ uno::Reference< accessibility::XAccessible > xParent( xContext->getAccessibleParent() );
+ if( xParent.is() )
+ {
+ uno::Reference< accessibility::XAccessibleContext > xParentContext( xParent->getAccessibleContext() );
+ if( xParentContext.is() )
+ {
+ return xParentContext->getAccessibleChild( xContext->getAccessibleIndexInParent() );
+ }
+ }
+ }
+
+ return uno::Reference< accessibility::XAccessible >();
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible
+)
+{
+ uno::Reference< accessibility::XAccessibleContext > xContext =
+ xAccessible->getAccessibleContext();
+
+ if( xContext.is() )
+ attachRecursive(xAccessible, xContext);
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible,
+ const uno::Reference< accessibility::XAccessibleContext >& xContext
+)
+{
+ uno::Reference< accessibility::XAccessibleStateSet > xStateSet =
+ xContext->getAccessibleStateSet();
+
+ if( xStateSet.is() )
+ attachRecursive(xAccessible, xContext, xStateSet);
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible,
+ const uno::Reference< accessibility::XAccessibleContext >& xContext,
+ const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
+)
+{
+ if( xStateSet->contains(accessibility::AccessibleStateType::FOCUSED ) )
+ atk_wrapper_focus_tracker_notify_when_idle( xAccessible );
+
+ uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
+
+ if (!xBroadcaster.is())
+ return;
+
+ // If not already done, add the broadcaster to the list and attach as listener.
+ const uno::Reference< uno::XInterface >& xInterface = xBroadcaster;
+ if( !m_aRefList.insert(xInterface).second )
+ return;
+
+ xBroadcaster->addAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));
+
+ if( ! xStateSet->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS ) )
+ {
+ sal_Int32 n, nmax = xContext->getAccessibleChildCount();
+ for( n = 0; n < nmax; n++ )
+ {
+ uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) );
+
+ if( xChild.is() )
+ attachRecursive(xChild);
+ }
+ }
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::detachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible
+)
+{
+ uno::Reference< accessibility::XAccessibleContext > xContext =
+ xAccessible->getAccessibleContext();
+
+ if( xContext.is() )
+ detachRecursive(xContext);
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::detachRecursive(
+ const uno::Reference< accessibility::XAccessibleContext >& xContext
+)
+{
+ uno::Reference< accessibility::XAccessibleStateSet > xStateSet =
+ xContext->getAccessibleStateSet();
+
+ if( xStateSet.is() )
+ detachRecursive(xContext, xStateSet);
+}
+
+/*****************************************************************************/
+
+void DocumentFocusListener::detachRecursive(
+ const uno::Reference< accessibility::XAccessibleContext >& xContext,
+ const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
+)
+{
+ uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
+
+ if( !xBroadcaster.is() || 0 >= m_aRefList.erase(xBroadcaster) )
+ return;
+
+ xBroadcaster->removeAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));
+
+ if( ! xStateSet->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS ) )
+ {
+ sal_Int32 n, nmax = xContext->getAccessibleChildCount();
+ for( n = 0; n < nmax; n++ )
+ {
+ uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) );
+
+ if( xChild.is() )
+ detachRecursive(xChild);
+ }
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * page tabs in gtk are widgets, so we need to simulate focus events for those
+ */
+
+static void handle_tabpage_activated(vcl::Window *pWindow)
+{
+ uno::Reference< accessibility::XAccessible > xAccessible =
+ pWindow->GetAccessible();
+
+ if( ! xAccessible.is() )
+ return;
+
+ uno::Reference< accessibility::XAccessibleSelection > xSelection(
+ xAccessible->getAccessibleContext(), uno::UNO_QUERY);
+
+ if( xSelection.is() )
+ atk_wrapper_focus_tracker_notify_when_idle( xSelection->getSelectedAccessibleChild(0) );
+}
+
+/*****************************************************************************/
+
+/*
+ * toolbar items in gtk are widgets, so we need to simulate focus events for those
+ */
+
+static void notify_toolbox_item_focus(ToolBox *pToolBox)
+{
+ uno::Reference< accessibility::XAccessible > xAccessible =
+ pToolBox->GetAccessible();
+
+ if( ! xAccessible.is() )
+ return;
+
+ uno::Reference< accessibility::XAccessibleContext > xContext =
+ xAccessible->getAccessibleContext();
+
+ if( ! xContext.is() )
+ return;
+
+ ToolBox::ImplToolItems::size_type nPos = pToolBox->GetItemPos( pToolBox->GetHighlightItemId() );
+ if( nPos != ToolBox::ITEM_NOTFOUND )
+ atk_wrapper_focus_tracker_notify_when_idle( xContext->getAccessibleChild( nPos ) );
+ //TODO: ToolBox::ImplToolItems::size_type -> sal_Int32
+}
+
+static void handle_toolbox_highlight(vcl::Window *pWindow)
+{
+ ToolBox *pToolBox = static_cast <ToolBox *> (pWindow);
+
+ // Make sure either the toolbox or its parent toolbox has the focus
+ if ( ! pToolBox->HasFocus() )
+ {
+ ToolBox* pToolBoxParent = dynamic_cast< ToolBox* >( pToolBox->GetParent() );
+ if ( ! pToolBoxParent || ! pToolBoxParent->HasFocus() )
+ return;
+ }
+
+ notify_toolbox_item_focus(pToolBox);
+}
+
+static void handle_toolbox_highlightoff(vcl::Window const *pWindow)
+{
+ ToolBox* pToolBoxParent = dynamic_cast< ToolBox* >( pWindow->GetParent() );
+
+ // Notify when leaving sub toolboxes
+ if( pToolBoxParent && pToolBoxParent->HasFocus() )
+ notify_toolbox_item_focus( pToolBoxParent );
+}
+
+/*****************************************************************************/
+
+static void create_wrapper_for_child(
+ const uno::Reference< accessibility::XAccessibleContext >& xContext,
+ sal_Int32 index)
+{
+ if( xContext.is() )
+ {
+ uno::Reference< accessibility::XAccessible > xChild(xContext->getAccessibleChild(index));
+ if( xChild.is() )
+ {
+ // create the wrapper object - it will survive the unref unless it is a transient object
+ g_object_unref( atk_object_wrapper_ref( xChild ) );
+ }
+ }
+}
+
+/*****************************************************************************/
+
+static void handle_toolbox_buttonchange(VclWindowEvent const *pEvent)
+{
+ vcl::Window* pWindow = pEvent->GetWindow();
+ sal_Int32 index = static_cast<sal_Int32>(reinterpret_cast<sal_IntPtr>(pEvent->GetData()));
+
+ if( pWindow && pWindow->IsReallyVisible() )
+ {
+ uno::Reference< accessibility::XAccessible > xAccessible(pWindow->GetAccessible());
+ if( xAccessible.is() )
+ {
+ create_wrapper_for_child(xAccessible->getAccessibleContext(), index);
+ }
+ }
+}
+
+/*****************************************************************************/
+
+namespace {
+
+struct WindowList {
+ ~WindowList() { assert(list.empty()); };
+ // needs to be empty already on DeInitVCL, but at least check it's empty
+ // on exit
+
+ std::set< VclPtr<vcl::Window> > list;
+};
+
+WindowList g_aWindowList;
+
+}
+
+DocumentFocusListener & GtkSalData::GetDocumentFocusListener()
+{
+ if (!m_xDocumentFocusListener)
+ {
+ m_xDocumentFocusListener = new DocumentFocusListener;
+ }
+ return *m_xDocumentFocusListener;
+}
+
+static void handle_get_focus(::VclWindowEvent const * pEvent)
+{
+ GtkSalData *const pSalData(GetGtkSalData());
+ assert(pSalData);
+
+ DocumentFocusListener & rDocumentFocusListener(pSalData->GetDocumentFocusListener());
+
+ vcl::Window *pWindow = pEvent->GetWindow();
+
+ // The menu bar is handled through VclEventId::MenuHighlightED
+ if( ! pWindow || !pWindow->IsReallyVisible() || pWindow->GetType() == WindowType::MENUBARWINDOW )
+ return;
+
+ // ToolBoxes are handled through VclEventId::ToolboxHighlight
+ if( pWindow->GetType() == WindowType::TOOLBOX )
+ return;
+
+ if( pWindow->GetType() == WindowType::TABCONTROL )
+ {
+ handle_tabpage_activated( pWindow );
+ return;
+ }
+
+ uno::Reference< accessibility::XAccessible > xAccessible =
+ pWindow->GetAccessible();
+
+ if( ! xAccessible.is() )
+ return;
+
+ uno::Reference< accessibility::XAccessibleContext > xContext =
+ xAccessible->getAccessibleContext();
+
+ if( ! xContext.is() )
+ return;
+
+ uno::Reference< accessibility::XAccessibleStateSet > xStateSet =
+ xContext->getAccessibleStateSet();
+
+ if( ! xStateSet.is() )
+ return;
+
+/* the UNO ToolBox wrapper does not (yet?) support XAccessibleSelection, so we
+ * need to add listeners to the children instead of re-using the tabpage stuff
+ */
+ if( xStateSet->contains(accessibility::AccessibleStateType::FOCUSED) &&
+ ( pWindow->GetType() != WindowType::TREELISTBOX ) )
+ {
+ atk_wrapper_focus_tracker_notify_when_idle( xAccessible );
+ }
+ else
+ {
+ if( g_aWindowList.list.insert(pWindow).second )
+ {
+ try
+ {
+ rDocumentFocusListener.attachRecursive(xAccessible, xContext, xStateSet);
+ }
+ catch (const uno::Exception&)
+ {
+ g_warning( "Exception caught processing focus events" );
+ }
+ }
+ }
+}
+
+/*****************************************************************************/
+
+static void handle_menu_highlighted(::VclMenuEvent const * pEvent)
+{
+ try
+ {
+ Menu* pMenu = pEvent->GetMenu();
+ sal_uInt16 nPos = pEvent->GetItemPos();
+
+ if( pMenu && nPos != 0xFFFF)
+ {
+ uno::Reference< accessibility::XAccessible > xAccessible ( pMenu->GetAccessible() );
+
+ if( xAccessible.is() )
+ {
+ uno::Reference< accessibility::XAccessibleContext > xContext ( xAccessible->getAccessibleContext() );
+
+ if( xContext.is() )
+ atk_wrapper_focus_tracker_notify_when_idle( xContext->getAccessibleChild( nPos ) );
+ }
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ g_warning( "Exception caught processing menu highlight events" );
+ }
+}
+
+/*****************************************************************************/
+
+static void WindowEventHandler(void *, VclSimpleEvent& rEvent)
+{
+ try
+ {
+ switch (rEvent.GetId())
+ {
+ case VclEventId::WindowShow:
+ break;
+ case VclEventId::WindowHide:
+ break;
+ case VclEventId::WindowClose:
+ break;
+ case VclEventId::WindowGetFocus:
+ handle_get_focus(static_cast< ::VclWindowEvent const * >(&rEvent));
+ break;
+ case VclEventId::WindowLoseFocus:
+ break;
+ case VclEventId::WindowMinimize:
+ break;
+ case VclEventId::WindowNormalize:
+ break;
+ case VclEventId::WindowKeyInput:
+ case VclEventId::WindowKeyUp:
+ case VclEventId::WindowCommand:
+ case VclEventId::WindowMouseMove:
+ break;
+
+ case VclEventId::MenuHighlight:
+ if (const VclMenuEvent* pMenuEvent = dynamic_cast<const VclMenuEvent*>(&rEvent))
+ {
+ handle_menu_highlighted(pMenuEvent);
+ }
+ break;
+
+ case VclEventId::ToolboxHighlight:
+ handle_toolbox_highlight(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
+ break;
+
+ case VclEventId::ToolboxButtonStateChanged:
+ handle_toolbox_buttonchange(static_cast< ::VclWindowEvent const * >(&rEvent));
+ break;
+
+ case VclEventId::ObjectDying:
+ g_aWindowList.list.erase( static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow() );
+ [[fallthrough]];
+ case VclEventId::ToolboxHighlightOff:
+ handle_toolbox_highlightoff(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
+ break;
+
+ case VclEventId::TabpageActivate:
+ handle_tabpage_activated(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
+ break;
+
+ case VclEventId::ComboboxSetText:
+ // This looks quite strange to me. Stumbled over this when fixing #i104290#.
+ // This kicked in when leaving the combobox in the toolbar, after that the events worked.
+ // I guess this was a try to work around missing combobox events, which didn't do the full job, and shouldn't be necessary anymore.
+ // Fix for #i104290# was done in toolkit/source/awt/vclxaccessiblecomponent, FOCUSED state for compound controls in general.
+ // create_wrapper_for_children(static_cast< ::VclWindowEvent const * >(pEvent)->GetWindow());
+ break;
+
+ default:
+ break;
+ }
+ }
+ catch (const lang::IndexOutOfBoundsException&)
+ {
+ g_warning("WindowEventHandler: Focused object has invalid index in parent");
+ }
+}
+
+static Link<VclSimpleEvent&,void> g_aEventListenerLink( nullptr, WindowEventHandler );
+
+/*****************************************************************************/
+
+void ooo_atk_util_ensure_event_listener()
+{
+ static bool bInited;
+ if (!bInited)
+ {
+ Application::AddEventListener( g_aEventListenerLink );
+ bInited = true;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkutil.hxx b/vcl/unx/gtk3/a11y/atkutil.hxx
new file mode 100644
index 000000000..bc3d9d73b
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkutil.hxx
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <atk/atk.h>
+
+void ooo_atk_util_ensure_event_listener();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkvalue.cxx b/vcl/unx/gtk3/a11y/atkvalue.cxx
new file mode 100644
index 000000000..f5e45d3b2
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkvalue.cxx
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleValue.hpp>
+
+#include <string.h>
+
+using namespace ::com::sun::star;
+
+/// @throws uno::RuntimeException
+static css::uno::Reference<css::accessibility::XAccessibleValue>
+ getValue( AtkValue *pValue )
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pValue );
+ if( pWrap )
+ {
+ if( !pWrap->mpValue.is() )
+ {
+ pWrap->mpValue.set(pWrap->mpContext, css::uno::UNO_QUERY);
+ }
+
+ return pWrap->mpValue;
+ }
+
+ return css::uno::Reference<css::accessibility::XAccessibleValue>();
+}
+
+static void anyToGValue( const uno::Any& aAny, GValue *pValue )
+{
+ // FIXME: expand to lots of types etc.
+ double aDouble=0;
+ aAny >>= aDouble;
+
+ memset( pValue, 0, sizeof( GValue ) );
+ g_value_init( pValue, G_TYPE_DOUBLE );
+ g_value_set_double( pValue, aDouble );
+}
+
+extern "C" {
+
+static void
+value_wrapper_get_current_value( AtkValue *value,
+ GValue *gval )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleValue> pValue
+ = getValue( value );
+ if( pValue.is() )
+ anyToGValue( pValue->getCurrentValue(), gval );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getCurrentValue()" );
+ }
+}
+
+static void
+value_wrapper_get_maximum_value( AtkValue *value,
+ GValue *gval )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleValue> pValue
+ = getValue( value );
+ if( pValue.is() )
+ anyToGValue( pValue->getMaximumValue(), gval );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getCurrentValue()" );
+ }
+}
+
+static void
+value_wrapper_get_minimum_value( AtkValue *value,
+ GValue *gval )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleValue> pValue
+ = getValue( value );
+ if( pValue.is() )
+ anyToGValue( pValue->getMinimumValue(), gval );
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getCurrentValue()" );
+ }
+}
+
+static gboolean
+value_wrapper_set_current_value( AtkValue *value,
+ const GValue *gval )
+{
+ try {
+ css::uno::Reference<css::accessibility::XAccessibleValue> pValue
+ = getValue( value );
+ if( pValue.is() )
+ {
+ // FIXME - this needs expanding
+ double aDouble = g_value_get_double( gval );
+ return pValue->setCurrentValue( uno::Any(aDouble) );
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getCurrentValue()" );
+ }
+
+ return FALSE;
+}
+
+} // extern "C"
+
+void
+valueIfaceInit (AtkValueIface *iface)
+{
+ g_return_if_fail (iface != nullptr);
+
+ iface->get_current_value = value_wrapper_get_current_value;
+ iface->get_maximum_value = value_wrapper_get_maximum_value;
+ iface->get_minimum_value = value_wrapper_get_minimum_value;
+ iface->set_current_value = value_wrapper_set_current_value;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkwrapper.cxx b/vcl/unx/gtk3/a11y/atkwrapper.cxx
new file mode 100644
index 000000000..097a5e54d
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkwrapper.cxx
@@ -0,0 +1,1014 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/uno/Type.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/accessibility/AccessibleRelation.hpp>
+#include <com/sun/star/accessibility/AccessibleRelationType.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/XAccessible.hpp>
+#include <com/sun/star/accessibility/XAccessibleText.hpp>
+#include <com/sun/star/accessibility/XAccessibleValue.hpp>
+#include <com/sun/star/accessibility/XAccessibleAction.hpp>
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/XAccessibleContext2.hpp>
+#include <com/sun/star/accessibility/XAccessibleComponent.hpp>
+#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
+#include <com/sun/star/accessibility/XAccessibleStateSet.hpp>
+#include <com/sun/star/accessibility/XAccessibleRelationSet.hpp>
+#include <com/sun/star/accessibility/XAccessibleTable.hpp>
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+#include <com/sun/star/accessibility/XAccessibleExtendedAttributes.hpp>
+#include <com/sun/star/accessibility/XAccessibleImage.hpp>
+#include <com/sun/star/accessibility/XAccessibleHypertext.hpp>
+#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
+#include <com/sun/star/awt/XWindow.hpp>
+
+#include <rtl/strbuf.hxx>
+#include <osl/diagnose.h>
+#include <tools/diagnose_ex.h>
+#include <vcl/syschild.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/toolkit/unowrap.hxx>
+
+#include "atkwrapper.hxx"
+#include "atkregistry.hxx"
+#include "atklistener.hxx"
+#include "atktextattributes.hxx"
+
+#include <vector>
+#include <dlfcn.h>
+
+using namespace ::com::sun::star;
+
+static GObjectClass *parent_class = nullptr;
+
+static AtkRelationType mapRelationType( sal_Int16 nRelation )
+{
+ AtkRelationType type = ATK_RELATION_NULL;
+
+ switch( nRelation )
+ {
+ case accessibility::AccessibleRelationType::CONTENT_FLOWS_FROM:
+ type = ATK_RELATION_FLOWS_FROM;
+ break;
+
+ case accessibility::AccessibleRelationType::CONTENT_FLOWS_TO:
+ type = ATK_RELATION_FLOWS_TO;
+ break;
+
+ case accessibility::AccessibleRelationType::CONTROLLED_BY:
+ type = ATK_RELATION_CONTROLLED_BY;
+ break;
+
+ case accessibility::AccessibleRelationType::CONTROLLER_FOR:
+ type = ATK_RELATION_CONTROLLER_FOR;
+ break;
+
+ case accessibility::AccessibleRelationType::LABEL_FOR:
+ type = ATK_RELATION_LABEL_FOR;
+ break;
+
+ case accessibility::AccessibleRelationType::LABELED_BY:
+ type = ATK_RELATION_LABELLED_BY;
+ break;
+
+ case accessibility::AccessibleRelationType::MEMBER_OF:
+ type = ATK_RELATION_MEMBER_OF;
+ break;
+
+ case accessibility::AccessibleRelationType::SUB_WINDOW_OF:
+ type = ATK_RELATION_SUBWINDOW_OF;
+ break;
+
+ case accessibility::AccessibleRelationType::NODE_CHILD_OF:
+ type = ATK_RELATION_NODE_CHILD_OF;
+ break;
+
+ default:
+ break;
+ }
+
+ return type;
+}
+
+AtkStateType mapAtkState( sal_Int16 nState )
+{
+ AtkStateType type = ATK_STATE_INVALID;
+
+ // A perfect / complete mapping ...
+ switch( nState )
+ {
+#define MAP_DIRECT( a ) \
+ case accessibility::AccessibleStateType::a: \
+ type = ATK_STATE_##a; break
+
+ MAP_DIRECT( INVALID );
+ MAP_DIRECT( ACTIVE );
+ MAP_DIRECT( ARMED );
+ MAP_DIRECT( BUSY );
+ MAP_DIRECT( CHECKED );
+ MAP_DIRECT( EDITABLE );
+ MAP_DIRECT( ENABLED );
+ MAP_DIRECT( EXPANDABLE );
+ MAP_DIRECT( EXPANDED );
+ MAP_DIRECT( FOCUSABLE );
+ MAP_DIRECT( FOCUSED );
+ MAP_DIRECT( HORIZONTAL );
+ MAP_DIRECT( ICONIFIED );
+ MAP_DIRECT( INDETERMINATE );
+ MAP_DIRECT( MANAGES_DESCENDANTS );
+ MAP_DIRECT( MODAL );
+ MAP_DIRECT( MULTI_LINE );
+ MAP_DIRECT( OPAQUE );
+ MAP_DIRECT( PRESSED );
+ MAP_DIRECT( RESIZABLE );
+ MAP_DIRECT( SELECTABLE );
+ MAP_DIRECT( SELECTED );
+ MAP_DIRECT( SENSITIVE );
+ MAP_DIRECT( SHOWING );
+ MAP_DIRECT( SINGLE_LINE );
+ MAP_DIRECT( STALE );
+ MAP_DIRECT( TRANSIENT );
+ MAP_DIRECT( VERTICAL );
+ MAP_DIRECT( VISIBLE );
+ MAP_DIRECT( DEFAULT );
+ // a spelling error ...
+ case accessibility::AccessibleStateType::DEFUNC:
+ type = ATK_STATE_DEFUNCT; break;
+ case accessibility::AccessibleStateType::MULTI_SELECTABLE:
+ type = ATK_STATE_MULTISELECTABLE; break;
+ default:
+ //Mis-use ATK_STATE_LAST_DEFINED to check if a state is unmapped
+ //NOTE! Do not report it
+ type = ATK_STATE_LAST_DEFINED;
+ break;
+ }
+
+ return type;
+}
+
+static AtkRole mapToAtkRole( sal_Int16 nRole )
+{
+ switch (nRole)
+ {
+ case accessibility::AccessibleRole::UNKNOWN:
+ return ATK_ROLE_UNKNOWN;
+ case accessibility::AccessibleRole::ALERT:
+ return ATK_ROLE_ALERT;
+ case accessibility::AccessibleRole::COLUMN_HEADER:
+ return ATK_ROLE_COLUMN_HEADER;
+ case accessibility::AccessibleRole::CANVAS:
+ return ATK_ROLE_CANVAS;
+ case accessibility::AccessibleRole::CHECK_BOX:
+ return ATK_ROLE_CHECK_BOX;
+ case accessibility::AccessibleRole::CHECK_MENU_ITEM:
+ return ATK_ROLE_CHECK_MENU_ITEM;
+ case accessibility::AccessibleRole::COLOR_CHOOSER:
+ return ATK_ROLE_COLOR_CHOOSER;
+ case accessibility::AccessibleRole::COMBO_BOX:
+ return ATK_ROLE_COMBO_BOX;
+ case accessibility::AccessibleRole::DATE_EDITOR:
+ return ATK_ROLE_DATE_EDITOR;
+ case accessibility::AccessibleRole::DESKTOP_ICON:
+ return ATK_ROLE_DESKTOP_ICON;
+ case accessibility::AccessibleRole::DESKTOP_PANE:
+ return ATK_ROLE_DESKTOP_FRAME;
+ case accessibility::AccessibleRole::DIRECTORY_PANE:
+ return ATK_ROLE_DIRECTORY_PANE;
+ case accessibility::AccessibleRole::DIALOG:
+ return ATK_ROLE_DIALOG;
+ case accessibility::AccessibleRole::DOCUMENT:
+ return ATK_ROLE_DOCUMENT_FRAME;
+ case accessibility::AccessibleRole::EMBEDDED_OBJECT:
+ return ATK_ROLE_EMBEDDED;
+ case accessibility::AccessibleRole::END_NOTE:
+ return ATK_ROLE_FOOTNOTE;
+ case accessibility::AccessibleRole::FILE_CHOOSER:
+ return ATK_ROLE_FILE_CHOOSER;
+ case accessibility::AccessibleRole::FILLER:
+ return ATK_ROLE_FILLER;
+ case accessibility::AccessibleRole::FONT_CHOOSER:
+ return ATK_ROLE_FONT_CHOOSER;
+ case accessibility::AccessibleRole::FOOTER:
+ return ATK_ROLE_FOOTER;
+ case accessibility::AccessibleRole::FOOTNOTE:
+ return ATK_ROLE_FOOTNOTE;
+ case accessibility::AccessibleRole::FRAME:
+ return ATK_ROLE_FRAME;
+ case accessibility::AccessibleRole::GLASS_PANE:
+ return ATK_ROLE_GLASS_PANE;
+ case accessibility::AccessibleRole::GRAPHIC:
+ return ATK_ROLE_IMAGE;
+ case accessibility::AccessibleRole::GROUP_BOX:
+ return ATK_ROLE_GROUPING;
+ case accessibility::AccessibleRole::HEADER:
+ return ATK_ROLE_HEADER;
+ case accessibility::AccessibleRole::HEADING:
+ return ATK_ROLE_HEADING;
+ case accessibility::AccessibleRole::HYPER_LINK:
+ return ATK_ROLE_LINK;
+ case accessibility::AccessibleRole::ICON:
+ return ATK_ROLE_ICON;
+ case accessibility::AccessibleRole::INTERNAL_FRAME:
+ return ATK_ROLE_INTERNAL_FRAME;
+ case accessibility::AccessibleRole::LABEL:
+ return ATK_ROLE_LABEL;
+ case accessibility::AccessibleRole::LAYERED_PANE:
+ return ATK_ROLE_LAYERED_PANE;
+ case accessibility::AccessibleRole::LIST:
+ return ATK_ROLE_LIST;
+ case accessibility::AccessibleRole::LIST_ITEM:
+ return ATK_ROLE_LIST_ITEM;
+ case accessibility::AccessibleRole::MENU:
+ return ATK_ROLE_MENU;
+ case accessibility::AccessibleRole::MENU_BAR:
+ return ATK_ROLE_MENU_BAR;
+ case accessibility::AccessibleRole::MENU_ITEM:
+ return ATK_ROLE_MENU_ITEM;
+ case accessibility::AccessibleRole::OPTION_PANE:
+ return ATK_ROLE_OPTION_PANE;
+ case accessibility::AccessibleRole::PAGE_TAB:
+ return ATK_ROLE_PAGE_TAB;
+ case accessibility::AccessibleRole::PAGE_TAB_LIST:
+ return ATK_ROLE_PAGE_TAB_LIST;
+ case accessibility::AccessibleRole::PANEL:
+ return ATK_ROLE_PANEL;
+ case accessibility::AccessibleRole::PARAGRAPH:
+ return ATK_ROLE_PARAGRAPH;
+ case accessibility::AccessibleRole::PASSWORD_TEXT:
+ return ATK_ROLE_PASSWORD_TEXT;
+ case accessibility::AccessibleRole::POPUP_MENU:
+ return ATK_ROLE_POPUP_MENU;
+ case accessibility::AccessibleRole::PUSH_BUTTON:
+ return ATK_ROLE_PUSH_BUTTON;
+ case accessibility::AccessibleRole::PROGRESS_BAR:
+ return ATK_ROLE_PROGRESS_BAR;
+ case accessibility::AccessibleRole::RADIO_BUTTON:
+ return ATK_ROLE_RADIO_BUTTON;
+ case accessibility::AccessibleRole::RADIO_MENU_ITEM:
+ return ATK_ROLE_RADIO_MENU_ITEM;
+ case accessibility::AccessibleRole::ROW_HEADER:
+ return ATK_ROLE_ROW_HEADER;
+ case accessibility::AccessibleRole::ROOT_PANE:
+ return ATK_ROLE_ROOT_PANE;
+ case accessibility::AccessibleRole::SCROLL_BAR:
+ return ATK_ROLE_SCROLL_BAR;
+ case accessibility::AccessibleRole::SCROLL_PANE:
+ return ATK_ROLE_SCROLL_PANE;
+ case accessibility::AccessibleRole::SHAPE:
+ return ATK_ROLE_PANEL;
+ case accessibility::AccessibleRole::SEPARATOR:
+ return ATK_ROLE_SEPARATOR;
+ case accessibility::AccessibleRole::SLIDER:
+ return ATK_ROLE_SLIDER;
+ case accessibility::AccessibleRole::SPIN_BOX:
+ return ATK_ROLE_SPIN_BUTTON;
+ case accessibility::AccessibleRole::SPLIT_PANE:
+ return ATK_ROLE_SPLIT_PANE;
+ case accessibility::AccessibleRole::STATUS_BAR:
+ return ATK_ROLE_STATUSBAR;
+ case accessibility::AccessibleRole::TABLE:
+ return ATK_ROLE_TABLE;
+ case accessibility::AccessibleRole::TABLE_CELL:
+ return ATK_ROLE_TABLE_CELL;
+ case accessibility::AccessibleRole::TEXT:
+ return ATK_ROLE_TEXT;
+ case accessibility::AccessibleRole::TEXT_FRAME:
+ return ATK_ROLE_PANEL;
+ case accessibility::AccessibleRole::TOGGLE_BUTTON:
+ return ATK_ROLE_TOGGLE_BUTTON;
+ case accessibility::AccessibleRole::TOOL_BAR:
+ return ATK_ROLE_TOOL_BAR;
+ case accessibility::AccessibleRole::TOOL_TIP:
+ return ATK_ROLE_TOOL_TIP;
+ case accessibility::AccessibleRole::TREE:
+ return ATK_ROLE_TREE;
+ case accessibility::AccessibleRole::VIEW_PORT:
+ return ATK_ROLE_VIEWPORT;
+ case accessibility::AccessibleRole::WINDOW:
+ return ATK_ROLE_WINDOW;
+ case accessibility::AccessibleRole::BUTTON_DROPDOWN:
+ return ATK_ROLE_PUSH_BUTTON;
+ case accessibility::AccessibleRole::BUTTON_MENU:
+ return ATK_ROLE_PUSH_BUTTON;
+ case accessibility::AccessibleRole::CAPTION:
+ return ATK_ROLE_CAPTION;
+ case accessibility::AccessibleRole::CHART:
+ return ATK_ROLE_CHART;
+ case accessibility::AccessibleRole::EDIT_BAR:
+ return ATK_ROLE_EDITBAR;
+ case accessibility::AccessibleRole::FORM:
+ return ATK_ROLE_FORM;
+ case accessibility::AccessibleRole::IMAGE_MAP:
+ return ATK_ROLE_IMAGE_MAP;
+ case accessibility::AccessibleRole::NOTE:
+ return ATK_ROLE_COMMENT;
+ case accessibility::AccessibleRole::PAGE:
+ return ATK_ROLE_PAGE;
+ case accessibility::AccessibleRole::RULER:
+ return ATK_ROLE_RULER;
+ case accessibility::AccessibleRole::SECTION:
+ return ATK_ROLE_SECTION;
+ case accessibility::AccessibleRole::TREE_ITEM:
+ return ATK_ROLE_TREE_ITEM;
+ case accessibility::AccessibleRole::TREE_TABLE:
+ return ATK_ROLE_TREE_TABLE;
+ case accessibility::AccessibleRole::COMMENT:
+ return ATK_ROLE_COMMENT;
+ case accessibility::AccessibleRole::COMMENT_END:
+ return ATK_ROLE_UNKNOWN;
+ case accessibility::AccessibleRole::DOCUMENT_PRESENTATION:
+ return ATK_ROLE_DOCUMENT_PRESENTATION;
+ case accessibility::AccessibleRole::DOCUMENT_SPREADSHEET:
+ return ATK_ROLE_DOCUMENT_SPREADSHEET;
+ case accessibility::AccessibleRole::DOCUMENT_TEXT:
+ return ATK_ROLE_DOCUMENT_TEXT;
+ case accessibility::AccessibleRole::STATIC:
+ return ATK_ROLE_STATIC;
+ default:
+ SAL_WARN("vcl.gtk", "Unmapped accessible role: " << nRole);
+ return ATK_ROLE_UNKNOWN;
+ }
+}
+
+/*****************************************************************************/
+
+extern "C" {
+
+/*****************************************************************************/
+
+static const gchar*
+wrapper_get_name( AtkObject *atk_obj )
+{
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
+
+ if( obj->mpContext.is() )
+ {
+ try {
+ OString aName =
+ OUStringToOString(
+ obj->mpContext->getAccessibleName(),
+ RTL_TEXTENCODING_UTF8);
+
+ int nCmp = atk_obj->name ? rtl_str_compare( atk_obj->name, aName.getStr() ) : -1;
+ if( nCmp != 0 )
+ {
+ if( atk_obj->name )
+ g_free(atk_obj->name);
+ atk_obj->name = g_strdup(aName.getStr());
+ }
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleName()" );
+ }
+ }
+
+ return ATK_OBJECT_CLASS (parent_class)->get_name(atk_obj);
+}
+
+/*****************************************************************************/
+
+static const gchar*
+wrapper_get_description( AtkObject *atk_obj )
+{
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
+
+ if( obj->mpContext.is() )
+ {
+ try {
+ OString aDescription =
+ OUStringToOString(
+ obj->mpContext->getAccessibleDescription(),
+ RTL_TEXTENCODING_UTF8);
+
+ g_free(atk_obj->description);
+ atk_obj->description = g_strdup(aDescription.getStr());
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleDescription()" );
+ }
+ }
+
+ return ATK_OBJECT_CLASS (parent_class)->get_description(atk_obj);
+
+}
+
+/*****************************************************************************/
+
+static AtkAttributeSet *
+wrapper_get_attributes( AtkObject *atk_obj )
+{
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER( atk_obj );
+ AtkAttributeSet *pSet = nullptr;
+
+ try
+ {
+ uno::Reference< accessibility::XAccessibleExtendedAttributes >
+ xExtendedAttrs( obj->mpContext, uno::UNO_QUERY );
+ if( xExtendedAttrs.is() )
+ pSet = attribute_set_new_from_extended_attributes( xExtendedAttrs );
+ }
+ catch(const uno::Exception&)
+ {
+ g_warning( "Exception in getAccessibleAttributes()" );
+ }
+
+ return pSet;
+}
+
+/*****************************************************************************/
+
+static gint
+wrapper_get_n_children( AtkObject *atk_obj )
+{
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
+
+ if (obj->mpSysObjChild)
+ return 1;
+
+ gint n = 0;
+
+ if( obj->mpContext.is() )
+ {
+ try {
+ n = obj->mpContext->getAccessibleChildCount();
+ }
+ catch(const uno::Exception&) {
+ TOOLS_WARN_EXCEPTION( "vcl", "Exception" );
+ }
+ }
+
+ return n;
+}
+
+/*****************************************************************************/
+
+static AtkObject *
+wrapper_ref_child( AtkObject *atk_obj,
+ gint i )
+{
+ SolarMutexGuard aGuard;
+
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
+
+ if (obj->mpSysObjChild)
+ {
+ g_object_ref(obj->mpSysObjChild);
+ return obj->mpSysObjChild;
+ }
+
+ AtkObject* child = nullptr;
+
+ // see comments above atk_object_wrapper_remove_child
+ if( -1 < i && obj->index_of_child_about_to_be_removed == i )
+ {
+ g_object_ref( obj->child_about_to_be_removed );
+ return obj->child_about_to_be_removed;
+ }
+
+ if( obj->mpContext.is() )
+ {
+ try {
+ uno::Reference< accessibility::XAccessible > xAccessible =
+ obj->mpContext->getAccessibleChild( i );
+
+ child = atk_object_wrapper_ref( xAccessible );
+ }
+ catch(const uno::Exception&) {
+ TOOLS_WARN_EXCEPTION( "vcl", "getAccessibleChild");
+ }
+ }
+
+ return child;
+}
+
+/*****************************************************************************/
+
+static gint
+wrapper_get_index_in_parent( AtkObject *atk_obj )
+{
+ SolarMutexGuard aGuard;
+
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
+
+ //if we're a native GtkDrawingArea with custom a11y, use the default toolkit a11y
+ if (obj->mpOrig)
+ return atk_object_get_index_in_parent(obj->mpOrig);
+
+ gint i = -1;
+
+ if( obj->mpContext.is() )
+ {
+ try {
+ i = obj->mpContext->getAccessibleIndexInParent();
+ }
+ catch(const uno::Exception&) {
+ g_warning( "Exception in getAccessibleIndexInParent()" );
+ }
+ }
+ return i;
+}
+
+/*****************************************************************************/
+
+AtkRelation*
+atk_object_wrapper_relation_new(const accessibility::AccessibleRelation& rRelation)
+{
+ sal_uInt32 nTargetCount = rRelation.TargetSet.getLength();
+
+ std::vector<AtkObject*> aTargets;
+
+ for (const auto& rTarget : rRelation.TargetSet)
+ {
+ uno::Reference< accessibility::XAccessible > xAccessible( rTarget, uno::UNO_QUERY );
+ aTargets.push_back(atk_object_wrapper_ref(xAccessible));
+ }
+
+ AtkRelation *pRel =
+ atk_relation_new(
+ aTargets.data(), nTargetCount,
+ mapRelationType( rRelation.RelationType )
+ );
+
+ return pRel;
+}
+
+static AtkRelationSet *
+wrapper_ref_relation_set( AtkObject *atk_obj )
+{
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
+
+ //if we're a native GtkDrawingArea with custom a11y, use the default toolkit relation set impl
+ if (obj->mpOrig)
+ return atk_object_ref_relation_set(obj->mpOrig);
+
+ AtkRelationSet *pSet = atk_relation_set_new();
+
+ if( obj->mpContext.is() )
+ {
+ try {
+ uno::Reference< accessibility::XAccessibleRelationSet > xRelationSet(
+ obj->mpContext->getAccessibleRelationSet()
+ );
+
+ sal_Int32 nRelations = xRelationSet.is() ? xRelationSet->getRelationCount() : 0;
+ for( sal_Int32 n = 0; n < nRelations; n++ )
+ {
+ AtkRelation *pRel = atk_object_wrapper_relation_new(xRelationSet->getRelation(n));
+ atk_relation_set_add(pSet, pRel);
+ g_object_unref(pRel);
+ }
+ }
+ catch(const uno::Exception &) {
+ g_object_unref( G_OBJECT( pSet ) );
+ pSet = nullptr;
+ }
+ }
+
+ return pSet;
+}
+
+static AtkStateSet *
+wrapper_ref_state_set( AtkObject *atk_obj )
+{
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
+ AtkStateSet *pSet = atk_state_set_new();
+
+ if( obj->mpContext.is() )
+ {
+ try {
+ uno::Reference< accessibility::XAccessibleStateSet > xStateSet(
+ obj->mpContext->getAccessibleStateSet());
+
+ if( xStateSet.is() )
+ {
+ uno::Sequence< sal_Int16 > aStates = xStateSet->getStates();
+
+ for( const auto nState : aStates )
+ {
+ // ATK_STATE_LAST_DEFINED is used to check if the state
+ // is unmapped, do not report it to Atk
+ if ( mapAtkState( nState ) != ATK_STATE_LAST_DEFINED )
+ atk_state_set_add_state( pSet, mapAtkState( nState ) );
+ }
+
+ // We need to emulate FOCUS state for menus, menu-items etc.
+ if( atk_obj == atk_get_focus_object() )
+ atk_state_set_add_state( pSet, ATK_STATE_FOCUSED );
+/* FIXME - should we do this ?
+ else
+ atk_state_set_remove_state( pSet, ATK_STATE_FOCUSED );
+*/
+ }
+ }
+
+ catch(const uno::Exception &) {
+ g_warning( "Exception in wrapper_ref_state_set" );
+ atk_state_set_add_state( pSet, ATK_STATE_DEFUNCT );
+ }
+ }
+ else
+ atk_state_set_add_state( pSet, ATK_STATE_DEFUNCT );
+
+ return pSet;
+}
+
+/*****************************************************************************/
+
+static void
+atk_object_wrapper_finalize (GObject *obj)
+{
+ AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER (obj);
+
+ if( pWrap->mpAccessible.is() )
+ {
+ ooo_wrapper_registry_remove( pWrap->mpAccessible );
+ SolarMutexGuard aGuard;
+ pWrap->mpAccessible.clear();
+ }
+
+ atk_object_wrapper_dispose( pWrap );
+
+ parent_class->finalize( obj );
+}
+
+static void
+atk_object_wrapper_class_init (AtkObjectWrapperClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS( klass );
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS( klass );
+
+ parent_class = static_cast<GObjectClass *>(g_type_class_peek_parent (klass));
+
+ // GObject methods
+ gobject_class->finalize = atk_object_wrapper_finalize;
+
+ // AtkObject methods
+ atk_class->get_name = wrapper_get_name;
+ atk_class->get_description = wrapper_get_description;
+ atk_class->get_attributes = wrapper_get_attributes;
+ atk_class->get_n_children = wrapper_get_n_children;
+ atk_class->ref_child = wrapper_ref_child;
+ atk_class->get_index_in_parent = wrapper_get_index_in_parent;
+ atk_class->ref_relation_set = wrapper_ref_relation_set;
+ atk_class->ref_state_set = wrapper_ref_state_set;
+
+ AtkObjectClass* orig_atk_klass = static_cast<AtkObjectClass*>(g_type_class_ref(ATK_TYPE_OBJECT));
+ // tdf#150496 we want to inherit from GtkAccessible because gtk assumes it can cast to GtkAccessible
+ // but we want the original behaviour we got from atk_object_real_get_parent when we inherited
+ // from AtkObject
+ atk_class->get_parent = orig_atk_klass->get_parent;
+ g_type_class_unref(orig_atk_klass);
+}
+
+static void
+atk_object_wrapper_init (AtkObjectWrapper *wrapper,
+ AtkObjectWrapperClass*)
+{
+ wrapper->mpAction = nullptr;
+ wrapper->mpComponent = nullptr;
+ wrapper->mpEditableText = nullptr;
+ wrapper->mpHypertext = nullptr;
+ wrapper->mpImage = nullptr;
+ wrapper->mpSelection = nullptr;
+ wrapper->mpTable = nullptr;
+ wrapper->mpText = nullptr;
+ wrapper->mpValue = nullptr;
+}
+
+} // extern "C"
+
+GType
+atk_object_wrapper_get_type()
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ static const GTypeInfo typeInfo =
+ {
+ sizeof (AtkObjectWrapperClass),
+ nullptr,
+ nullptr,
+ reinterpret_cast<GClassInitFunc>(atk_object_wrapper_class_init),
+ nullptr,
+ nullptr,
+ sizeof (AtkObjectWrapper),
+ 0,
+ reinterpret_cast<GInstanceInitFunc>(atk_object_wrapper_init),
+ nullptr
+ } ;
+ type = g_type_register_static (GTK_TYPE_WIDGET_ACCESSIBLE,
+ "OOoAtkObj",
+ &typeInfo, GTypeFlags(0)) ;
+ }
+ return type;
+}
+
+static bool
+isOfType( uno::XInterface *pInterface, const uno::Type & rType )
+{
+ g_return_val_if_fail( pInterface != nullptr, false );
+
+ bool bIs = false;
+ try {
+ uno::Any aRet = pInterface->queryInterface( rType );
+
+ bIs = ( ( typelib_TypeClass_INTERFACE == aRet.pType->eTypeClass ) &&
+ ( aRet.pReserved != nullptr ) );
+ } catch( const uno::Exception &) { }
+
+ return bIs;
+}
+
+extern "C" {
+typedef GType (* GetGIfaceType ) ();
+}
+const struct {
+ const char *name;
+ GInterfaceInitFunc const aInit;
+ GetGIfaceType const aGetGIfaceType;
+ const uno::Type & (*aGetUnoType) ();
+} aTypeTable[] = {
+// re-location heaven:
+ {
+ "Comp", reinterpret_cast<GInterfaceInitFunc>(componentIfaceInit),
+ atk_component_get_type,
+ cppu::UnoType<accessibility::XAccessibleComponent>::get
+ },
+ {
+ "Act", reinterpret_cast<GInterfaceInitFunc>(actionIfaceInit),
+ atk_action_get_type,
+ cppu::UnoType<accessibility::XAccessibleAction>::get
+ },
+ {
+ "Txt", reinterpret_cast<GInterfaceInitFunc>(textIfaceInit),
+ atk_text_get_type,
+ cppu::UnoType<accessibility::XAccessibleText>::get
+ },
+ {
+ "Val", reinterpret_cast<GInterfaceInitFunc>(valueIfaceInit),
+ atk_value_get_type,
+ cppu::UnoType<accessibility::XAccessibleValue>::get
+ },
+ {
+ "Tab", reinterpret_cast<GInterfaceInitFunc>(tableIfaceInit),
+ atk_table_get_type,
+ cppu::UnoType<accessibility::XAccessibleTable>::get
+ },
+ {
+ "Edt", reinterpret_cast<GInterfaceInitFunc>(editableTextIfaceInit),
+ atk_editable_text_get_type,
+ cppu::UnoType<accessibility::XAccessibleEditableText>::get
+ },
+ {
+ "Img", reinterpret_cast<GInterfaceInitFunc>(imageIfaceInit),
+ atk_image_get_type,
+ cppu::UnoType<accessibility::XAccessibleImage>::get
+ },
+ {
+ "Hyp", reinterpret_cast<GInterfaceInitFunc>(hypertextIfaceInit),
+ atk_hypertext_get_type,
+ cppu::UnoType<accessibility::XAccessibleHypertext>::get
+ },
+ {
+ "Sel", reinterpret_cast<GInterfaceInitFunc>(selectionIfaceInit),
+ atk_selection_get_type,
+ cppu::UnoType<accessibility::XAccessibleSelection>::get
+ }
+ // AtkDocument is a nastily broken interface (so far)
+ // we could impl. get_document_type perhaps though.
+};
+
+const int aTypeTableSize = G_N_ELEMENTS( aTypeTable );
+
+static GType
+ensureTypeFor( uno::XInterface *pAccessible )
+{
+ int i;
+ bool bTypes[ aTypeTableSize ] = { false, };
+ OStringBuffer aTypeNameBuf( "OOoAtkObj" );
+
+ for( i = 0; i < aTypeTableSize; i++ )
+ {
+ if( isOfType( pAccessible, aTypeTable[i].aGetUnoType() ) )
+ {
+ aTypeNameBuf.append(aTypeTable[i].name);
+ bTypes[i] = true;
+ }
+ }
+
+ OString aTypeName = aTypeNameBuf.makeStringAndClear();
+ GType nType = g_type_from_name( aTypeName.getStr() );
+ if( nType == G_TYPE_INVALID )
+ {
+ GTypeInfo aTypeInfo = {
+ sizeof( AtkObjectWrapperClass ),
+ nullptr, nullptr, nullptr, nullptr, nullptr,
+ sizeof( AtkObjectWrapper ),
+ 0, nullptr, nullptr
+ } ;
+ nType = g_type_register_static( ATK_TYPE_OBJECT_WRAPPER,
+ aTypeName.getStr(), &aTypeInfo,
+ GTypeFlags(0) ) ;
+
+ for( int j = 0; j < aTypeTableSize; j++ )
+ if( bTypes[j] )
+ {
+ GInterfaceInfo aIfaceInfo = { nullptr, nullptr, nullptr };
+ aIfaceInfo.interface_init = aTypeTable[j].aInit;
+ g_type_add_interface_static (nType, aTypeTable[j].aGetGIfaceType(),
+ &aIfaceInfo);
+ }
+ }
+ return nType;
+}
+
+AtkObject *
+atk_object_wrapper_ref( const uno::Reference< accessibility::XAccessible > &rxAccessible, bool create )
+{
+ g_return_val_if_fail( bool(rxAccessible), nullptr );
+
+ AtkObject *obj = ooo_wrapper_registry_get(rxAccessible);
+ if( obj )
+ {
+ g_object_ref( obj );
+ return obj;
+ }
+
+ if( create )
+ return atk_object_wrapper_new( rxAccessible );
+
+ return nullptr;
+}
+
+AtkObject *
+atk_object_wrapper_new( const css::uno::Reference< css::accessibility::XAccessible >& rxAccessible,
+ AtkObject* parent, AtkObject* orig )
+{
+ g_return_val_if_fail( bool(rxAccessible), nullptr );
+
+ AtkObjectWrapper *pWrap = nullptr;
+
+ try {
+ uno::Reference< accessibility::XAccessibleContext > xContext(rxAccessible->getAccessibleContext());
+
+ g_return_val_if_fail( bool(xContext), nullptr );
+
+ GType nType = ensureTypeFor( xContext.get() );
+ gpointer obj = g_object_new( nType, nullptr);
+
+ pWrap = ATK_OBJECT_WRAPPER( obj );
+ pWrap->mpAccessible = rxAccessible;
+
+ pWrap->index_of_child_about_to_be_removed = -1;
+ pWrap->child_about_to_be_removed = nullptr;
+
+ pWrap->mpContext = xContext;
+ pWrap->mpOrig = orig;
+
+ AtkObject* atk_obj = ATK_OBJECT(pWrap);
+ atk_obj->role = mapToAtkRole( xContext->getAccessibleRole() );
+ atk_obj->accessible_parent = parent;
+
+ ooo_wrapper_registry_add( rxAccessible, atk_obj );
+
+ if( parent )
+ g_object_ref( atk_obj->accessible_parent );
+ else
+ {
+ /* gail_focus_tracker remembers the focused object at the first
+ * parent in the hierarchy that is a Gtk+ widget, but at the time the
+ * event gets processed (at idle), it may be too late to create the
+ * hierarchy, so doing it now ..
+ */
+ uno::Reference< accessibility::XAccessible > xParent( xContext->getAccessibleParent() );
+
+ if( xParent.is() )
+ atk_obj->accessible_parent = atk_object_wrapper_ref( xParent );
+ }
+
+ // Attach a listener to the UNO object if it's not TRANSIENT
+ uno::Reference< accessibility::XAccessibleStateSet > xStateSet( xContext->getAccessibleStateSet() );
+ if( xStateSet.is() && ! xStateSet->contains( accessibility::AccessibleStateType::TRANSIENT ) )
+ {
+ uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
+ if( xBroadcaster.is() )
+ {
+ uno::Reference<accessibility::XAccessibleEventListener> xListener(new AtkListener(pWrap));
+ xBroadcaster->addAccessibleEventListener(xListener);
+ }
+ else
+ OSL_ASSERT( false );
+ }
+
+ static auto func = reinterpret_cast<void(*)(AtkObject*, const gchar*)>(dlsym(nullptr, "atk_object_set_accessible_id"));
+ if (func)
+ {
+ css::uno::Reference<css::accessibility::XAccessibleContext2> xContext2(xContext, css::uno::UNO_QUERY);
+ if( xContext2.is() )
+ {
+ OString aId = OUStringToOString( xContext2->getAccessibleId(), RTL_TEXTENCODING_UTF8);
+ (*func)(atk_obj, aId.getStr());
+ }
+ }
+
+ // tdf#141197 if we have a sysobj child then include that in the hierarchy
+ if (UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper())
+ {
+ css::uno::Reference<css::awt::XWindow> xAWTWindow(rxAccessible, css::uno::UNO_QUERY);
+ VclPtr<vcl::Window> xWindow = pWrapper->GetWindow(xAWTWindow);
+ if (xWindow && xWindow->GetType() == WindowType::SYSTEMCHILDWINDOW)
+ {
+ const SystemEnvData* pEnvData = static_cast<SystemChildWindow*>(xWindow.get())->GetSystemData();
+ if (GtkWidget *pSysObj = pEnvData ? static_cast<GtkWidget*>(pEnvData->pWidget) : nullptr)
+ pWrap->mpSysObjChild = gtk_widget_get_accessible(pSysObj);
+ }
+ }
+
+ return ATK_OBJECT( pWrap );
+ }
+ catch (const uno::Exception &)
+ {
+ if( pWrap )
+ g_object_unref( pWrap );
+
+ return nullptr;
+ }
+}
+
+/*****************************************************************************/
+
+void atk_object_wrapper_add_child(AtkObjectWrapper* wrapper, AtkObject *child, gint index)
+{
+ AtkObject *atk_obj = ATK_OBJECT( wrapper );
+
+ atk_object_set_parent( child, atk_obj );
+ g_signal_emit_by_name( atk_obj, "children_changed::add", index, child, nullptr );
+}
+
+/*****************************************************************************/
+
+void atk_object_wrapper_remove_child(AtkObjectWrapper* wrapper, AtkObject *child, gint index)
+{
+ /*
+ * the atk-bridge GTK+ module gets back to the event source to ref the child just
+ * vanishing, so we keep this reference because the semantic on OOo side is different.
+ */
+ wrapper->child_about_to_be_removed = child;
+ wrapper->index_of_child_about_to_be_removed = index;
+
+ g_signal_emit_by_name( ATK_OBJECT( wrapper ), "children_changed::remove", index, child, nullptr );
+
+ wrapper->index_of_child_about_to_be_removed = -1;
+ wrapper->child_about_to_be_removed = nullptr;
+}
+
+/*****************************************************************************/
+
+void atk_object_wrapper_set_role(AtkObjectWrapper* wrapper, sal_Int16 role)
+{
+ AtkObject *atk_obj = ATK_OBJECT( wrapper );
+ atk_object_set_role( atk_obj, mapToAtkRole( role ) );
+}
+
+/*****************************************************************************/
+
+void atk_object_wrapper_dispose(AtkObjectWrapper* wrapper)
+{
+ wrapper->mpContext.clear();
+ wrapper->mpAction.clear();
+ wrapper->mpComponent.clear();
+ wrapper->mpEditableText.clear();
+ wrapper->mpHypertext.clear();
+ wrapper->mpImage.clear();
+ wrapper->mpSelection.clear();
+ wrapper->mpMultiLineText.clear();
+ wrapper->mpTable.clear();
+ wrapper->mpText.clear();
+ wrapper->mpTextMarkup.clear();
+ wrapper->mpTextAttributes.clear();
+ wrapper->mpValue.clear();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/atkwrapper.hxx b/vcl/unx/gtk3/a11y/atkwrapper.hxx
new file mode 100644
index 000000000..6f18bde61
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkwrapper.hxx
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <atk/atk.h>
+#include <gtk/gtk.h>
+#include <gtk/gtk-a11y.h>
+#include <com/sun/star/accessibility/XAccessible.hpp>
+
+extern "C" {
+
+namespace com::sun::star::accessibility {
+ class XAccessibleAction;
+ class XAccessibleComponent;
+ class XAccessibleEditableText;
+ class XAccessibleHypertext;
+ class XAccessibleImage;
+ class XAccessibleMultiLineText;
+ class XAccessibleSelection;
+ class XAccessibleTable;
+ class XAccessibleText;
+ class XAccessibleTextMarkup;
+ class XAccessibleTextAttributes;
+ class XAccessibleValue;
+}
+
+struct AtkObjectWrapper
+{
+ GtkWidgetAccessible aParent;
+
+ AtkObject* mpOrig; //if we're a GtkDrawingArea acting as a custom LibreOffice widget, this is the toolkit default impl
+ AtkObject* mpSysObjChild; //if we're a container for a sysobj, then this is the sysobj native gtk AtkObject
+
+ css::uno::Reference<css::accessibility::XAccessible> mpAccessible;
+ css::uno::Reference<css::accessibility::XAccessibleContext> mpContext;
+ css::uno::Reference<css::accessibility::XAccessibleAction> mpAction;
+ css::uno::Reference<css::accessibility::XAccessibleComponent> mpComponent;
+ css::uno::Reference<css::accessibility::XAccessibleEditableText>
+ mpEditableText;
+ css::uno::Reference<css::accessibility::XAccessibleHypertext> mpHypertext;
+ css::uno::Reference<css::accessibility::XAccessibleImage> mpImage;
+ css::uno::Reference<css::accessibility::XAccessibleMultiLineText>
+ mpMultiLineText;
+ css::uno::Reference<css::accessibility::XAccessibleSelection> mpSelection;
+ css::uno::Reference<css::accessibility::XAccessibleTable> mpTable;
+ css::uno::Reference<css::accessibility::XAccessibleText> mpText;
+ css::uno::Reference<css::accessibility::XAccessibleTextMarkup> mpTextMarkup;
+ css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
+ mpTextAttributes;
+ css::uno::Reference<css::accessibility::XAccessibleValue> mpValue;
+
+ AtkObject *child_about_to_be_removed;
+ gint index_of_child_about_to_be_removed;
+// OString * m_pKeyBindings
+};
+
+struct AtkObjectWrapperClass
+{
+ GtkWidgetAccessibleClass aParentClass;
+};
+
+GType atk_object_wrapper_get_type() G_GNUC_CONST;
+AtkObject * atk_object_wrapper_ref(
+ const css::uno::Reference< css::accessibility::XAccessible >& rxAccessible,
+ bool create = true );
+
+AtkObject * atk_object_wrapper_new(
+ const css::uno::Reference< css::accessibility::XAccessible >& rxAccessible,
+ AtkObject* parent = nullptr, AtkObject* orig = nullptr );
+
+void atk_object_wrapper_add_child(AtkObjectWrapper* wrapper, AtkObject *child, gint index);
+void atk_object_wrapper_remove_child(AtkObjectWrapper* wrapper, AtkObject *child, gint index);
+void atk_object_wrapper_set_role(AtkObjectWrapper* wrapper, sal_Int16 role);
+
+void atk_object_wrapper_dispose(AtkObjectWrapper* wrapper);
+
+AtkStateType mapAtkState( sal_Int16 nState );
+
+AtkRelation* atk_object_wrapper_relation_new(const css::accessibility::AccessibleRelation& rRelation);
+
+void actionIfaceInit(AtkActionIface *iface);
+void componentIfaceInit(AtkComponentIface *iface);
+void editableTextIfaceInit(AtkEditableTextIface *iface);
+void hypertextIfaceInit(AtkHypertextIface *iface);
+void imageIfaceInit(AtkImageIface *iface);
+void selectionIfaceInit(AtkSelectionIface *iface);
+void tableIfaceInit(AtkTableIface *iface);
+void textIfaceInit(AtkTextIface *iface);
+void valueIfaceInit(AtkValueIface *iface);
+
+} // extern "C"
+
+#define ATK_TYPE_OBJECT_WRAPPER atk_object_wrapper_get_type()
+#define ATK_OBJECT_WRAPPER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), ATK_TYPE_OBJECT_WRAPPER, AtkObjectWrapper))
+#define ATK_IS_OBJECT_WRAPPER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ATK_TYPE_OBJECT_WRAPPER))
+
+static inline gchar *
+OUStringToGChar(std::u16string_view rString )
+{
+ OString aUtf8 = OUStringToOString( rString, RTL_TEXTENCODING_UTF8 );
+ return g_strdup( aUtf8.getStr() );
+}
+
+#define OUStringToConstGChar( string ) OUStringToOString( string, RTL_TEXTENCODING_UTF8 ).getStr()
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/customcellrenderer.cxx b/vcl/unx/gtk3/customcellrenderer.cxx
new file mode 100644
index 000000000..ac786541f
--- /dev/null
+++ b/vcl/unx/gtk3/customcellrenderer.cxx
@@ -0,0 +1,308 @@
+/* -*- 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 <vcl/svapp.hxx>
+#include "customcellrenderer.hxx"
+#if !GTK_CHECK_VERSION(4, 0, 0)
+#include <gtk/gtk-a11y.h>
+#endif
+
+namespace
+{
+struct _CustomCellRendererClass : public GtkCellRendererTextClass
+{
+};
+
+enum
+{
+ PROP_ID = 10000,
+ PROP_INSTANCE_TREE_VIEW = 10001
+};
+}
+
+G_DEFINE_TYPE(CustomCellRenderer, custom_cell_renderer, GTK_TYPE_CELL_RENDERER_TEXT)
+
+static void custom_cell_renderer_init(CustomCellRenderer* self)
+{
+ {
+ SolarMutexGuard aGuard;
+ new (&self->device) VclPtr<VirtualDevice>;
+ }
+
+ // prevent loplugin:unreffun firing on macro generated function
+ (void)custom_cell_renderer_get_instance_private(self);
+}
+
+static void custom_cell_renderer_get_property(GObject* object, guint param_id, GValue* value,
+ GParamSpec* pspec)
+{
+ CustomCellRenderer* cellsurface = CUSTOM_CELL_RENDERER(object);
+
+ switch (param_id)
+ {
+ case PROP_ID:
+ g_value_set_string(value, cellsurface->id);
+ break;
+ case PROP_INSTANCE_TREE_VIEW:
+ g_value_set_pointer(value, cellsurface->instance);
+ break;
+ default:
+ G_OBJECT_CLASS(custom_cell_renderer_parent_class)
+ ->get_property(object, param_id, value, pspec);
+ break;
+ }
+}
+
+static void custom_cell_renderer_set_property(GObject* object, guint param_id, const GValue* value,
+ GParamSpec* pspec)
+{
+ CustomCellRenderer* cellsurface = CUSTOM_CELL_RENDERER(object);
+
+ switch (param_id)
+ {
+ case PROP_ID:
+ g_free(cellsurface->id);
+ cellsurface->id = g_value_dup_string(value);
+ break;
+ case PROP_INSTANCE_TREE_VIEW:
+ cellsurface->instance = g_value_get_pointer(value);
+ break;
+ default:
+ G_OBJECT_CLASS(custom_cell_renderer_parent_class)
+ ->set_property(object, param_id, value, pspec);
+ break;
+ }
+}
+
+static bool custom_cell_renderer_get_preferred_size(GtkCellRenderer* cell,
+ GtkOrientation orientation, gint* minimum_size,
+ gint* natural_size);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+static void custom_cell_renderer_snapshot(GtkCellRenderer* cell, GtkSnapshot* snapshot,
+ GtkWidget* widget, const GdkRectangle* background_area,
+ const GdkRectangle* cell_area,
+ GtkCellRendererState flags);
+#endif
+
+static void custom_cell_renderer_render(GtkCellRenderer* cell, cairo_t* cr, GtkWidget* widget,
+ const GdkRectangle* background_area,
+ const GdkRectangle* cell_area, GtkCellRendererState flags);
+
+static void custom_cell_renderer_finalize(GObject* object)
+{
+ CustomCellRenderer* cellsurface = CUSTOM_CELL_RENDERER(object);
+
+ g_free(cellsurface->id);
+
+ {
+ SolarMutexGuard aGuard;
+ cellsurface->device.disposeAndClear();
+ cellsurface->device.~VclPtr<VirtualDevice>();
+ }
+
+ G_OBJECT_CLASS(custom_cell_renderer_parent_class)->finalize(object);
+}
+
+static void custom_cell_renderer_get_preferred_width(GtkCellRenderer* cell, GtkWidget* widget,
+ gint* minimum_size, gint* natural_size)
+{
+ if (!custom_cell_renderer_get_preferred_size(cell, GTK_ORIENTATION_HORIZONTAL, minimum_size,
+ natural_size))
+ {
+ // fallback to parent if we're empty
+ GTK_CELL_RENDERER_CLASS(custom_cell_renderer_parent_class)
+ ->get_preferred_width(cell, widget, minimum_size, natural_size);
+ }
+}
+
+static void custom_cell_renderer_get_preferred_height(GtkCellRenderer* cell, GtkWidget* widget,
+ gint* minimum_size, gint* natural_size)
+{
+ if (!custom_cell_renderer_get_preferred_size(cell, GTK_ORIENTATION_VERTICAL, minimum_size,
+ natural_size))
+ {
+ // fallback to parent if we're empty
+ GTK_CELL_RENDERER_CLASS(custom_cell_renderer_parent_class)
+ ->get_preferred_height(cell, widget, minimum_size, natural_size);
+ }
+}
+
+static void custom_cell_renderer_get_preferred_height_for_width(GtkCellRenderer* cell,
+ GtkWidget* widget, gint /*width*/,
+ gint* minimum_height,
+ gint* natural_height)
+{
+ gtk_cell_renderer_get_preferred_height(cell, widget, minimum_height, natural_height);
+}
+
+static void custom_cell_renderer_get_preferred_width_for_height(GtkCellRenderer* cell,
+ GtkWidget* widget, gint /*height*/,
+ gint* minimum_width,
+ gint* natural_width)
+{
+ gtk_cell_renderer_get_preferred_width(cell, widget, minimum_width, natural_width);
+}
+
+void custom_cell_renderer_class_init(CustomCellRendererClass* klass)
+{
+ GtkCellRendererClass* cell_class = GTK_CELL_RENDERER_CLASS(klass);
+ GObjectClass* object_class = G_OBJECT_CLASS(klass);
+
+ /* Hook up functions to set and get our custom cell renderer properties */
+ object_class->get_property = custom_cell_renderer_get_property;
+ object_class->set_property = custom_cell_renderer_set_property;
+
+ custom_cell_renderer_parent_class = g_type_class_peek_parent(klass);
+ object_class->finalize = custom_cell_renderer_finalize;
+
+ cell_class->get_preferred_width = custom_cell_renderer_get_preferred_width;
+ cell_class->get_preferred_height = custom_cell_renderer_get_preferred_height;
+ cell_class->get_preferred_width_for_height
+ = custom_cell_renderer_get_preferred_width_for_height;
+ cell_class->get_preferred_height_for_width
+ = custom_cell_renderer_get_preferred_height_for_width;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ cell_class->snapshot = custom_cell_renderer_snapshot;
+#else
+ cell_class->render = custom_cell_renderer_render;
+#endif
+
+ g_object_class_install_property(
+ object_class, PROP_ID,
+ g_param_spec_string("id", "ID", "The ID of the custom data", nullptr, G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ object_class, PROP_INSTANCE_TREE_VIEW,
+ g_param_spec_pointer("instance", "Instance", "The GtkInstanceTreeView", G_PARAM_READWRITE));
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_cell_renderer_class_set_accessible_type(cell_class, GTK_TYPE_TEXT_CELL_ACCESSIBLE);
+#endif
+}
+
+GtkCellRenderer* custom_cell_renderer_new()
+{
+ return GTK_CELL_RENDERER(g_object_new(CUSTOM_TYPE_CELL_RENDERER, nullptr));
+}
+
+bool custom_cell_renderer_get_preferred_size(GtkCellRenderer* cell, GtkOrientation orientation,
+ gint* minimum_size, gint* natural_size)
+{
+ GValue value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_STRING);
+ g_object_get_property(G_OBJECT(cell), "id", &value);
+
+ const char* pStr = g_value_get_string(&value);
+
+ OUString sId(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+
+ value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_POINTER);
+ g_object_get_property(G_OBJECT(cell), "instance", &value);
+
+ CustomCellRenderer* cellsurface = CUSTOM_CELL_RENDERER(cell);
+
+ Size aSize;
+
+ gpointer pWidget = g_value_get_pointer(&value);
+ if (pWidget)
+ {
+ custom_cell_renderer_ensure_device(cellsurface, pWidget);
+ aSize = custom_cell_renderer_get_size(*cellsurface->device, sId, pWidget);
+ }
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (minimum_size)
+ *minimum_size = aSize.Width();
+
+ if (natural_size)
+ *natural_size = aSize.Width();
+ }
+ else
+ {
+ if (minimum_size)
+ *minimum_size = aSize.Height();
+
+ if (natural_size)
+ *natural_size = aSize.Height();
+ }
+
+ return true;
+}
+
+void custom_cell_renderer_render(GtkCellRenderer* cell, cairo_t* cr, GtkWidget* /*widget*/,
+ const GdkRectangle* /*background_area*/,
+ const GdkRectangle* cell_area, GtkCellRendererState flags)
+{
+ GValue value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_STRING);
+ g_object_get_property(G_OBJECT(cell), "id", &value);
+
+ const char* pStr = g_value_get_string(&value);
+ OUString sId(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+
+ value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_POINTER);
+ g_object_get_property(G_OBJECT(cell), "instance", &value);
+
+ CustomCellRenderer* cellsurface = CUSTOM_CELL_RENDERER(cell);
+
+ gpointer pWidget = g_value_get_pointer(&value);
+ if (!pWidget)
+ return;
+
+ SolarMutexGuard aGuard;
+
+ custom_cell_renderer_ensure_device(cellsurface, pWidget);
+
+ Size aSize(cell_area->width, cell_area->height);
+ // false to not bother setting the bg on resize as we'll do that
+ // ourself via cairo
+ cellsurface->device->SetOutputSizePixel(aSize, false);
+
+ cairo_surface_t* pSurface = get_underlying_cairo_surface(*cellsurface->device);
+
+ // fill surface as transparent so it can be blended with the potentially
+ // selected background
+ cairo_t* tempcr = cairo_create(pSurface);
+ cairo_set_source_rgba(tempcr, 0, 0, 0, 0);
+ cairo_set_operator(tempcr, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(tempcr);
+ cairo_destroy(tempcr);
+ cairo_surface_flush(pSurface);
+
+ custom_cell_renderer_render(*cellsurface->device, tools::Rectangle(Point(0, 0), aSize),
+ static_cast<bool>(flags & GTK_CELL_RENDERER_SELECTED), sId,
+ pWidget);
+
+ cairo_surface_mark_dirty(pSurface);
+
+ cairo_set_source_surface(cr, pSurface, cell_area->x, cell_area->y);
+ cairo_paint(cr);
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+static void custom_cell_renderer_snapshot(GtkCellRenderer* cell, GtkSnapshot* snapshot,
+ GtkWidget* widget, const GdkRectangle* background_area,
+ const GdkRectangle* cell_area, GtkCellRendererState flags)
+{
+ graphene_rect_t rect = GRAPHENE_RECT_INIT(
+ static_cast<float>(cell_area->x), static_cast<float>(cell_area->y),
+ static_cast<float>(cell_area->width), static_cast<float>(cell_area->height));
+ cairo_t* cr = gtk_snapshot_append_cairo(GTK_SNAPSHOT(snapshot), &rect);
+ custom_cell_renderer_render(cell, cr, widget, background_area, cell_area, flags);
+ cairo_destroy(cr);
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/customcellrenderer.hxx b/vcl/unx/gtk3/customcellrenderer.hxx
new file mode 100644
index 000000000..a4d847e7c
--- /dev/null
+++ b/vcl/unx/gtk3/customcellrenderer.hxx
@@ -0,0 +1,49 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <vcl/virdev.hxx>
+
+G_BEGIN_DECLS
+
+struct _CustomCellRenderer
+{
+ GtkCellRendererText parent;
+ VclPtr<VirtualDevice> device;
+ gchar* id;
+ gpointer instance;
+};
+
+/*
+ Provide a mechanism to support custom rendering of cells in a GtkTreeView/GtkComboBox
+*/
+
+G_DECLARE_FINAL_TYPE(CustomCellRenderer, custom_cell_renderer, CUSTOM, CELL_RENDERER,
+ GtkCellRendererText)
+
+#define CUSTOM_TYPE_CELL_RENDERER (custom_cell_renderer_get_type())
+
+#define CUSTOM_CELL_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), CUSTOM_TYPE_CELL_RENDERER, CustomCellRenderer))
+
+#define CUSTOM_IS_CELL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), CUSTOM_TYPE_CELL_RENDERER))
+
+GtkCellRenderer* custom_cell_renderer_new();
+
+G_END_DECLS
+
+void custom_cell_renderer_ensure_device(CustomCellRenderer* cellsurface, gpointer user_data);
+Size custom_cell_renderer_get_size(VirtualDevice& rDevice, const OUString& rCellId,
+ gpointer user_data);
+void custom_cell_renderer_render(VirtualDevice& rDevice, const tools::Rectangle& rRect,
+ bool bSelected, const OUString& rId, gpointer user_data);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx
new file mode 100644
index 000000000..fc990b6fe
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx
@@ -0,0 +1,2087 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifdef AIX
+#define _LINUX_SOURCE_COMPAT
+#include <sys/timer.h>
+#undef _LINUX_SOURCE_COMPAT
+#endif
+
+#include <config_gio.h>
+
+#include <com/sun/star/awt/SystemDependentXWindow.hpp>
+#include <com/sun/star/awt/Toolkit.hpp>
+#include <com/sun/star/awt/XSystemDependentWindowPeer.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/SystemDependent.hpp>
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp>
+#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
+#include <osl/diagnose.h>
+#include <rtl/process.h>
+#include <sal/log.hxx>
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+#include <com/sun/star/ui/dialogs/ControlActions.hpp>
+#include <com/sun/star/uno/Any.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+
+#include <vcl/svapp.hxx>
+
+#include <o3tl/string_view.hxx>
+#include <tools/urlobj.hxx>
+#include <unotools/ucbhelper.hxx>
+
+#include <algorithm>
+#include <set>
+#include <string.h>
+#include <string_view>
+
+#include "SalGtkFilePicker.hxx"
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::ui::dialogs::TemplateDescription;
+using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds;
+using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::uno;
+
+void SalGtkFilePicker::dialog_mapped_cb(GtkWidget *, SalGtkFilePicker *pobjFP)
+{
+ pobjFP->InitialMapping();
+}
+
+void SalGtkFilePicker::InitialMapping()
+{
+ if (!mbPreviewState )
+ {
+ gtk_widget_hide( m_pPreview );
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_file_chooser_set_preview_widget_active( GTK_FILE_CHOOSER( m_pDialog ), false);
+#endif
+ }
+ gtk_widget_set_size_request (m_pPreview, -1, -1);
+}
+
+SalGtkFilePicker::SalGtkFilePicker( const uno::Reference< uno::XComponentContext >& xContext ) :
+ SalGtkPicker( xContext ),
+ SalGtkFilePicker_Base( m_rbHelperMtx ),
+ m_pVBox ( nullptr ),
+ mnHID_FolderChange( 0 ),
+ mnHID_SelectionChange( 0 ),
+ bVersionWidthUnset( false ),
+ mbPreviewState( false ),
+ mbInitialized(false),
+ mHID_Preview( 0 ),
+ m_pPreview( nullptr ),
+ m_pPseudoFilter( nullptr )
+{
+ int i;
+
+ for( i = 0; i < TOGGLE_LAST; i++ )
+ {
+ m_pToggles[i] = nullptr;
+ mbToggleVisibility[i] = false;
+ }
+
+ for( i = 0; i < BUTTON_LAST; i++ )
+ {
+ m_pButtons[i] = nullptr;
+ mbButtonVisibility[i] = false;
+ }
+
+ for( i = 0; i < LIST_LAST; i++ )
+ {
+ m_pHBoxs[i] = nullptr;
+ m_pLists[i] = nullptr;
+ m_pListLabels[i] = nullptr;
+ mbListVisibility[i] = false;
+ }
+
+ OUString aFilePickerTitle = getResString( FILE_PICKER_TITLE_OPEN );
+
+ m_pDialog = GTK_WIDGET(g_object_new(GTK_TYPE_FILE_CHOOSER_DIALOG,
+ "title", OUStringToOString(aFilePickerTitle, RTL_TEXTENCODING_UTF8).getStr(),
+ "action", GTK_FILE_CHOOSER_ACTION_OPEN,
+ nullptr));
+ gtk_window_set_modal(GTK_WINDOW(m_pDialog), true);
+ gtk_dialog_set_default_response( GTK_DIALOG (m_pDialog), GTK_RESPONSE_ACCEPT );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+#if ENABLE_GIO
+ gtk_file_chooser_set_local_only( GTK_FILE_CHOOSER( m_pDialog ), false );
+#endif
+#endif
+
+ gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER( m_pDialog ), false );
+
+ m_pVBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+
+ // We don't want clickable items to have a huge hit-area
+ GtkWidget *pHBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ GtkWidget *pThinVBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_end (GTK_BOX( m_pVBox ), pHBox, false, false, 0);
+ gtk_box_pack_start (GTK_BOX( pHBox ), pThinVBox, false, false, 0);
+#else
+ gtk_box_append(GTK_BOX(m_pVBox), pHBox);
+ gtk_box_prepend(GTK_BOX(m_pVBox), pThinVBox);
+#endif
+ gtk_widget_show( pHBox );
+ gtk_widget_show( pThinVBox );
+
+ OUString aLabel;
+
+ for( i = 0; i < TOGGLE_LAST; i++ )
+ {
+ m_pToggles[i] = gtk_check_button_new();
+
+#define LABEL_TOGGLE( elem ) \
+ case elem : \
+ aLabel = getResString( CHECKBOX_##elem ); \
+ setLabel( CHECKBOX_##elem, aLabel ); \
+ break
+
+ switch( i ) {
+ LABEL_TOGGLE( AUTOEXTENSION );
+ LABEL_TOGGLE( PASSWORD );
+ LABEL_TOGGLE( GPGENCRYPTION );
+ LABEL_TOGGLE( FILTEROPTIONS );
+ LABEL_TOGGLE( READONLY );
+ LABEL_TOGGLE( LINK );
+ LABEL_TOGGLE( PREVIEW );
+ LABEL_TOGGLE( SELECTION );
+ default:
+ SAL_WARN( "vcl.gtk", "Handle unknown control " << i);
+ break;
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_end( GTK_BOX( pThinVBox ), m_pToggles[i], false, false, 0 );
+#else
+ gtk_box_append(GTK_BOX(pThinVBox), m_pToggles[i]);
+#endif
+ }
+
+ for( i = 0; i < LIST_LAST; i++ )
+ {
+ m_pHBoxs[i] = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+
+ GtkListStore *pListStores[ LIST_LAST ];
+ pListStores[i] = gtk_list_store_new (1, G_TYPE_STRING);
+ m_pLists[i] = gtk_combo_box_new_with_model(GTK_TREE_MODEL(pListStores[i]));
+ g_object_unref (pListStores[i]); // owned by the widget.
+ GtkCellRenderer *pCell = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start(
+ GTK_CELL_LAYOUT(m_pLists[i]), pCell, true);
+ gtk_cell_layout_set_attributes(
+ GTK_CELL_LAYOUT (m_pLists[i]), pCell, "text", 0, nullptr);
+
+ m_pListLabels[i] = gtk_label_new( "" );
+
+#define LABEL_LIST( elem ) \
+ case elem : \
+ aLabel = getResString( LISTBOX_##elem##_LABEL ); \
+ setLabel( LISTBOX_##elem##_LABEL, aLabel ); \
+ break
+
+ switch( i )
+ {
+ LABEL_LIST( VERSION );
+ LABEL_LIST( TEMPLATE );
+ LABEL_LIST( IMAGE_TEMPLATE );
+ LABEL_LIST( IMAGE_ANCHOR );
+ default:
+ SAL_WARN( "vcl.gtk", "Handle unknown control " << i);
+ break;
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_end( GTK_BOX( m_pHBoxs[i] ), m_pLists[i], false, false, 0 );
+ gtk_box_pack_end( GTK_BOX( m_pHBoxs[i] ), m_pListLabels[i], false, false, 0 );
+#else
+ gtk_box_append(GTK_BOX(m_pHBoxs[i]), m_pLists[i]);
+ gtk_box_append(GTK_BOX(m_pHBoxs[i]), m_pListLabels[i]);
+#endif
+ gtk_label_set_mnemonic_widget( GTK_LABEL(m_pListLabels[i]), m_pLists[i] );
+ gtk_box_set_spacing( GTK_BOX( m_pHBoxs[i] ), 12 );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_end( GTK_BOX( m_pVBox ), m_pHBoxs[i], false, false, 0 );
+#else
+ gtk_box_append(GTK_BOX(m_pVBox), m_pHBoxs[i]);
+#endif
+ }
+
+ aLabel = getResString( FILE_PICKER_FILE_TYPE );
+ m_pFilterExpander = gtk_expander_new_with_mnemonic(
+ OUStringToOString( aLabel, RTL_TEXTENCODING_UTF8 ).getStr());
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_end( GTK_BOX( m_pVBox ), m_pFilterExpander, false, true, 0 );
+#else
+ gtk_box_append(GTK_BOX(m_pVBox), m_pFilterExpander);
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget *scrolled_window = gtk_scrolled_window_new (nullptr, nullptr);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_SHADOW_IN);
+#else
+ GtkWidget *scrolled_window = gtk_scrolled_window_new();
+ gtk_scrolled_window_set_has_frame(GTK_SCROLLED_WINDOW(scrolled_window), true);
+#endif
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add (GTK_CONTAINER (m_pFilterExpander), scrolled_window);
+#else
+ gtk_expander_set_child(GTK_EXPANDER(m_pFilterExpander), scrolled_window);
+#endif
+ gtk_widget_show (scrolled_window);
+
+ m_pFilterStore = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_STRING,
+ G_TYPE_STRING, G_TYPE_STRING);
+ m_pFilterView = gtk_tree_view_new_with_model (GTK_TREE_MODEL(m_pFilterStore));
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(m_pFilterView), false);
+
+ GtkCellRenderer *cell = nullptr;
+
+ for (i = 0; i < 2; ++i)
+ {
+ GtkTreeViewColumn *column = gtk_tree_view_column_new ();
+ cell = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_set_expand (column, true);
+ gtk_tree_view_column_pack_start (column, cell, false);
+ gtk_tree_view_column_set_attributes (column, cell, "text", i, nullptr);
+ gtk_tree_view_append_column (GTK_TREE_VIEW(m_pFilterView), column);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add (GTK_CONTAINER (scrolled_window), m_pFilterView);
+#else
+ gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window), m_pFilterView);
+#endif
+ gtk_widget_show (m_pFilterView);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_file_chooser_set_extra_widget( GTK_FILE_CHOOSER( m_pDialog ), m_pVBox );
+#endif
+
+ m_pPreview = gtk_image_new();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_file_chooser_set_preview_widget( GTK_FILE_CHOOSER( m_pDialog ), m_pPreview );
+#endif
+
+ g_signal_connect( G_OBJECT( m_pToggles[PREVIEW] ), "toggled",
+ G_CALLBACK( preview_toggled_cb ), this );
+ g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW(m_pFilterView)), "changed",
+ G_CALLBACK ( type_changed_cb ), this);
+ g_signal_connect( G_OBJECT( m_pDialog ), "notify::filter",
+ G_CALLBACK( filter_changed_cb ), this);
+ g_signal_connect( G_OBJECT( m_pFilterExpander ), "activate",
+ G_CALLBACK( expander_changed_cb ), this);
+ g_signal_connect (G_OBJECT( m_pDialog ), "map",
+ G_CALLBACK (dialog_mapped_cb), this);
+
+ gtk_widget_show( m_pVBox );
+
+ PangoLayout *layout = gtk_widget_create_pango_layout (m_pFilterView, nullptr);
+ guint ypad;
+ PangoRectangle row_height;
+ pango_layout_set_markup (layout, "All Files", -1);
+ pango_layout_get_pixel_extents (layout, nullptr, &row_height);
+ g_object_unref (layout);
+
+ g_object_get (cell, "ypad", &ypad, nullptr);
+ guint height = (row_height.height + 2*ypad) * 5;
+ gtk_widget_set_size_request (m_pFilterView, -1, height);
+ gtk_widget_set_size_request (m_pPreview, 1, height);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_file_chooser_set_preview_widget_active( GTK_FILE_CHOOSER( m_pDialog ), true);
+#endif
+}
+
+// XFilePickerNotifier
+
+void SAL_CALL SalGtkFilePicker::addFilePickerListener( const uno::Reference<XFilePickerListener>& xListener )
+{
+ SolarMutexGuard g;
+
+ OSL_ENSURE(!m_xListener.is(),
+ "SalGtkFilePicker only talks with one listener at a time...");
+ m_xListener = xListener;
+}
+
+void SAL_CALL SalGtkFilePicker::removeFilePickerListener( const uno::Reference<XFilePickerListener>& )
+{
+ SolarMutexGuard g;
+
+ m_xListener.clear();
+}
+
+// FilePicker Event functions
+
+void SalGtkFilePicker::impl_fileSelectionChanged( const FilePickerEvent& aEvent )
+{
+ if (m_xListener.is()) m_xListener->fileSelectionChanged( aEvent );
+}
+
+void SalGtkFilePicker::impl_directoryChanged( const FilePickerEvent& aEvent )
+{
+ if (m_xListener.is()) m_xListener->directoryChanged( aEvent );
+}
+
+void SalGtkFilePicker::impl_controlStateChanged( const FilePickerEvent& aEvent )
+{
+ if (m_xListener.is()) m_xListener->controlStateChanged( aEvent );
+}
+
+struct FilterEntry
+{
+protected:
+ OUString m_sTitle;
+ OUString m_sFilter;
+
+ css::uno::Sequence< css::beans::StringPair > m_aSubFilters;
+
+public:
+ FilterEntry( const OUString& _rTitle, const OUString& _rFilter )
+ :m_sTitle( _rTitle )
+ ,m_sFilter( _rFilter )
+ {
+ }
+
+ const OUString& getTitle() const { return m_sTitle; }
+ const OUString& getFilter() const { return m_sFilter; }
+
+ /// determines if the filter has sub filter (i.e., the filter is a filter group in real)
+ bool hasSubFilters( ) const;
+
+ /** retrieves the filters belonging to the entry
+ */
+ void getSubFilters( css::uno::Sequence< css::beans::StringPair >& _rSubFilterList );
+
+ // helpers for iterating the sub filters
+ const css::beans::StringPair* beginSubFilters() const { return m_aSubFilters.begin(); }
+ const css::beans::StringPair* endSubFilters() const { return m_aSubFilters.end(); }
+};
+
+bool FilterEntry::hasSubFilters() const
+{
+ return m_aSubFilters.hasElements();
+}
+
+void FilterEntry::getSubFilters( css::uno::Sequence< css::beans::StringPair >& _rSubFilterList )
+{
+ _rSubFilterList = m_aSubFilters;
+}
+
+static bool
+isFilterString( std::u16string_view rFilterString, const char *pMatch )
+{
+ sal_Int32 nIndex = 0;
+ bool bIsFilter = true;
+
+ OUString aMatch(OUString::createFromAscii(pMatch));
+
+ do
+ {
+ std::u16string_view aToken = o3tl::getToken(rFilterString, 0, ';', nIndex );
+ if( !o3tl::starts_with(aToken, aMatch) )
+ {
+ bIsFilter = false;
+ break;
+ }
+ }
+ while( nIndex >= 0 );
+
+ return bIsFilter;
+}
+
+static OUString
+shrinkFilterName( const OUString &rFilterName, bool bAllowNoStar = false )
+{
+ int i;
+ int nBracketLen = -1;
+ int nBracketEnd = -1;
+ const sal_Unicode *pStr = rFilterName.getStr();
+ OUString aRealName = rFilterName;
+
+ for( i = aRealName.getLength() - 1; i > 0; i-- )
+ {
+ if( pStr[i] == ')' )
+ nBracketEnd = i;
+ else if( pStr[i] == '(' )
+ {
+ nBracketLen = nBracketEnd - i;
+ if( nBracketEnd <= 0 )
+ continue;
+ if( isFilterString( rFilterName.subView( i + 1, nBracketLen - 1 ), "*." ) )
+ aRealName = aRealName.replaceAt( i, nBracketLen + 1, u"" );
+ else if (bAllowNoStar)
+ {
+ if( isFilterString( rFilterName.subView( i + 1, nBracketLen - 1 ), ".") )
+ aRealName = aRealName.replaceAt( i, nBracketLen + 1, u"" );
+ }
+ }
+ }
+
+ return aRealName;
+}
+
+namespace {
+
+ struct FilterTitleMatch
+ {
+ protected:
+ const OUString& rTitle;
+
+ public:
+ explicit FilterTitleMatch( const OUString& _rTitle ) : rTitle( _rTitle ) { }
+
+ bool operator () ( const FilterEntry& _rEntry )
+ {
+ bool bMatch;
+ if( !_rEntry.hasSubFilters() )
+ // a real filter
+ bMatch = (_rEntry.getTitle() == rTitle)
+ || (shrinkFilterName(_rEntry.getTitle()) == rTitle);
+ else
+ // a filter group -> search the sub filters
+ bMatch =
+ ::std::any_of(
+ _rEntry.beginSubFilters(),
+ _rEntry.endSubFilters(),
+ *this
+ );
+
+ return bMatch;
+ }
+ bool operator () ( const css::beans::StringPair& _rEntry )
+ {
+ OUString aShrunkName = shrinkFilterName( _rEntry.First );
+ return aShrunkName == rTitle;
+ }
+ };
+}
+
+bool SalGtkFilePicker::FilterNameExists( const OUString& rTitle )
+{
+ bool bRet = false;
+
+ if( m_pFilterVector )
+ bRet =
+ ::std::any_of(
+ m_pFilterVector->begin(),
+ m_pFilterVector->end(),
+ FilterTitleMatch( rTitle )
+ );
+
+ return bRet;
+}
+
+bool SalGtkFilePicker::FilterNameExists( const css::uno::Sequence< css::beans::StringPair >& _rGroupedFilters )
+{
+ bool bRet = false;
+
+ if( m_pFilterVector )
+ {
+ bRet = std::any_of(_rGroupedFilters.begin(), _rGroupedFilters.end(),
+ [&](const css::beans::StringPair& rFilter) {
+ return ::std::any_of( m_pFilterVector->begin(), m_pFilterVector->end(), FilterTitleMatch( rFilter.First ) ); });
+ }
+
+ return bRet;
+}
+
+void SalGtkFilePicker::ensureFilterVector( const OUString& _rInitialCurrentFilter )
+{
+ if( !m_pFilterVector )
+ {
+ m_pFilterVector.reset( new std::vector<FilterEntry> );
+
+ // set the first filter to the current filter
+ if ( m_aCurrentFilter.isEmpty() )
+ m_aCurrentFilter = _rInitialCurrentFilter;
+ }
+}
+
+void SAL_CALL SalGtkFilePicker::appendFilter( const OUString& aTitle, const OUString& aFilter )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ if( FilterNameExists( aTitle ) )
+ throw IllegalArgumentException();
+
+ // ensure that we have a filter list
+ ensureFilterVector( aTitle );
+
+ // append the filter
+ m_pFilterVector->insert( m_pFilterVector->end(), FilterEntry( aTitle, aFilter ) );
+}
+
+void SAL_CALL SalGtkFilePicker::setCurrentFilter( const OUString& aTitle )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ if( aTitle != m_aCurrentFilter )
+ {
+ m_aCurrentFilter = aTitle;
+ SetCurFilter( m_aCurrentFilter );
+ }
+
+ // TODO m_pImpl->setCurrentFilter( aTitle );
+}
+
+void SalGtkFilePicker::updateCurrentFilterFromName(const gchar* filtername)
+{
+ OUString aFilterName(filtername, strlen(filtername), RTL_TEXTENCODING_UTF8);
+ if (m_pFilterVector)
+ {
+ for (auto const& filter : *m_pFilterVector)
+ {
+ if (aFilterName == shrinkFilterName(filter.getTitle()))
+ {
+ m_aCurrentFilter = filter.getTitle();
+ break;
+ }
+ }
+ }
+}
+
+void SalGtkFilePicker::UpdateFilterfromUI()
+{
+ // Update the filtername from the users selection if they have had a chance to do so.
+ // If the user explicitly sets a type then use that, if not then take the implicit type
+ // from the filter of the files glob on which he is currently searching
+ if (!mnHID_FolderChange || !mnHID_SelectionChange)
+ return;
+
+ GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_pFilterView));
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ gchar *title;
+ gtk_tree_model_get (model, &iter, 2, &title, -1);
+ updateCurrentFilterFromName(title);
+ g_free (title);
+ }
+ else if( GtkFileFilter *filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(m_pDialog)))
+ {
+ if (m_pPseudoFilter != filter)
+ updateCurrentFilterFromName(gtk_file_filter_get_name( filter ));
+ else
+ updateCurrentFilterFromName(OUStringToOString( m_aInitialFilter, RTL_TEXTENCODING_UTF8 ).getStr());
+ }
+}
+
+OUString SAL_CALL SalGtkFilePicker::getCurrentFilter()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ UpdateFilterfromUI();
+
+ return m_aCurrentFilter;
+}
+
+// XFilterGroupManager functions
+
+void SAL_CALL SalGtkFilePicker::appendFilterGroup( const OUString& /*sGroupTitle*/, const uno::Sequence<beans::StringPair>& aFilters )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO m_pImpl->appendFilterGroup( sGroupTitle, aFilters );
+ // check the names
+ if( FilterNameExists( aFilters ) )
+ // TODO: a more precise exception message
+ throw IllegalArgumentException();
+
+ // ensure that we have a filter list
+ OUString sInitialCurrentFilter;
+ if( aFilters.hasElements() )
+ sInitialCurrentFilter = aFilters[0].First;
+
+ ensureFilterVector( sInitialCurrentFilter );
+
+ // append the filter
+ for( const auto& rSubFilter : aFilters )
+ m_pFilterVector->insert( m_pFilterVector->end(), FilterEntry( rSubFilter.First, rSubFilter.Second ) );
+
+}
+
+// XFilePicker functions
+
+void SAL_CALL SalGtkFilePicker::setMultiSelectionMode( sal_Bool bMode )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(m_pDialog), bMode );
+}
+
+void SAL_CALL SalGtkFilePicker::setDefaultName( const OUString& aName )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ OString aStr = OUStringToOString( aName, RTL_TEXTENCODING_UTF8 );
+ GtkFileChooserAction eAction = gtk_file_chooser_get_action( GTK_FILE_CHOOSER( m_pDialog ) );
+
+ // set_current_name launches a Gtk critical error if called for other than save
+ if( GTK_FILE_CHOOSER_ACTION_SAVE == eAction )
+ gtk_file_chooser_set_current_name( GTK_FILE_CHOOSER( m_pDialog ), aStr.getStr() );
+}
+
+void SAL_CALL SalGtkFilePicker::setDisplayDirectory( const OUString& rDirectory )
+{
+ SolarMutexGuard g;
+
+ implsetDisplayDirectory(rDirectory);
+}
+
+OUString SAL_CALL SalGtkFilePicker::getDisplayDirectory()
+{
+ SolarMutexGuard g;
+
+ return implgetDisplayDirectory();
+}
+
+uno::Sequence<OUString> SAL_CALL SalGtkFilePicker::getFiles()
+{
+ // no member access => no mutex needed
+
+ uno::Sequence< OUString > aFiles = getSelectedFiles();
+ /*
+ The previous multiselection API design was completely broken
+ and unimplementable for some heterogeneous pseudo-URIs eg. search:
+ Thus crop unconditionally to a single selection.
+ */
+ aFiles.realloc (1);
+ return aFiles;
+}
+
+namespace
+{
+
+bool lcl_matchFilter( const OUString& rFilter, const OUString& rExt )
+{
+ const sal_Unicode cSep {';'};
+ sal_Int32 nIdx {0};
+
+ for (;;)
+ {
+ const sal_Int32 nBegin = rFilter.indexOf(rExt, nIdx);
+
+ if (nBegin<0) // not found
+ break;
+
+ // Let nIdx point to end of matched string, useful in order to
+ // check string boundaries and also for a possible next iteration
+ nIdx = nBegin + rExt.getLength();
+
+ // Check if the found occurrence is an exact match: right side
+ if (nIdx<rFilter.getLength() && rFilter[nIdx]!=cSep)
+ continue;
+
+ // Check if the found occurrence is an exact match: left side
+ if (nBegin>0 && rFilter[nBegin-1]!=cSep)
+ continue;
+
+ return true;
+ }
+
+ return false;
+}
+
+}
+
+uno::Sequence<OUString> SAL_CALL SalGtkFilePicker::getSelectedFiles()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GSList* pPathList = gtk_file_chooser_get_uris( GTK_FILE_CHOOSER(m_pDialog) );
+ int nCount = g_slist_length( pPathList );
+#else
+ GListModel* pPathList = gtk_file_chooser_get_files(GTK_FILE_CHOOSER(m_pDialog));
+ int nCount = g_list_model_get_n_items(pPathList);
+#endif
+
+ int nIndex = 0;
+
+ // get the current action setting
+ GtkFileChooserAction eAction = gtk_file_chooser_get_action(
+ GTK_FILE_CHOOSER( m_pDialog ));
+
+ uno::Sequence< OUString > aSelectedFiles(nCount);
+ auto aSelectedFilesRange = asNonConstRange(aSelectedFiles);
+
+ // Convert to OOo
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ for( GSList *pElem = pPathList; pElem; pElem = pElem->next)
+ {
+ gchar *pURI = static_cast<gchar*>(pElem->data);
+#else
+ while (gpointer pElem = g_list_model_get_item(pPathList, nIndex))
+ {
+ gchar *pURI = g_file_get_uri(static_cast<GFile*>(pElem));
+#endif
+
+ aSelectedFilesRange[ nIndex ] = uritounicode(pURI);
+
+ if( GTK_FILE_CHOOSER_ACTION_SAVE == eAction )
+ {
+ OUString sFilterName;
+ sal_Int32 nTokenIndex = 0;
+ bool bExtensionTypedIn = false;
+
+ GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_pFilterView));
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ gchar *title = nullptr;
+ gtk_tree_model_get (model, &iter, 2, &title, -1);
+ if (title)
+ sFilterName = OUString( title, strlen( title ), RTL_TEXTENCODING_UTF8 );
+ else
+ sFilterName = OUString();
+ g_free (title);
+ }
+ else
+ {
+ if( aSelectedFiles[nIndex].indexOf('.') > 0 )
+ {
+ std::u16string_view sExtension;
+ nTokenIndex = 0;
+ do
+ sExtension = o3tl::getToken(aSelectedFiles[nIndex], 0, '.', nTokenIndex );
+ while( nTokenIndex >= 0 );
+
+ if( sExtension.size() >= 3 ) // 3 = typical/minimum extension length
+ {
+ OUString aNewFilter;
+ OUString aOldFilter = getCurrentFilter();
+ bool bChangeFilter = true;
+ if ( m_pFilterVector)
+ for (auto const& filter : *m_pFilterVector)
+ {
+ if( lcl_matchFilter( filter.getFilter(), OUString::Concat("*.") + sExtension ) )
+ {
+ if( aNewFilter.isEmpty() )
+ aNewFilter = filter.getTitle();
+
+ if( aOldFilter == filter.getTitle() )
+ bChangeFilter = false;
+
+ bExtensionTypedIn = true;
+ }
+ }
+ if( bChangeFilter && bExtensionTypedIn )
+ {
+ gchar* pCurrentName = gtk_file_chooser_get_current_name(GTK_FILE_CHOOSER(m_pDialog));
+ setCurrentFilter( aNewFilter );
+ gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(m_pDialog), pCurrentName);
+ g_free(pCurrentName);
+ }
+ }
+ }
+
+ GtkFileFilter *filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(m_pDialog));
+ if (m_pPseudoFilter != filter)
+ {
+ const gchar* filtername = filter ? gtk_file_filter_get_name(filter) : nullptr;
+ if (filtername)
+ sFilterName = OUString(filtername, strlen( filtername ), RTL_TEXTENCODING_UTF8);
+ else
+ sFilterName.clear();
+ }
+ else
+ sFilterName = m_aInitialFilter;
+ }
+
+ if (m_pFilterVector)
+ {
+ auto aVectorIter = ::std::find_if(
+ m_pFilterVector->begin(), m_pFilterVector->end(), FilterTitleMatch(sFilterName) );
+
+ OUString aFilter;
+ if (aVectorIter != m_pFilterVector->end())
+ aFilter = aVectorIter->getFilter();
+
+ nTokenIndex = 0;
+ OUString sToken;
+ do
+ {
+ sToken = aFilter.getToken( 0, '.', nTokenIndex );
+
+ if ( sToken.lastIndexOf( ';' ) != -1 )
+ {
+ sToken = sToken.getToken(0, ';');
+ break;
+ }
+ }
+ while( nTokenIndex >= 0 );
+
+ if( !bExtensionTypedIn && ( sToken != "*" ) )
+ {
+ //if the filename does not already have the auto extension, stick it on
+ OUString sExtension = "." + sToken;
+ OUString &rBase = aSelectedFilesRange[nIndex];
+ sal_Int32 nExtensionIdx = rBase.getLength() - sExtension.getLength();
+ SAL_INFO(
+ "vcl.gtk",
+ "idx are " << rBase.lastIndexOf(sExtension) << " "
+ << nExtensionIdx);
+
+ if( rBase.lastIndexOf( sExtension ) != nExtensionIdx )
+ rBase += sExtension;
+ }
+ }
+
+ }
+
+ nIndex++;
+ g_free( pURI );
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_slist_free( pPathList );
+#else
+ g_object_unref(pPathList);
+#endif
+
+ return aSelectedFiles;
+}
+
+// XExecutableDialog functions
+
+void SAL_CALL SalGtkFilePicker::setTitle( const OUString& rTitle )
+{
+ SolarMutexGuard g;
+
+ implsetTitle(rTitle);
+}
+
+sal_Int16 SAL_CALL SalGtkFilePicker::execute()
+{
+ SolarMutexGuard g;
+
+ if (!mbInitialized)
+ {
+ // tdf#144084 if not initialized default to FILEOPEN_SIMPLE
+ impl_initialize(nullptr, FILEOPEN_SIMPLE);
+ assert(mbInitialized);
+ }
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ sal_Int16 retVal = 0;
+
+ SetFilters();
+
+ // tdf#84431 - set the filter after the corresponding widget is created
+ if ( !m_aCurrentFilter.isEmpty() )
+ SetCurFilter(m_aCurrentFilter);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mnHID_FolderChange =
+ g_signal_connect( GTK_FILE_CHOOSER( m_pDialog ), "current-folder-changed",
+ G_CALLBACK( folder_changed_cb ), static_cast<gpointer>(this) );
+#else
+ // no replacement in 4-0 that I can see :-(
+ mnHID_FolderChange = 0;
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mnHID_SelectionChange =
+ g_signal_connect( GTK_FILE_CHOOSER( m_pDialog ), "selection-changed",
+ G_CALLBACK( selection_changed_cb ), static_cast<gpointer>(this) );
+#else
+ // no replacement in 4-0 that I can see :-(
+ mnHID_SelectionChange = 0;
+#endif
+
+ int btn = GTK_RESPONSE_NO;
+
+ uno::Reference< awt::XExtendedToolkit > xToolkit(
+ awt::Toolkit::create(m_xContext),
+ UNO_QUERY_THROW );
+
+ uno::Reference< frame::XDesktop > xDesktop(
+ frame::Desktop::create(m_xContext),
+ UNO_QUERY_THROW );
+
+ GtkWindow *pParent = GTK_WINDOW(m_pParentWidget);
+ if (!pParent)
+ {
+ SAL_WARN( "vcl.gtk", "no parent widget set");
+ pParent = RunDialog::GetTransientFor();
+ }
+ if (pParent)
+ gtk_window_set_transient_for(GTK_WINDOW(m_pDialog), pParent);
+ rtl::Reference<RunDialog> pRunDialog = new RunDialog(m_pDialog, xToolkit, xDesktop);
+ while( GTK_RESPONSE_NO == btn )
+ {
+ btn = GTK_RESPONSE_YES; // we don't want to repeat unless user clicks NO for file save.
+
+ gint nStatus = pRunDialog->run();
+ switch( nStatus )
+ {
+ case GTK_RESPONSE_ACCEPT:
+ if( GTK_FILE_CHOOSER_ACTION_SAVE == gtk_file_chooser_get_action( GTK_FILE_CHOOSER( m_pDialog ) ) )
+ {
+ Sequence < OUString > aPathSeq = getFiles();
+ if( aPathSeq.getLength() == 1 )
+ {
+ OUString sFileName = aPathSeq[0];
+ if (::utl::UCBContentHelper::Exists(sFileName))
+ {
+ INetURLObject aFileObj(sFileName);
+
+ OString baseName(
+ OUStringToOString(
+ aFileObj.getName(
+ INetURLObject::LAST_SEGMENT,
+ true,
+ INetURLObject::DecodeMechanism::WithCharset
+ ),
+ RTL_TEXTENCODING_UTF8
+ )
+ );
+ OString aMsg(
+ OUStringToOString(
+ getResString( FILE_PICKER_OVERWRITE_PRIMARY ),
+ RTL_TEXTENCODING_UTF8
+ )
+ );
+ OString toReplace("$filename$");
+
+ aMsg = aMsg.replaceAt(
+ aMsg.indexOf( toReplace ),
+ toReplace.getLength(),
+ baseName
+ );
+
+ GtkWidget *dlg = gtk_message_dialog_new( nullptr,
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_YES_NO,
+ "%s",
+ aMsg.getStr()
+ );
+
+ GtkWidget* pOkButton = gtk_dialog_get_widget_for_response(GTK_DIALOG(dlg), GTK_RESPONSE_YES);
+ GtkStyleContext* pStyleContext = gtk_widget_get_style_context(pOkButton);
+ gtk_style_context_add_class(pStyleContext, "destructive-action");
+
+ sal_Int32 nSegmentCount = aFileObj.getSegmentCount();
+ if (nSegmentCount >= 2)
+ {
+ OString dirName(
+ OUStringToOString(
+ aFileObj.getName(
+ nSegmentCount-2,
+ true,
+ INetURLObject::DecodeMechanism::WithCharset
+ ),
+ RTL_TEXTENCODING_UTF8
+ )
+ );
+
+ aMsg =
+ OUStringToOString(
+ getResString( FILE_PICKER_OVERWRITE_SECONDARY ),
+ RTL_TEXTENCODING_UTF8
+ );
+
+ toReplace = "$dirname$";
+
+ aMsg = aMsg.replaceAt(
+ aMsg.indexOf( toReplace ),
+ toReplace.getLength(),
+ dirName
+ );
+
+ gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( dlg ), "%s", aMsg.getStr() );
+ }
+
+ gtk_window_set_title( GTK_WINDOW( dlg ),
+ OUStringToOString(getResString(FILE_PICKER_TITLE_SAVE ),
+ RTL_TEXTENCODING_UTF8 ).getStr() );
+ gtk_window_set_transient_for(GTK_WINDOW(dlg), GTK_WINDOW(m_pDialog));
+ rtl::Reference<RunDialog> pAnotherDialog = new RunDialog(dlg, xToolkit, xDesktop);
+ btn = pAnotherDialog->run();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(dlg);
+#else
+ gtk_window_destroy(GTK_WINDOW(dlg));
+#endif
+ }
+
+ if( btn == GTK_RESPONSE_YES )
+ retVal = ExecutableDialogResults::OK;
+ }
+ }
+ else
+ retVal = ExecutableDialogResults::OK;
+ break;
+
+ case GTK_RESPONSE_CANCEL:
+ retVal = ExecutableDialogResults::CANCEL;
+ break;
+
+ case 1: //PLAY
+ {
+ FilePickerEvent evt;
+ evt.ElementId = PUSHBUTTON_PLAY;
+ impl_controlStateChanged( evt );
+ btn = GTK_RESPONSE_NO;
+ }
+ break;
+
+ default:
+ retVal = 0;
+ break;
+ }
+ }
+ gtk_widget_hide(m_pDialog);
+
+ if (mnHID_FolderChange)
+ g_signal_handler_disconnect(GTK_FILE_CHOOSER( m_pDialog ), mnHID_FolderChange);
+ if (mnHID_SelectionChange)
+ g_signal_handler_disconnect(GTK_FILE_CHOOSER( m_pDialog ), mnHID_SelectionChange);
+
+ return retVal;
+}
+
+// cf. offapi/com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.idl
+GtkWidget *SalGtkFilePicker::getWidget( sal_Int16 nControlId, GType *pType )
+{
+ GType tType = GTK_TYPE_CHECK_BUTTON; //prevent warning by initializing
+ GtkWidget *pWidget = nullptr;
+
+#define MAP_TOGGLE( elem ) \
+ case ExtendedFilePickerElementIds::CHECKBOX_##elem: \
+ pWidget = m_pToggles[elem]; tType = GTK_TYPE_CHECK_BUTTON; \
+ break
+#define MAP_BUTTON( elem ) \
+ case CommonFilePickerElementIds::PUSHBUTTON_##elem: \
+ pWidget = m_pButtons[elem]; tType = GTK_TYPE_BUTTON; \
+ break
+#define MAP_EXT_BUTTON( elem ) \
+ case ExtendedFilePickerElementIds::PUSHBUTTON_##elem: \
+ pWidget = m_pButtons[elem]; tType = GTK_TYPE_BUTTON; \
+ break
+#define MAP_LIST( elem ) \
+ case ExtendedFilePickerElementIds::LISTBOX_##elem: \
+ pWidget = m_pLists[elem]; tType = GTK_TYPE_COMBO_BOX; \
+ break
+#define MAP_LIST_LABEL( elem ) \
+ case ExtendedFilePickerElementIds::LISTBOX_##elem##_LABEL: \
+ pWidget = m_pListLabels[elem]; tType = GTK_TYPE_LABEL; \
+ break
+
+ switch( nControlId )
+ {
+ MAP_TOGGLE( AUTOEXTENSION );
+ MAP_TOGGLE( PASSWORD );
+ MAP_TOGGLE( GPGENCRYPTION );
+ MAP_TOGGLE( FILTEROPTIONS );
+ MAP_TOGGLE( READONLY );
+ MAP_TOGGLE( LINK );
+ MAP_TOGGLE( PREVIEW );
+ MAP_TOGGLE( SELECTION );
+ MAP_BUTTON( OK );
+ MAP_BUTTON( CANCEL );
+ MAP_EXT_BUTTON( PLAY );
+ MAP_LIST( VERSION );
+ MAP_LIST( TEMPLATE );
+ MAP_LIST( IMAGE_TEMPLATE );
+ MAP_LIST( IMAGE_ANCHOR );
+ MAP_LIST_LABEL( VERSION );
+ MAP_LIST_LABEL( TEMPLATE );
+ MAP_LIST_LABEL( IMAGE_TEMPLATE );
+ MAP_LIST_LABEL( IMAGE_ANCHOR );
+ default:
+ SAL_WARN( "vcl.gtk", "Handle unknown control " << nControlId);
+ break;
+ }
+#undef MAP
+
+ if( pType )
+ *pType = tType;
+ return pWidget;
+}
+
+// XFilePickerControlAccess functions
+
+static void HackWidthToFirst(GtkComboBox *pWidget)
+{
+ GtkRequisition requisition;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_size_request(GTK_WIDGET(pWidget), &requisition);
+#else
+ gtk_widget_get_preferred_size(GTK_WIDGET(pWidget), &requisition, nullptr);
+#endif
+ gtk_widget_set_size_request(GTK_WIDGET(pWidget), requisition.width, -1);
+}
+
+static void ComboBoxAppendText(GtkComboBox *pCombo, std::u16string_view rStr)
+{
+ GtkTreeIter aIter;
+ GtkListStore *pStore = GTK_LIST_STORE(gtk_combo_box_get_model(pCombo));
+ OString aStr = OUStringToOString(rStr, RTL_TEXTENCODING_UTF8);
+ gtk_list_store_append(pStore, &aIter);
+ gtk_list_store_set(pStore, &aIter, 0, aStr.getStr(), -1);
+}
+
+void SalGtkFilePicker::HandleSetListValue(GtkComboBox *pWidget, sal_Int16 nControlAction, const uno::Any& rValue)
+{
+ switch (nControlAction)
+ {
+ case ControlActions::ADD_ITEM:
+ {
+ OUString sItem;
+ rValue >>= sItem;
+ ComboBoxAppendText(pWidget, sItem);
+ if (!bVersionWidthUnset)
+ {
+ HackWidthToFirst(pWidget);
+ bVersionWidthUnset = true;
+ }
+ }
+ break;
+ case ControlActions::ADD_ITEMS:
+ {
+ Sequence< OUString > aStringList;
+ rValue >>= aStringList;
+ for (const auto& rString : std::as_const(aStringList))
+ {
+ ComboBoxAppendText(pWidget, rString);
+ if (!bVersionWidthUnset)
+ {
+ HackWidthToFirst(pWidget);
+ bVersionWidthUnset = true;
+ }
+ }
+ }
+ break;
+ case ControlActions::DELETE_ITEM:
+ {
+ sal_Int32 nPos=0;
+ rValue >>= nPos;
+
+ GtkTreeIter aIter;
+ GtkListStore *pStore = GTK_LIST_STORE(
+ gtk_combo_box_get_model(GTK_COMBO_BOX(pWidget)));
+ if(gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pStore), &aIter, nullptr, nPos))
+ gtk_list_store_remove(pStore, &aIter);
+ }
+ break;
+ case ControlActions::DELETE_ITEMS:
+ {
+ gtk_combo_box_set_active(pWidget, -1);
+ GtkListStore *pStore = GTK_LIST_STORE(
+ gtk_combo_box_get_model(GTK_COMBO_BOX(pWidget)));
+ gtk_list_store_clear(pStore);
+ }
+ break;
+ case ControlActions::SET_SELECT_ITEM:
+ {
+ sal_Int32 nPos=0;
+ rValue >>= nPos;
+ gtk_combo_box_set_active(pWidget, nPos);
+ }
+ break;
+ default:
+ SAL_WARN( "vcl.gtk", "undocumented/unimplemented ControlAction for a list " << nControlAction);
+ break;
+ }
+
+ //I think its best to make it insensitive unless there is the chance to
+ //actually select something from the list.
+ gint nItems = gtk_tree_model_iter_n_children(
+ gtk_combo_box_get_model(pWidget), nullptr);
+ gtk_widget_set_sensitive(GTK_WIDGET(pWidget), nItems > 1);
+}
+
+uno::Any SalGtkFilePicker::HandleGetListValue(GtkComboBox *pWidget, sal_Int16 nControlAction)
+{
+ uno::Any aAny;
+ switch (nControlAction)
+ {
+ case ControlActions::GET_ITEMS:
+ {
+ Sequence< OUString > aItemList;
+
+ GtkTreeModel *pTree = gtk_combo_box_get_model(pWidget);
+ GtkTreeIter iter;
+ if (gtk_tree_model_get_iter_first(pTree, &iter))
+ {
+ sal_Int32 nSize = gtk_tree_model_iter_n_children(
+ pTree, nullptr);
+
+ aItemList.realloc(nSize);
+ auto pItemList = aItemList.getArray();
+ for (sal_Int32 i=0; i < nSize; ++i)
+ {
+ gchar *item;
+ gtk_tree_model_get(gtk_combo_box_get_model(pWidget),
+ &iter, 0, &item, -1);
+ pItemList[i] = OUString(item, strlen(item), RTL_TEXTENCODING_UTF8);
+ g_free(item);
+ (void)gtk_tree_model_iter_next(pTree, &iter);
+ }
+ }
+ aAny <<= aItemList;
+ }
+ break;
+ case ControlActions::GET_SELECTED_ITEM:
+ {
+ GtkTreeIter iter;
+ if (gtk_combo_box_get_active_iter(pWidget, &iter))
+ {
+ gchar *item;
+ gtk_tree_model_get(gtk_combo_box_get_model(pWidget),
+ &iter, 0, &item, -1);
+ OUString sItem(item, strlen(item), RTL_TEXTENCODING_UTF8);
+ aAny <<= sItem;
+ g_free(item);
+ }
+ }
+ break;
+ case ControlActions::GET_SELECTED_ITEM_INDEX:
+ {
+ gint nActive = gtk_combo_box_get_active(pWidget);
+ aAny <<= static_cast< sal_Int32 >(nActive);
+ }
+ break;
+ default:
+ SAL_WARN( "vcl.gtk", "undocumented/unimplemented ControlAction for a list " << nControlAction);
+ break;
+ }
+ return aAny;
+}
+
+void SAL_CALL SalGtkFilePicker::setValue( sal_Int16 nControlId, sal_Int16 nControlAction, const uno::Any& rValue )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ GType tType;
+ GtkWidget *pWidget;
+
+ if( !( pWidget = getWidget( nControlId, &tType ) ) )
+ SAL_WARN( "vcl.gtk", "enable unknown control " << nControlId);
+ else if( tType == GTK_TYPE_CHECK_BUTTON)
+ {
+ bool bChecked = false;
+ rValue >>= bChecked;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_check_button_set_active(GTK_CHECK_BUTTON(pWidget), bChecked);
+#else
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pWidget), bChecked);
+#endif
+ }
+ else if( tType == GTK_TYPE_COMBO_BOX )
+ HandleSetListValue(GTK_COMBO_BOX(pWidget), nControlAction, rValue);
+ else
+ {
+ SAL_WARN( "vcl.gtk", "Can't set value on button / list " << nControlId << " " << nControlAction );
+ }
+}
+
+uno::Any SAL_CALL SalGtkFilePicker::getValue( sal_Int16 nControlId, sal_Int16 nControlAction )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ uno::Any aRetval;
+
+ GType tType;
+ GtkWidget *pWidget;
+
+ if( !( pWidget = getWidget( nControlId, &tType ) ) )
+ SAL_WARN( "vcl.gtk", "enable unknown control " << nControlId);
+ else if( tType == GTK_TYPE_CHECK_BUTTON)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ aRetval <<= bool(gtk_check_button_get_active(GTK_CHECK_BUTTON(pWidget)));
+#else
+ aRetval <<= bool(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pWidget)));
+#endif
+ }
+ else if( tType == GTK_TYPE_COMBO_BOX )
+ aRetval = HandleGetListValue(GTK_COMBO_BOX(pWidget), nControlAction);
+ else
+ SAL_WARN( "vcl.gtk", "Can't get value on button / list " << nControlId << " " << nControlAction );
+
+ return aRetval;
+}
+
+void SAL_CALL SalGtkFilePicker::enableControl( sal_Int16 nControlId, sal_Bool bEnable )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ GtkWidget *pWidget;
+
+ if ( ( pWidget = getWidget( nControlId ) ) )
+ {
+ if( bEnable )
+ {
+ gtk_widget_set_sensitive( pWidget, true );
+ }
+ else
+ {
+ gtk_widget_set_sensitive( pWidget, false );
+ }
+ }
+ else
+ SAL_WARN( "vcl.gtk", "enable unknown control " << nControlId );
+}
+
+void SAL_CALL SalGtkFilePicker::setLabel( sal_Int16 nControlId, const OUString& rLabel )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ GType tType;
+ GtkWidget *pWidget;
+
+ if( !( pWidget = getWidget( nControlId, &tType ) ) )
+ {
+ SAL_WARN( "vcl.gtk", "Set label '" << rLabel << "' on unknown control " << nControlId);
+ return;
+ }
+
+ OString aTxt = OUStringToOString( rLabel.replace('~', '_'), RTL_TEXTENCODING_UTF8 );
+ if( tType == GTK_TYPE_CHECK_BUTTON || tType == GTK_TYPE_BUTTON || tType == GTK_TYPE_LABEL )
+ g_object_set( pWidget, "label", aTxt.getStr(),
+ "use_underline", true, nullptr );
+ else
+ SAL_WARN( "vcl.gtk", "Can't set label on list");
+}
+
+OUString SAL_CALL SalGtkFilePicker::getLabel( sal_Int16 nControlId )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ GType tType;
+ OString aTxt;
+ GtkWidget *pWidget;
+
+ if( !( pWidget = getWidget( nControlId, &tType ) ) )
+ SAL_WARN( "vcl.gtk", "Get label on unknown control " << nControlId);
+ else if( tType == GTK_TYPE_CHECK_BUTTON || tType == GTK_TYPE_BUTTON || tType == GTK_TYPE_LABEL )
+ aTxt = gtk_button_get_label( GTK_BUTTON( pWidget ) );
+ else
+ SAL_WARN( "vcl.gtk", "Can't get label on list");
+
+ return OStringToOUString( aTxt, RTL_TEXTENCODING_UTF8 );
+}
+
+// XFilePreview functions
+
+uno::Sequence<sal_Int16> SAL_CALL SalGtkFilePicker::getSupportedImageFormats()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO return m_pImpl->getSupportedImageFormats();
+ return uno::Sequence<sal_Int16>();
+}
+
+sal_Int32 SAL_CALL SalGtkFilePicker::getTargetColorDepth()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO return m_pImpl->getTargetColorDepth();
+ return 0;
+}
+
+sal_Int32 SAL_CALL SalGtkFilePicker::getAvailableWidth()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ return g_PreviewImageWidth;
+}
+
+sal_Int32 SAL_CALL SalGtkFilePicker::getAvailableHeight()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ return g_PreviewImageHeight;
+}
+
+void SAL_CALL SalGtkFilePicker::setImage( sal_Int16 /*aImageFormat*/, const uno::Any& /*aImage*/ )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO m_pImpl->setImage( aImageFormat, aImage );
+}
+
+void SalGtkFilePicker::implChangeType( GtkTreeSelection *selection )
+{
+ OUString aLabel = getResString( FILE_PICKER_FILE_TYPE );
+
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ gchar *title;
+ gtk_tree_model_get (model, &iter, 2, &title, -1);
+ aLabel += ": " +
+ OUString( title, strlen(title), RTL_TEXTENCODING_UTF8 );
+ g_free (title);
+ }
+ gtk_expander_set_label (GTK_EXPANDER (m_pFilterExpander),
+ OUStringToOString( aLabel, RTL_TEXTENCODING_UTF8 ).getStr());
+ FilePickerEvent evt;
+ evt.ElementId = LISTBOX_FILTER;
+ impl_controlStateChanged( evt );
+}
+
+void SalGtkFilePicker::type_changed_cb( GtkTreeSelection *selection, SalGtkFilePicker *pobjFP )
+{
+ pobjFP->implChangeType(selection);
+}
+
+void SalGtkFilePicker::unselect_type()
+{
+ gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(m_pFilterView)));
+}
+
+void SalGtkFilePicker::expander_changed_cb( GtkExpander *expander, SalGtkFilePicker *pobjFP )
+{
+ if (gtk_expander_get_expanded(expander))
+ pobjFP->unselect_type();
+}
+
+void SalGtkFilePicker::filter_changed_cb( GtkFileChooser *, GParamSpec *,
+ SalGtkFilePicker *pobjFP )
+{
+ FilePickerEvent evt;
+ evt.ElementId = LISTBOX_FILTER;
+ SAL_INFO( "vcl.gtk", "filter_changed, isn't it great " << pobjFP );
+ pobjFP->impl_controlStateChanged( evt );
+}
+
+void SalGtkFilePicker::folder_changed_cb( GtkFileChooser *, SalGtkFilePicker *pobjFP )
+{
+ FilePickerEvent evt;
+ SAL_INFO( "vcl.gtk", "folder_changed, isn't it great " << pobjFP );
+ pobjFP->impl_directoryChanged( evt );
+}
+
+void SalGtkFilePicker::selection_changed_cb( GtkFileChooser *, SalGtkFilePicker *pobjFP )
+{
+ FilePickerEvent evt;
+ SAL_INFO( "vcl.gtk", "selection_changed, isn't it great " << pobjFP );
+ pobjFP->impl_fileSelectionChanged( evt );
+}
+
+void SalGtkFilePicker::update_preview_cb( GtkFileChooser *file_chooser, SalGtkFilePicker* pobjFP )
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool have_preview = false;
+
+ GtkWidget* preview = pobjFP->m_pPreview;
+ char* filename = gtk_file_chooser_get_preview_filename( file_chooser );
+
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pobjFP->m_pToggles[PREVIEW])) && filename && g_file_test(filename, G_FILE_TEST_IS_REGULAR))
+ {
+ GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_size(
+ filename,
+ g_PreviewImageWidth,
+ g_PreviewImageHeight, nullptr );
+
+ have_preview = ( pixbuf != nullptr );
+
+ gtk_image_set_from_pixbuf( GTK_IMAGE( preview ), pixbuf );
+ if( pixbuf )
+ g_object_unref( G_OBJECT( pixbuf ) );
+
+ }
+
+ gtk_file_chooser_set_preview_widget_active( file_chooser, have_preview );
+
+ if( filename )
+ g_free( filename );
+#else
+ (void)file_chooser;
+ (void)pobjFP;
+#endif
+}
+
+sal_Bool SAL_CALL SalGtkFilePicker::setShowState( sal_Bool bShowState )
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO return m_pImpl->setShowState( bShowState );
+ if( bool(bShowState) != mbPreviewState )
+ {
+ if( bShowState )
+ {
+ // Show
+ if( !mHID_Preview )
+ {
+ mHID_Preview = g_signal_connect(
+ GTK_FILE_CHOOSER( m_pDialog ), "update-preview",
+ G_CALLBACK( update_preview_cb ), static_cast<gpointer>(this) );
+ }
+ gtk_widget_show( m_pPreview );
+ }
+ else
+ {
+ // Hide
+ gtk_widget_hide( m_pPreview );
+ }
+
+ // also emit the signal
+ g_signal_emit_by_name( G_OBJECT( m_pDialog ), "update-preview" );
+
+ mbPreviewState = bShowState;
+ }
+ return true;
+}
+
+sal_Bool SAL_CALL SalGtkFilePicker::getShowState()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ return mbPreviewState;
+}
+
+GtkWidget* SalGtkPicker::GetParentWidget(const uno::Sequence<uno::Any>& rArguments)
+{
+ GtkWidget* pParentWidget = nullptr;
+
+ css::uno::Reference<css::awt::XWindow> xParentWindow;
+ if (rArguments.getLength() > 1)
+ {
+ rArguments[1] >>= xParentWindow;
+ }
+
+ if (xParentWindow.is())
+ {
+ if (SalGtkXWindow* pGtkXWindow = dynamic_cast<SalGtkXWindow*>(xParentWindow.get()))
+ pParentWidget = pGtkXWindow->getGtkWidget();
+ else
+ {
+ css::uno::Reference<css::awt::XSystemDependentWindowPeer> xSysDepWin(xParentWindow, css::uno::UNO_QUERY);
+ if (xSysDepWin.is())
+ {
+ css::uno::Sequence<sal_Int8> aProcessIdent(16);
+ rtl_getGlobalProcessId(reinterpret_cast<sal_uInt8*>(aProcessIdent.getArray()));
+ uno::Any aAny = xSysDepWin->getWindowHandle(aProcessIdent, css::lang::SystemDependent::SYSTEM_XWINDOW);
+ css::awt::SystemDependentXWindow tmp;
+ aAny >>= tmp;
+ pParentWidget = GetGtkSalData()->GetGtkDisplay()->findGtkWidgetForNativeHandle(tmp.WindowHandle);
+ }
+ }
+ }
+
+ return pParentWidget;
+}
+
+// XInitialization
+
+void SAL_CALL SalGtkFilePicker::initialize( const uno::Sequence<uno::Any>& aArguments )
+{
+ // parameter checking
+ uno::Any aAny;
+ if( !aArguments.hasElements() )
+ throw lang::IllegalArgumentException(
+ "no arguments",
+ static_cast<XFilePicker2*>( this ), 1 );
+
+ aAny = aArguments[0];
+
+ if( ( aAny.getValueType() != cppu::UnoType<sal_Int16>::get()) &&
+ (aAny.getValueType() != cppu::UnoType<sal_Int8>::get()) )
+ throw lang::IllegalArgumentException(
+ "invalid argument type",
+ static_cast<XFilePicker2*>( this ), 1 );
+
+ sal_Int16 templateId = -1;
+ aAny >>= templateId;
+
+ impl_initialize(GetParentWidget(aArguments), templateId);
+}
+
+void SalGtkFilePicker::impl_initialize(GtkWidget* pParentWidget, sal_Int16 templateId)
+{
+ m_pParentWidget = pParentWidget;
+
+ GtkFileChooserAction eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ OString sOpen = getOpenText();
+ OString sSave = getSaveText();
+ const gchar *first_button_text = sOpen.getStr();
+
+ SolarMutexGuard g;
+
+ // TODO: extract full semantic from
+ // svtools/source/filepicker/filepicker.cxx (getWinBits)
+ switch( templateId )
+ {
+ case FILEOPEN_SIMPLE:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ break;
+ case FILESAVE_SIMPLE:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = sSave.getStr();
+ break;
+ case FILESAVE_AUTOEXTENSION_PASSWORD:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = sSave.getStr();
+ mbToggleVisibility[PASSWORD] = true;
+ mbToggleVisibility[GPGENCRYPTION] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = sSave.getStr();
+ mbToggleVisibility[PASSWORD] = true;
+ mbToggleVisibility[GPGENCRYPTION] = true;
+ mbToggleVisibility[FILTEROPTIONS] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION_SELECTION:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE; // SELECT_FOLDER ?
+ first_button_text = sSave.getStr();
+ mbToggleVisibility[SELECTION] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION_TEMPLATE:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = sSave.getStr();
+ mbListVisibility[TEMPLATE] = true;
+ // TODO
+ break;
+ case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[LINK] = true;
+ mbToggleVisibility[PREVIEW] = true;
+ mbListVisibility[IMAGE_TEMPLATE] = true;
+ // TODO
+ break;
+ case FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[LINK] = true;
+ mbToggleVisibility[PREVIEW] = true;
+ mbListVisibility[IMAGE_ANCHOR] = true;
+ // TODO
+ break;
+ case FILEOPEN_PLAY:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbButtonVisibility[PLAY] = true;
+ // TODO
+ break;
+ case FILEOPEN_LINK_PLAY:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[LINK] = true;
+ mbButtonVisibility[PLAY] = true;
+ // TODO
+ break;
+ case FILEOPEN_READONLY_VERSION:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[READONLY] = true;
+ mbListVisibility[VERSION] = true;
+ break;
+ case FILEOPEN_LINK_PREVIEW:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[LINK] = true;
+ mbToggleVisibility[PREVIEW] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = sSave.getStr();
+ // TODO
+ break;
+ case FILEOPEN_PREVIEW:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = sOpen.getStr();
+ mbToggleVisibility[PREVIEW] = true;
+ // TODO
+ break;
+ default:
+ throw lang::IllegalArgumentException(
+ "Unknown template",
+ static_cast< XFilePicker2* >( this ),
+ 1 );
+ }
+
+ if( GTK_FILE_CHOOSER_ACTION_SAVE == eAction )
+ {
+ OUString aFilePickerTitle(getResString( FILE_PICKER_TITLE_SAVE ));
+ gtk_window_set_title ( GTK_WINDOW( m_pDialog ),
+ OUStringToOString( aFilePickerTitle, RTL_TEXTENCODING_UTF8 ).getStr() );
+ }
+
+ gtk_file_chooser_set_action( GTK_FILE_CHOOSER( m_pDialog ), eAction);
+ m_pButtons[CANCEL] = gtk_dialog_add_button(GTK_DIALOG(m_pDialog), getCancelText().getStr(), GTK_RESPONSE_CANCEL);
+ mbButtonVisibility[CANCEL] = true;
+
+ if (mbButtonVisibility[PLAY])
+ {
+ OString aPlay = OUStringToOString(getResString(PUSHBUTTON_PLAY), RTL_TEXTENCODING_UTF8);
+ m_pButtons[PLAY] = gtk_dialog_add_button(GTK_DIALOG(m_pDialog), aPlay.getStr(), 1);
+ }
+
+ m_pButtons[OK] = gtk_dialog_add_button(GTK_DIALOG(m_pDialog), first_button_text, GTK_RESPONSE_ACCEPT);
+ mbButtonVisibility[OK] = true;
+
+ gtk_dialog_set_default_response( GTK_DIALOG (m_pDialog), GTK_RESPONSE_ACCEPT );
+
+ // Setup special flags
+ for( int nTVIndex = 0; nTVIndex < TOGGLE_LAST; nTVIndex++ )
+ {
+ if( mbToggleVisibility[nTVIndex] )
+ gtk_widget_show( m_pToggles[ nTVIndex ] );
+ }
+
+ for( int nTVIndex = 0; nTVIndex < LIST_LAST; nTVIndex++ )
+ {
+ if( mbListVisibility[nTVIndex] )
+ {
+ gtk_widget_set_sensitive( m_pLists[ nTVIndex ], false );
+ gtk_widget_show( m_pLists[ nTVIndex ] );
+ gtk_widget_show( m_pListLabels[ nTVIndex ] );
+ gtk_widget_show( m_pHBoxs[ nTVIndex ] );
+ }
+ }
+
+ mbInitialized = true;
+}
+
+void SalGtkFilePicker::preview_toggled_cb( GObject *cb, SalGtkFilePicker* pobjFP )
+{
+ if( pobjFP->mbToggleVisibility[PREVIEW] )
+ pobjFP->setShowState( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( cb ) ) );
+}
+
+// XCancellable
+
+void SAL_CALL SalGtkFilePicker::cancel()
+{
+ SolarMutexGuard g;
+
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ // TODO m_pImpl->cancel();
+}
+
+// Misc
+
+void SalGtkFilePicker::SetCurFilter( const OUString& rFilter )
+{
+ // Get all the filters already added
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GListModel *filters = gtk_file_chooser_get_filters(GTK_FILE_CHOOSER(m_pDialog));
+#else
+ GSList *filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(m_pDialog));
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ int nIndex = 0;
+ while (gpointer pElem = g_list_model_get_item(filters, nIndex))
+ {
+ GtkFileFilter* pFilter = static_cast<GtkFileFilter*>(pElem);
+ ++nIndex;
+#else
+ for( GSList *iter = filters; iter; iter = iter->next )
+ {
+ GtkFileFilter* pFilter = static_cast<GtkFileFilter*>( iter->data );
+#endif
+ const gchar * filtername = gtk_file_filter_get_name( pFilter );
+ OUString sFilterName( filtername, strlen( filtername ), RTL_TEXTENCODING_UTF8 );
+
+ OUString aShrunkName = shrinkFilterName( rFilter );
+ if( aShrunkName == sFilterName )
+ {
+ SAL_INFO( "vcl.gtk", "actually setting " << filtername );
+ gtk_file_chooser_set_filter( GTK_FILE_CHOOSER( m_pDialog ), pFilter );
+ break;
+ }
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_slist_free( filters );
+#else
+ g_object_unref (filters);
+#endif
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+extern "C"
+{
+static gboolean
+case_insensitive_filter (const GtkFileFilterInfo *filter_info, gpointer data)
+{
+ bool bRetval = false;
+ const char *pFilter = static_cast<const char *>(data);
+
+ g_return_val_if_fail( data != nullptr, false );
+ g_return_val_if_fail( filter_info != nullptr, false );
+
+ if( !filter_info->uri )
+ return false;
+
+ const char *pExtn = strrchr( filter_info->uri, '.' );
+ if( !pExtn )
+ return false;
+ pExtn++;
+
+ if( !g_ascii_strcasecmp( pFilter, pExtn ) )
+ bRetval = true;
+
+ SAL_INFO( "vcl.gtk", "'" << filter_info->uri << "' match extn '" << pExtn << "' vs '" << pFilter << "' yields " << bRetval );
+
+ return bRetval;
+}
+}
+#endif
+
+GtkFileFilter* SalGtkFilePicker::implAddFilter( const OUString& rFilter, const OUString& rType )
+{
+ GtkFileFilter *filter = gtk_file_filter_new();
+
+ OUString aShrunkName = shrinkFilterName( rFilter );
+ OString aFilterName = OUStringToOString( aShrunkName, RTL_TEXTENCODING_UTF8 );
+ gtk_file_filter_set_name( filter, aFilterName.getStr() );
+
+ OUStringBuffer aTokens;
+
+ bool bAllGlob = rType == "*.*" || rType == "*";
+ if (bAllGlob)
+ gtk_file_filter_add_pattern( filter, "*" );
+ else
+ {
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OUString aToken = rType.getToken( 0, ';', nIndex );
+ // Assume all have the "*.<extn>" syntax
+ sal_Int32 nStarDot = aToken.lastIndexOf( "*." );
+ if (nStarDot >= 0)
+ aToken = aToken.copy( nStarDot + 2 );
+ if (!aToken.isEmpty())
+ {
+ if (!aTokens.isEmpty())
+ aTokens.append(",");
+ aTokens.append(aToken);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_file_filter_add_suffix(filter, aToken.toUtf8().getStr());
+#else
+ gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_URI,
+ case_insensitive_filter,
+ g_strdup( OUStringToOString(aToken, RTL_TEXTENCODING_UTF8).getStr() ),
+ g_free );
+#endif
+
+ SAL_INFO( "vcl.gtk", "fustering with " << aToken );
+ }
+#if OSL_DEBUG_LEVEL > 0
+ else
+ {
+ g_warning( "Duff filter token '%s'\n",
+ OUStringToOString(
+ o3tl::getToken(rType, 0, ';', nIndex ), RTL_TEXTENCODING_UTF8 ).getStr() );
+ }
+#endif
+ }
+ while( nIndex >= 0 );
+ }
+
+ gtk_file_chooser_add_filter( GTK_FILE_CHOOSER( m_pDialog ), filter );
+
+ if (!bAllGlob)
+ {
+ GtkTreeIter iter;
+ gtk_list_store_append (m_pFilterStore, &iter);
+ gtk_list_store_set (m_pFilterStore, &iter,
+ 0, OUStringToOString(shrinkFilterName( rFilter, true ), RTL_TEXTENCODING_UTF8).getStr(),
+ 1, OUStringToOString(aTokens, RTL_TEXTENCODING_UTF8).getStr(),
+ 2, aFilterName.getStr(),
+ 3, OUStringToOString(rType, RTL_TEXTENCODING_UTF8).getStr(),
+ -1);
+ }
+ return filter;
+}
+
+void SalGtkFilePicker::implAddFilterGroup( const Sequence< StringPair >& _rFilters )
+{
+ // Gtk+ has no filter group concept I think so ...
+ // implAddFilter( _rFilter, String() );
+ for( const auto& rSubFilter : _rFilters )
+ implAddFilter( rSubFilter.First, rSubFilter.Second );
+}
+
+void SalGtkFilePicker::SetFilters()
+{
+ if (m_aInitialFilter.isEmpty())
+ m_aInitialFilter = m_aCurrentFilter;
+
+ OUString sPseudoFilter;
+ if( GTK_FILE_CHOOSER_ACTION_SAVE == gtk_file_chooser_get_action( GTK_FILE_CHOOSER( m_pDialog ) ) )
+ {
+ std::set<OUString> aAllFormats;
+ if( m_pFilterVector )
+ {
+ for (auto & filter : *m_pFilterVector)
+ {
+ if( filter.hasSubFilters() )
+ { // it's a filter group
+ css::uno::Sequence< css::beans::StringPair > aSubFilters;
+ filter.getSubFilters( aSubFilters );
+ for( const auto& rSubFilter : std::as_const(aSubFilters) )
+ aAllFormats.insert(rSubFilter.Second);
+ }
+ else
+ aAllFormats.insert(filter.getFilter());
+ }
+ }
+ if (aAllFormats.size() > 1)
+ {
+ OUStringBuffer sAllFilter;
+ for (auto const& format : aAllFormats)
+ {
+ if (!sAllFilter.isEmpty())
+ sAllFilter.append(";");
+ sAllFilter.append(format);
+ }
+ sPseudoFilter = getResString(FILE_PICKER_ALLFORMATS);
+ m_pPseudoFilter = implAddFilter( sPseudoFilter, sAllFilter.makeStringAndClear() );
+ }
+ }
+
+ if( m_pFilterVector )
+ {
+ for (auto & filter : *m_pFilterVector)
+ {
+ if( filter.hasSubFilters() )
+ { // it's a filter group
+
+ css::uno::Sequence< css::beans::StringPair > aSubFilters;
+ filter.getSubFilters( aSubFilters );
+
+ implAddFilterGroup( aSubFilters );
+ }
+ else
+ {
+ // it's a single filter
+
+ implAddFilter( filter.getTitle(), filter.getFilter() );
+ }
+ }
+ }
+
+ // We always hide the expander now and depend on the user using the glob
+ // list, or type a filename suffix, to select a filter by inference.
+ gtk_widget_hide(m_pFilterExpander);
+
+ // set the default filter
+ if (!sPseudoFilter.isEmpty())
+ SetCurFilter( sPseudoFilter );
+ else if(!m_aCurrentFilter.isEmpty())
+ SetCurFilter( m_aCurrentFilter );
+
+ SAL_INFO( "vcl.gtk", "end setting filters");
+}
+
+SalGtkFilePicker::~SalGtkFilePicker()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ SolarMutexGuard g;
+
+ int i;
+
+ for( i = 0; i < TOGGLE_LAST; i++ )
+ gtk_widget_destroy( m_pToggles[i] );
+
+ for( i = 0; i < LIST_LAST; i++ )
+ {
+ gtk_widget_destroy( m_pListLabels[i] );
+ gtk_widget_destroy( m_pLists[i] );
+ gtk_widget_destroy( m_pHBoxs[i] );
+ }
+
+ m_pFilterVector.reset();
+
+ gtk_widget_destroy( m_pVBox );
+#endif
+}
+
+uno::Reference< ui::dialogs::XFilePicker2 >
+GtkInstance::createFilePicker( const css::uno::Reference< css::uno::XComponentContext > &xMSF )
+{
+ return uno::Reference< ui::dialogs::XFilePicker2 >(
+ new SalGtkFilePicker( xMSF ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx
new file mode 100644
index 000000000..fdc701a20
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <cppuhelper/compbase.hxx>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/ui/dialogs/XFilePickerControlAccess.hpp>
+#include <com/sun/star/ui/dialogs/XFilePreview.hpp>
+#include <com/sun/star/ui/dialogs/XFilePicker3.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/beans/StringPair.hpp>
+
+#include <vector>
+#include <memory>
+#include <rtl/ustring.hxx>
+
+#include "SalGtkPicker.hxx"
+
+// Implementation class for the XFilePicker Interface
+
+struct FilterEntry;
+struct ElementEntry_Impl;
+
+
+typedef cppu::WeakComponentImplHelper<
+ css::ui::dialogs::XFilePickerControlAccess,
+ css::ui::dialogs::XFilePreview,
+ css::ui::dialogs::XFilePicker3,
+ css::lang::XInitialization
+ > SalGtkFilePicker_Base;
+
+class SalGtkFilePicker : public SalGtkPicker, public SalGtkFilePicker_Base
+{
+ public:
+
+ // constructor
+ SalGtkFilePicker( const css::uno::Reference< css::uno::XComponentContext >& xServiceMgr );
+
+ // XFilePickerNotifier
+
+ virtual void SAL_CALL addFilePickerListener( const css::uno::Reference< css::ui::dialogs::XFilePickerListener >& xListener ) override;
+ virtual void SAL_CALL removeFilePickerListener( const css::uno::Reference< css::ui::dialogs::XFilePickerListener >& xListener ) override;
+
+ // XExecutableDialog functions
+
+ virtual void SAL_CALL setTitle( const OUString& aTitle ) override;
+
+ virtual sal_Int16 SAL_CALL execute() override;
+
+ // XFilePicker functions
+
+ virtual void SAL_CALL setMultiSelectionMode( sal_Bool bMode ) override;
+
+ virtual void SAL_CALL setDefaultName( const OUString& aName ) override;
+
+ virtual void SAL_CALL setDisplayDirectory( const OUString& aDirectory ) override;
+
+ virtual OUString SAL_CALL getDisplayDirectory( ) override;
+
+ virtual css::uno::Sequence< OUString > SAL_CALL getFiles( ) override;
+
+ // XFilePicker2 functions
+
+ virtual css::uno::Sequence< OUString > SAL_CALL getSelectedFiles() override;
+
+ // XFilterManager functions
+
+ virtual void SAL_CALL appendFilter( const OUString& aTitle, const OUString& aFilter ) override;
+
+ virtual void SAL_CALL setCurrentFilter( const OUString& aTitle ) override;
+
+ virtual OUString SAL_CALL getCurrentFilter( ) override;
+
+ // XFilterGroupManager functions
+
+ virtual void SAL_CALL appendFilterGroup( const OUString& sGroupTitle, const css::uno::Sequence< css::beans::StringPair >& aFilters ) override;
+
+ // XFilePickerControlAccess functions
+
+ virtual void SAL_CALL setValue( sal_Int16 nControlId, sal_Int16 nControlAction, const css::uno::Any& aValue ) override;
+
+ virtual css::uno::Any SAL_CALL getValue( sal_Int16 aControlId, sal_Int16 aControlAction ) override;
+
+ virtual void SAL_CALL enableControl( sal_Int16 nControlId, sal_Bool bEnable ) override;
+
+ virtual void SAL_CALL setLabel( sal_Int16 nControlId, const OUString& aLabel ) override;
+
+ virtual OUString SAL_CALL getLabel( sal_Int16 nControlId ) override;
+
+ // XFilePreview
+
+ virtual css::uno::Sequence< sal_Int16 > SAL_CALL getSupportedImageFormats( ) override;
+
+ virtual sal_Int32 SAL_CALL getTargetColorDepth( ) override;
+
+ virtual sal_Int32 SAL_CALL getAvailableWidth( ) override;
+
+ virtual sal_Int32 SAL_CALL getAvailableHeight( ) override;
+
+ virtual void SAL_CALL setImage( sal_Int16 aImageFormat, const css::uno::Any& aImage ) override;
+
+ virtual sal_Bool SAL_CALL setShowState( sal_Bool bShowState ) override;
+
+ virtual sal_Bool SAL_CALL getShowState( ) override;
+
+ // XInitialization
+
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+ // XCancellable
+
+ virtual void SAL_CALL cancel( ) override;
+
+ // FilePicker Event functions
+
+ private:
+ SalGtkFilePicker( const SalGtkFilePicker& ) = delete;
+ SalGtkFilePicker& operator=( const SalGtkFilePicker& ) = delete;
+
+ bool FilterNameExists( const OUString& rTitle );
+ bool FilterNameExists( const css::uno::Sequence< css::beans::StringPair >& _rGroupedFilters );
+
+ void ensureFilterVector( const OUString& _rInitialCurrentFilter );
+
+ void impl_fileSelectionChanged( const css::ui::dialogs::FilePickerEvent& aEvent );
+ void impl_directoryChanged( const css::ui::dialogs::FilePickerEvent& aEvent );
+ void impl_controlStateChanged( const css::ui::dialogs::FilePickerEvent& aEvent );
+ void impl_initialize(GtkWidget* pParentWidget, sal_Int16 templateId);
+
+ private:
+ css::uno::Reference< css::ui::dialogs::XFilePickerListener >
+ m_xListener;
+ std::unique_ptr<std::vector<FilterEntry>> m_pFilterVector;
+ GtkWidget *m_pVBox;
+ GtkWidget *m_pFilterExpander;
+ GtkWidget *m_pFilterView;
+ GtkListStore *m_pFilterStore;
+
+ enum {
+ AUTOEXTENSION,
+ PASSWORD,
+ FILTEROPTIONS,
+ READONLY,
+ LINK,
+ PREVIEW,
+ SELECTION,
+ GPGENCRYPTION,
+ TOGGLE_LAST
+ };
+
+ GtkWidget *m_pToggles[ TOGGLE_LAST ];
+
+ bool mbToggleVisibility[TOGGLE_LAST];
+
+ enum {
+ OK,
+ CANCEL,
+ PLAY,
+ BUTTON_LAST };
+
+ GtkWidget *m_pButtons[ BUTTON_LAST ];
+
+ enum {
+ VERSION,
+ TEMPLATE,
+ IMAGE_TEMPLATE,
+ IMAGE_ANCHOR,
+ LIST_LAST
+ };
+
+ GtkWidget *m_pHBoxs[ LIST_LAST ];
+ GtkWidget *m_pLists[ LIST_LAST ];
+ GtkWidget *m_pListLabels[ LIST_LAST ];
+ bool mbListVisibility[ LIST_LAST ];
+ bool mbButtonVisibility[ BUTTON_LAST ];
+ gulong mnHID_FolderChange;
+ gulong mnHID_SelectionChange;
+
+ OUString m_aCurrentFilter;
+ OUString m_aInitialFilter;
+
+ bool bVersionWidthUnset;
+ bool mbPreviewState;
+ bool mbInitialized;
+ gulong mHID_Preview;
+ GtkWidget* m_pPreview;
+ GtkFileFilter* m_pPseudoFilter;
+ static constexpr sal_Int32 g_PreviewImageWidth = 256;
+ static constexpr sal_Int32 g_PreviewImageHeight = 256;
+
+ GtkWidget *getWidget( sal_Int16 nControlId, GType *pType = nullptr);
+
+ void SetCurFilter( const OUString& rFilter );
+ void SetFilters();
+ void UpdateFilterfromUI();
+
+ void implChangeType( GtkTreeSelection *selection );
+ GtkFileFilter * implAddFilter( const OUString& rFilter, const OUString& rType );
+ void implAddFilterGroup(
+ const css::uno::Sequence< css::beans::StringPair>& _rFilters );
+ void updateCurrentFilterFromName(const gchar* filtername);
+ void unselect_type();
+ void InitialMapping();
+
+ void HandleSetListValue(GtkComboBox *pWidget, sal_Int16 nControlAction,
+ const css::uno::Any& rValue);
+ static css::uno::Any HandleGetListValue(GtkComboBox *pWidget, sal_Int16 nControlAction);
+
+ static void expander_changed_cb( GtkExpander *expander, SalGtkFilePicker *pobjFP );
+ static void preview_toggled_cb( GObject *cb, SalGtkFilePicker *pobjFP );
+ static void filter_changed_cb( GtkFileChooser *file_chooser, GParamSpec *pspec, SalGtkFilePicker *pobjFP );
+ static void type_changed_cb( GtkTreeSelection *selection, SalGtkFilePicker *pobjFP );
+ static void folder_changed_cb (GtkFileChooser *file_chooser, SalGtkFilePicker *pobjFP);
+ static void selection_changed_cb (GtkFileChooser *file_chooser, SalGtkFilePicker *pobjFP);
+ static void update_preview_cb (GtkFileChooser *file_chooser, SalGtkFilePicker *pobjFP);
+ static void dialog_mapped_cb(GtkWidget *widget, SalGtkFilePicker *pobjFP);
+ public:
+ virtual ~SalGtkFilePicker() override;
+
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx
new file mode 100644
index 000000000..ae617d5af
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx
@@ -0,0 +1,211 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifdef AIX
+#define _LINUX_SOURCE_COMPAT
+#include <sys/timer.h>
+#undef _LINUX_SOURCE_COMPAT
+#endif
+
+#include <config_gio.h>
+
+#include <com/sun/star/awt/Toolkit.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+#include <vcl/svapp.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include "SalGtkFolderPicker.hxx"
+#include <sal/log.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+// constructor
+
+SalGtkFolderPicker::SalGtkFolderPicker( const uno::Reference< uno::XComponentContext >& xContext ) :
+ SalGtkPicker( xContext )
+{
+ m_pDialog = gtk_file_chooser_dialog_new(
+ OUStringToOString( getResString( FOLDERPICKER_TITLE ), RTL_TEXTENCODING_UTF8 ).getStr(),
+ nullptr, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, getCancelText().getStr(), GTK_RESPONSE_CANCEL,
+ getOKText().getStr(), GTK_RESPONSE_ACCEPT, nullptr );
+ gtk_window_set_modal(GTK_WINDOW(m_pDialog), true);
+
+ gtk_dialog_set_default_response( GTK_DIALOG (m_pDialog), GTK_RESPONSE_ACCEPT );
+#if !GTK_CHECK_VERSION(4, 0, 0)
+#if ENABLE_GIO
+ gtk_file_chooser_set_local_only( GTK_FILE_CHOOSER( m_pDialog ), false );
+#endif
+#endif
+ gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER( m_pDialog ), false );
+}
+
+void SAL_CALL SalGtkFolderPicker::setDisplayDirectory( const OUString& aDirectory )
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+ OString aTxt = unicodetouri( aDirectory );
+ if( aTxt.isEmpty() ){
+ aTxt = unicodetouri("file:///.");
+ }
+
+ if( aTxt.endsWith("/") )
+ aTxt = aTxt.copy( 0, aTxt.getLength() - 1 );
+
+ SAL_INFO( "vcl", "setting path to " << aTxt );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GFile* pPath = g_file_new_for_uri(aTxt.getStr());
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(m_pDialog), pPath, nullptr);
+ g_object_unref(pPath);
+#else
+ gtk_file_chooser_set_current_folder_uri(GTK_FILE_CHOOSER(m_pDialog), aTxt.getStr());
+#endif
+}
+
+OUString SAL_CALL SalGtkFolderPicker::getDisplayDirectory()
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GFile* pPath =
+ gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(m_pDialog));
+ gchar* pCurrentFolder = g_file_get_uri(pPath);
+ g_object_unref(pPath);
+#else
+ gchar* pCurrentFolder =
+ gtk_file_chooser_get_current_folder_uri(GTK_FILE_CHOOSER(m_pDialog));
+#endif
+
+ OUString aCurrentFolderName = uritounicode(pCurrentFolder);
+ g_free( pCurrentFolder );
+
+ return aCurrentFolderName;
+}
+
+OUString SAL_CALL SalGtkFolderPicker::getDirectory()
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GFile* pPath =
+ gtk_file_chooser_get_file(GTK_FILE_CHOOSER(m_pDialog));
+ gchar* pSelectedFolder = g_file_get_uri(pPath);
+ g_object_unref(pPath);
+#else
+ gchar* pSelectedFolder =
+ gtk_file_chooser_get_uri( GTK_FILE_CHOOSER( m_pDialog ) );
+#endif
+ OUString aSelectedFolderName = uritounicode(pSelectedFolder);
+ g_free( pSelectedFolder );
+
+ return aSelectedFolderName;
+}
+
+void SAL_CALL SalGtkFolderPicker::setDescription( const OUString& /*rDescription*/ )
+{
+}
+
+// XExecutableDialog functions
+
+void SAL_CALL SalGtkFolderPicker::setTitle( const OUString& aTitle )
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+ OString aWindowTitle = OUStringToOString( aTitle, RTL_TEXTENCODING_UTF8 );
+
+ gtk_window_set_title( GTK_WINDOW( m_pDialog ), aWindowTitle.getStr() );
+}
+
+sal_Int16 SAL_CALL SalGtkFolderPicker::execute()
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+ sal_Int16 retVal = 0;
+
+ uno::Reference< awt::XExtendedToolkit > xToolkit =
+ awt::Toolkit::create(m_xContext);
+
+ uno::Reference<frame::XDesktop> xDesktop = frame::Desktop::create(m_xContext);
+
+ GtkWindow *pParent = GTK_WINDOW(m_pParentWidget);
+ if (!pParent)
+ {
+ SAL_WARN( "vcl.gtk", "no parent widget set");
+ pParent = RunDialog::GetTransientFor();
+ }
+ if (pParent)
+ gtk_window_set_transient_for(GTK_WINDOW(m_pDialog), pParent);
+ rtl::Reference<RunDialog> pRunDialog = new RunDialog(m_pDialog, xToolkit, xDesktop);
+ gint nStatus = pRunDialog->run();
+ switch( nStatus )
+ {
+ case GTK_RESPONSE_ACCEPT:
+ retVal = ExecutableDialogResults::OK;
+ break;
+ case GTK_RESPONSE_CANCEL:
+ retVal = ExecutableDialogResults::CANCEL;
+ break;
+ default:
+ retVal = 0;
+ break;
+ }
+ gtk_widget_hide(m_pDialog);
+
+ return retVal;
+}
+
+// XInitialization
+
+void SAL_CALL SalGtkFolderPicker::initialize(const uno::Sequence<uno::Any>& aArguments)
+{
+ m_pParentWidget = GetParentWidget(aArguments);
+}
+
+// XCancellable
+
+void SAL_CALL SalGtkFolderPicker::cancel()
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+ // TODO m_pImpl->cancel();
+}
+
+uno::Reference< ui::dialogs::XFolderPicker2 >
+GtkInstance::createFolderPicker( const uno::Reference< uno::XComponentContext > &xMSF )
+{
+ return uno::Reference< ui::dialogs::XFolderPicker2 >(
+ new SalGtkFolderPicker( xMSF ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.hxx b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.hxx
new file mode 100644
index 000000000..5b1d8dceb
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.hxx
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <memory>
+#include <rtl/ustring.hxx>
+#include <cppuhelper/implbase.hxx>
+
+#include "SalGtkPicker.hxx"
+
+class SalGtkFolderPicker :
+ public SalGtkPicker,
+ public cppu::WeakImplHelper<css::ui::dialogs::XFolderPicker2, css::lang::XInitialization>
+{
+ public:
+
+ // constructor
+ SalGtkFolderPicker( const css::uno::Reference< css::uno::XComponentContext >& xServiceMgr );
+
+ // XExecutableDialog functions
+
+ virtual void SAL_CALL setTitle( const OUString& aTitle ) override;
+
+ virtual sal_Int16 SAL_CALL execute( ) override;
+
+ // XFolderPicker functions
+
+ virtual void SAL_CALL setDisplayDirectory( const OUString& rDirectory ) override;
+
+ virtual OUString SAL_CALL getDisplayDirectory( ) override;
+
+ virtual OUString SAL_CALL getDirectory( ) override;
+
+ virtual void SAL_CALL setDescription( const OUString& rDescription ) override;
+
+ // XInitialization
+
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+ // XCancellable
+
+ virtual void SAL_CALL cancel( ) override;
+
+ private:
+ SalGtkFolderPicker( const SalGtkFolderPicker& ) = delete;
+ SalGtkFolderPicker& operator=( const SalGtkFolderPicker& ) = delete;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkPicker.cxx b/vcl/unx/gtk3/fpicker/SalGtkPicker.cxx
new file mode 100644
index 000000000..68172ad10
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkPicker.cxx
@@ -0,0 +1,300 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifdef AIX
+#define _LINUX_SOURCE_COMPAT
+#include <sys/timer.h>
+#undef _LINUX_SOURCE_COMPAT
+#endif
+
+#include <com/sun/star/frame/TerminationVetoException.hpp>
+#include <com/sun/star/lang/XMultiComponentFactory.hpp>
+#include <com/sun/star/uri/ExternalUriReferenceTranslator.hpp>
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <vcl/svapp.hxx>
+#include <tools/urlobj.hxx>
+
+#include <vcl/window.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include <unx/gtk/gtkframe.hxx>
+#include "SalGtkPicker.hxx"
+
+using namespace ::rtl;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+OUString SalGtkPicker::uritounicode(const gchar* pIn) const
+{
+ if (!pIn)
+ return OUString();
+
+ OUString sURL( pIn, strlen(pIn),
+ RTL_TEXTENCODING_UTF8 );
+
+ INetURLObject aURL(sURL);
+ if (INetProtocol::File == aURL.GetProtocol())
+ {
+ // all the URLs are handled by office in UTF-8
+ // so the Gnome FP related URLs should be converted accordingly
+ OUString aNewURL = uri::ExternalUriReferenceTranslator::create( m_xContext )->translateToInternal(sURL);
+ if( !aNewURL.isEmpty() )
+ sURL = aNewURL;
+ }
+ return sURL;
+}
+
+OString SalGtkPicker::unicodetouri(const OUString &rURL) const
+{
+ // all the URLs are handled by office in UTF-8 ( and encoded with "%xx" codes based on UTF-8 )
+ // so the Gnome FP related URLs should be converted accordingly
+ OString sURL = OUStringToOString(rURL, RTL_TEXTENCODING_UTF8);
+ INetURLObject aURL(rURL);
+ if (INetProtocol::File == aURL.GetProtocol())
+ {
+ OUString aNewURL = uri::ExternalUriReferenceTranslator::create( m_xContext )->translateToExternal(rURL);
+
+ if( !aNewURL.isEmpty() )
+ {
+ // At this point the URL should contain ascii characters only actually
+ sURL = OUStringToOString( aNewURL, osl_getThreadTextEncoding() );
+ }
+ }
+ return sURL;
+}
+
+extern "C"
+{
+ static gboolean canceldialog(RunDialog *pDialog)
+ {
+ SolarMutexGuard g;
+ pDialog->cancel();
+ return false;
+ }
+}
+
+GtkWindow* RunDialog::GetTransientFor()
+{
+ vcl::Window * pWindow = ::Application::GetActiveTopWindow();
+ if (!pWindow)
+ return nullptr;
+ GtkSalFrame *pFrame = dynamic_cast<GtkSalFrame*>(pWindow->ImplGetFrame());
+ if (!pFrame)
+ return nullptr;
+ return GTK_WINDOW(widget_get_toplevel(pFrame->getWindow()));
+}
+
+RunDialog::RunDialog(GtkWidget *pDialog, const uno::Reference<awt::XExtendedToolkit>& rToolkit,
+ const uno::Reference<frame::XDesktop>& rDesktop)
+ : cppu::WeakComponentImplHelper<awt::XTopWindowListener, frame::XTerminateListener>(maLock)
+ , mpDialog(pDialog)
+ , mbTerminateDesktop(false)
+ , mxToolkit(rToolkit)
+ , mxDesktop(rDesktop)
+{
+}
+
+RunDialog::~RunDialog()
+{
+ SolarMutexGuard g;
+
+ g_source_remove_by_user_data (this);
+}
+
+void SAL_CALL RunDialog::windowOpened(const css::lang::EventObject& e)
+{
+ SolarMutexGuard g;
+
+ //Don't popdown dialogs if a tooltip appears elsewhere, that's ok, but do pop down
+ //if another dialog/frame is launched.
+ css::uno::Reference<css::accessibility::XAccessible> xAccessible(e.Source, css::uno::UNO_QUERY);
+ if (xAccessible.is())
+ {
+ css::uno::Reference<css::accessibility::XAccessibleContext> xContext(xAccessible->getAccessibleContext());
+ if (xContext.is() && xContext->getAccessibleRole() == css::accessibility::AccessibleRole::TOOL_TIP)
+ {
+ return;
+ }
+ }
+
+ g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(canceldialog), this, nullptr);
+}
+
+void SAL_CALL RunDialog::queryTermination( const css::lang::EventObject& )
+{
+ SolarMutexGuard g;
+
+ g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(canceldialog), this, nullptr);
+
+ mbTerminateDesktop = true;
+
+ throw css::frame::TerminationVetoException();
+}
+
+void SAL_CALL RunDialog::notifyTermination( const css::lang::EventObject& )
+{
+}
+
+void RunDialog::cancel()
+{
+ gtk_dialog_response( GTK_DIALOG( mpDialog ), GTK_RESPONSE_CANCEL );
+ gtk_widget_hide( mpDialog );
+}
+
+namespace
+{
+ class ExecuteInfo
+ {
+ private:
+ css::uno::Reference<css::frame::XDesktop> mxDesktop;
+ public:
+ ExecuteInfo(const css::uno::Reference<css::frame::XDesktop>& rDesktop)
+ : mxDesktop(rDesktop)
+ {
+ }
+ void terminate()
+ {
+ mxDesktop->terminate();
+ }
+ };
+}
+
+gint RunDialog::run()
+{
+ if (mxToolkit.is())
+ mxToolkit->addTopWindowListener(this);
+
+ mxDesktop->addTerminateListener(this);
+
+ // [Inc/Dec]ModalCount on parent frame so it knows it is in modal mode
+ GtkWindow* pParent = gtk_window_get_transient_for(GTK_WINDOW(mpDialog));
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(GTK_WIDGET(pParent)) : nullptr;
+ VclPtr<vcl::Window> xFrameWindow = pFrame ? pFrame->GetWindow() : nullptr;
+ if (xFrameWindow)
+ {
+ xFrameWindow->IncModalCount();
+ xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(true);
+ }
+
+ gint nStatus = gtk_dialog_run(GTK_DIALOG(mpDialog));
+
+ if (xFrameWindow)
+ {
+ xFrameWindow->DecModalCount();
+ xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(false);
+ }
+
+ mxDesktop->removeTerminateListener(this);
+
+ if (mxToolkit.is())
+ mxToolkit->removeTopWindowListener(this);
+
+ if (mbTerminateDesktop)
+ {
+ ExecuteInfo* pExecuteInfo = new ExecuteInfo(mxDesktop);
+ Application::PostUserEvent(LINK(nullptr, RunDialog, TerminateDesktop), pExecuteInfo);
+ }
+
+ return nStatus;
+}
+
+IMPL_STATIC_LINK(RunDialog, TerminateDesktop, void*, p, void)
+{
+ ExecuteInfo* pExecuteInfo = static_cast<ExecuteInfo*>(p);
+ pExecuteInfo->terminate();
+ delete pExecuteInfo;
+}
+
+SalGtkPicker::SalGtkPicker( const uno::Reference<uno::XComponentContext>& xContext )
+ : m_pParentWidget(nullptr)
+ , m_pDialog(nullptr)
+ , m_xContext(xContext)
+{
+}
+
+SalGtkPicker::~SalGtkPicker()
+{
+ SolarMutexGuard g;
+
+ if (m_pDialog)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(m_pDialog);
+#else
+ gtk_window_destroy(GTK_WINDOW(m_pDialog));
+#endif
+ }
+}
+
+void SalGtkPicker::implsetDisplayDirectory( const OUString& aDirectory )
+{
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ OString aTxt = unicodetouri(aDirectory);
+ if( aTxt.isEmpty() ){
+ aTxt = unicodetouri("file:///.");
+ }
+
+ if( aTxt.endsWith("/") )
+ aTxt = aTxt.copy( 0, aTxt.getLength() - 1 );
+
+ SAL_INFO( "vcl", "setting path to " << aTxt );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GFile* pPath = g_file_new_for_uri(aTxt.getStr());
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(m_pDialog), pPath, nullptr);
+ g_object_unref(pPath);
+#else
+ gtk_file_chooser_set_current_folder_uri(GTK_FILE_CHOOSER(m_pDialog), aTxt.getStr());
+#endif
+}
+
+OUString SalGtkPicker::implgetDisplayDirectory()
+{
+ OSL_ASSERT( m_pDialog != nullptr );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GFile* pPath =
+ gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(m_pDialog));
+ gchar* pCurrentFolder = g_file_get_uri(pPath);
+ g_object_unref(pPath);
+#else
+ gchar* pCurrentFolder =
+ gtk_file_chooser_get_current_folder_uri(GTK_FILE_CHOOSER(m_pDialog));
+#endif
+ OUString aCurrentFolderName = uritounicode(pCurrentFolder);
+ g_free( pCurrentFolder );
+
+ return aCurrentFolderName;
+}
+
+void SalGtkPicker::implsetTitle( std::u16string_view aTitle )
+{
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ OString aWindowTitle = OUStringToOString( aTitle, RTL_TEXTENCODING_UTF8 );
+
+ gtk_window_set_title( GTK_WINDOW( m_pDialog ), aWindowTitle.getStr() );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/SalGtkPicker.hxx b/vcl/unx/gtk3/fpicker/SalGtkPicker.hxx
new file mode 100644
index 000000000..7da0ba04e
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkPicker.hxx
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <osl/mutex.hxx>
+#include <tools/link.hxx>
+#include <cppuhelper/compbase.hxx>
+
+#include <com/sun/star/awt/XTopWindowListener.hpp>
+#include <com/sun/star/awt/XExtendedToolkit.hpp>
+#include <com/sun/star/frame/XDesktop.hpp>
+#include <com/sun/star/frame/XTerminateListener.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <strings.hrc>
+#include <svdata.hxx>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#define FOLDERPICKER_TITLE 500
+#define FOLDER_PICKER_DEF_DESCRIPTION 501
+#define FILE_PICKER_TITLE_OPEN 502
+#define FILE_PICKER_TITLE_SAVE 503
+#define FILE_PICKER_FILE_TYPE 504
+#define FILE_PICKER_OVERWRITE_PRIMARY 505
+#define FILE_PICKER_OVERWRITE_SECONDARY 506
+#define FILE_PICKER_ALLFORMATS 507
+
+class SalGtkPicker
+{
+ public:
+ SalGtkPicker( const css::uno::Reference<css::uno::XComponentContext>& xContext );
+ virtual ~SalGtkPicker();
+ protected:
+ osl::Mutex m_rbHelperMtx;
+ GtkWidget *m_pParentWidget;
+ GtkWidget *m_pDialog;
+ protected:
+ /// @throws css::uno::RuntimeException
+ void implsetTitle( std::u16string_view aTitle );
+
+ /// @throws css::lang::IllegalArgumentException
+ /// @throws css::uno::RuntimeException
+ void implsetDisplayDirectory( const OUString& rDirectory );
+
+ /// @throws css::uno::RuntimeException
+ OUString implgetDisplayDirectory( );
+ OUString uritounicode(const gchar *pIn) const;
+ OString unicodetouri(const OUString &rURL) const;
+
+ // to instantiate own services
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+ static GtkWidget* GetParentWidget(const css::uno::Sequence<css::uno::Any>& rArguments);
+
+ static OUString getResString( sal_Int32 aId );
+};
+
+//Run the Gtk Dialog. Watch for any "new windows" created while we're
+//executing and consider that a CANCEL event to avoid e.g. "file cannot be opened"
+//modal dialogs and this one getting locked if some other API call causes this
+//to happen while we're opened waiting for user input, e.g.
+//https://bugzilla.redhat.com/show_bug.cgi?id=441108
+class RunDialog :
+ public cppu::WeakComponentImplHelper<
+ css::awt::XTopWindowListener,
+ css::frame::XTerminateListener >
+{
+private:
+ osl::Mutex maLock;
+ GtkWidget *mpDialog;
+ bool mbTerminateDesktop;
+ css::uno::Reference<css::awt::XExtendedToolkit> mxToolkit;
+ css::uno::Reference<css::frame::XDesktop> mxDesktop;
+ DECL_STATIC_LINK(RunDialog, TerminateDesktop, void*, void);
+public:
+
+ // XTopWindowListener
+ using cppu::WeakComponentImplHelperBase::disposing;
+ virtual void SAL_CALL disposing( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowOpened( const css::lang::EventObject& e ) override;
+ virtual void SAL_CALL windowClosing( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowClosed( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowMinimized( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowNormalized( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowActivated( const css::lang::EventObject& ) override {}
+ virtual void SAL_CALL windowDeactivated( const css::lang::EventObject& ) override {}
+
+ // XTerminateListener
+ virtual void SAL_CALL queryTermination( const css::lang::EventObject& aEvent ) override;
+ virtual void SAL_CALL notifyTermination( const css::lang::EventObject& aEvent ) override;
+public:
+ RunDialog(GtkWidget *pDialog,
+ const css::uno::Reference<css::awt::XExtendedToolkit>& rToolkit,
+ const css::uno::Reference<css::frame::XDesktop>& rDesktop);
+ virtual ~RunDialog() override;
+ gint run();
+ void cancel();
+ static GtkWindow* GetTransientFor();
+};
+
+inline OString getCancelText()
+{
+ return VclResId(SV_BUTTONTEXT_CANCEL).replace('~', '_').toUtf8();
+}
+
+inline OString getOpenText()
+{
+ return VclResId(SV_BUTTONTEXT_OPEN).replace('~', '_').toUtf8();
+}
+
+inline OString getSaveText()
+{
+ return VclResId(SV_BUTTONTEXT_SAVE).replace('~', '_').toUtf8();
+}
+
+inline OString getOKText()
+{
+ return VclResId(SV_BUTTONTEXT_OK).replace('~', '_').toUtf8();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/eventnotification.hxx b/vcl/unx/gtk3/fpicker/eventnotification.hxx
new file mode 100644
index 000000000..214da6da9
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/eventnotification.hxx
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/uno/XInterface.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+
+// encapsulate a filepicker event
+// notification, because there are
+// two types of filepicker notifications
+// with and without parameter
+// this is an application of the
+// "command" pattern see GoF
+
+class CEventNotification
+{
+public:
+ virtual ~CEventNotification(){};
+
+ virtual void SAL_CALL notifyEventListener(css::uno::Reference<css::uno::XInterface> xListener)
+ = 0;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/fpicker/resourceprovider.cxx b/vcl/unx/gtk3/fpicker/resourceprovider.cxx
new file mode 100644
index 000000000..fa90b8126
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/resourceprovider.cxx
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp>
+#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
+
+#include <fpicker/strings.hrc>
+#include <fpicker/fpsofficeResMgr.hxx>
+#include "SalGtkPicker.hxx"
+
+using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds;
+using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds;
+
+// translate control ids to resource ids
+
+const struct
+{
+ sal_Int32 ctrlId;
+ TranslateId resId;
+} CtrlIdToResIdTable[] = {
+ { CHECKBOX_AUTOEXTENSION, STR_SVT_FILEPICKER_AUTO_EXTENSION },
+ { CHECKBOX_PASSWORD, STR_SVT_FILEPICKER_PASSWORD },
+ { CHECKBOX_GPGENCRYPTION, STR_SVT_FILEPICKER_GPGENCRYPT },
+ { CHECKBOX_FILTEROPTIONS, STR_SVT_FILEPICKER_FILTER_OPTIONS },
+ { CHECKBOX_READONLY, STR_SVT_FILEPICKER_READONLY },
+ { CHECKBOX_LINK, STR_SVT_FILEPICKER_INSERT_AS_LINK },
+ { CHECKBOX_PREVIEW, STR_SVT_FILEPICKER_SHOW_PREVIEW },
+ { PUSHBUTTON_PLAY, STR_SVT_FILEPICKER_PLAY },
+ { LISTBOX_VERSION_LABEL, STR_SVT_FILEPICKER_VERSION },
+ { LISTBOX_TEMPLATE_LABEL, STR_SVT_FILEPICKER_TEMPLATES },
+ { LISTBOX_IMAGE_TEMPLATE_LABEL, STR_SVT_FILEPICKER_IMAGE_TEMPLATE },
+ { LISTBOX_IMAGE_ANCHOR_LABEL, STR_SVT_FILEPICKER_IMAGE_ANCHOR },
+ { CHECKBOX_SELECTION, STR_SVT_FILEPICKER_SELECTION },
+ { FOLDERPICKER_TITLE, STR_SVT_FOLDERPICKER_DEFAULT_TITLE },
+ { FOLDER_PICKER_DEF_DESCRIPTION, STR_SVT_FOLDERPICKER_DEFAULT_DESCRIPTION },
+ { FILE_PICKER_OVERWRITE_PRIMARY, STR_SVT_ALREADYEXISTOVERWRITE },
+ { FILE_PICKER_OVERWRITE_SECONDARY, STR_SVT_ALREADYEXISTOVERWRITE_SECONDARY },
+ { FILE_PICKER_ALLFORMATS, STR_SVT_ALLFORMATS },
+ { FILE_PICKER_TITLE_OPEN, STR_FILEDLG_OPEN },
+ { FILE_PICKER_TITLE_SAVE, STR_FILEDLG_SAVE },
+ { FILE_PICKER_FILE_TYPE, STR_FILEDLG_TYPE }
+};
+
+static TranslateId CtrlIdToResId( sal_Int32 aControlId )
+{
+ for (auto & i : CtrlIdToResIdTable)
+ {
+ if ( i.ctrlId == aControlId )
+ return i.resId;
+ }
+ return {};
+}
+
+OUString SalGtkPicker::getResString( sal_Int32 aId )
+{
+ OUString aResString;
+ // translate the control id to a resource id
+ TranslateId pResId = CtrlIdToResId( aId );
+ if (pResId)
+ aResString = FpsResId(pResId);
+ return aResString.replace('~', '_');
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gloactiongroup.cxx b/vcl/unx/gtk3/gloactiongroup.cxx
new file mode 100644
index 000000000..1b64da0ff
--- /dev/null
+++ b/vcl/unx/gtk3/gloactiongroup.cxx
@@ -0,0 +1,405 @@
+/* -*- 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 <unx/gtk/gtksalmenu.hxx>
+
+#include <unx/gtk/gloactiongroup.h>
+
+#include <sal/log.hxx>
+
+/*
+ * GLOAction
+ */
+
+#define G_TYPE_LO_ACTION (g_lo_action_get_type ())
+#define G_LO_ACTION(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
+ G_TYPE_LO_ACTION, GLOAction))
+namespace {
+
+struct GLOAction
+{
+ GObject parent_instance;
+
+ gint item_id; // Menu item ID.
+ bool submenu; // TRUE if action is a submenu action.
+ bool enabled; // TRUE if action is enabled.
+ GVariantType* parameter_type; // A GVariantType with the action parameter type.
+ GVariantType* state_type; // A GVariantType with item state type
+ GVariant* state_hint; // A GVariant with state hints.
+ GVariant* state; // A GVariant with current item state
+};
+
+}
+
+typedef GObjectClass GLOActionClass;
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
+#if defined __clang__
+#if __has_warning("-Wdeprecated-volatile")
+#pragma clang diagnostic ignored "-Wdeprecated-volatile"
+#endif
+#endif
+#endif
+G_DEFINE_TYPE (GLOAction, g_lo_action, G_TYPE_OBJECT);
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+static GLOAction*
+g_lo_action_new()
+{
+ return G_LO_ACTION (g_object_new (G_TYPE_LO_ACTION, nullptr));
+}
+
+static void
+g_lo_action_init (GLOAction *action)
+{
+ action->item_id = -1;
+ action->submenu = false;
+ action->enabled = true;
+ action->parameter_type = nullptr;
+ action->state_type = nullptr;
+ action->state_hint = nullptr;
+ action->state = nullptr;
+}
+
+static void
+g_lo_action_finalize (GObject *object)
+{
+ GLOAction* action = G_LO_ACTION(object);
+
+ if (action->parameter_type)
+ g_variant_type_free (action->parameter_type);
+
+ if (action->state_type)
+ g_variant_type_free (action->state_type);
+
+ if (action->state_hint)
+ g_variant_unref (action->state_hint);
+
+ if (action->state)
+ g_variant_unref (action->state);
+
+ G_OBJECT_CLASS (g_lo_action_parent_class)->finalize (object);
+}
+
+static void
+g_lo_action_class_init (GLOActionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->finalize = g_lo_action_finalize;
+}
+
+/*
+ * GLOActionGroup
+ */
+
+struct GLOActionGroupPrivate
+{
+ GHashTable *table; /* string -> GLOAction */
+};
+
+static void g_lo_action_group_iface_init (GActionGroupInterface *);
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
+#if defined __clang__
+#if __has_warning("-Wdeprecated-volatile")
+#pragma clang diagnostic ignored "-Wdeprecated-volatile"
+#endif
+#endif
+#endif
+G_DEFINE_TYPE_WITH_CODE (GLOActionGroup,
+ g_lo_action_group, G_TYPE_OBJECT,
+ G_ADD_PRIVATE(GLOActionGroup)
+ G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP,
+ g_lo_action_group_iface_init));
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+static gchar **
+g_lo_action_group_list_actions (GActionGroup *group)
+{
+ GLOActionGroup *loGroup = G_LO_ACTION_GROUP (group);
+ GHashTableIter iter;
+ gint n, i = 0;
+ gchar **keys;
+ gpointer key;
+
+ n = g_hash_table_size (loGroup->priv->table);
+ keys = g_new (gchar *, n + 1);
+
+ g_hash_table_iter_init (&iter, loGroup->priv->table);
+ while (g_hash_table_iter_next (&iter, &key, nullptr))
+ keys[i++] = g_strdup (static_cast<gchar*>(key));
+ g_assert_cmpint (i, ==, n);
+ keys[n] = nullptr;
+
+ return keys;
+}
+
+static gboolean
+g_lo_action_group_query_action (GActionGroup *group,
+ const gchar *action_name,
+ gboolean *enabled,
+ const GVariantType **parameter_type,
+ const GVariantType **state_type,
+ GVariant **state_hint,
+ GVariant **state)
+{
+ //SAL_INFO("vcl.unity", "g_lo_action_group_query_action on " << group);
+ GLOActionGroup *lo_group = G_LO_ACTION_GROUP (group);
+ GLOAction* action = G_LO_ACTION (g_hash_table_lookup (lo_group->priv->table, action_name));
+
+ if (action == nullptr)
+ return FALSE;
+
+ if (enabled)
+ {
+ *enabled = action->enabled;
+ }
+
+ if (parameter_type)
+ *parameter_type = action->parameter_type;
+
+ if (state_type)
+ *state_type = action->state_type;
+
+ if (state_hint)
+ *state_hint = (action->state_hint) ? g_variant_ref (action->state_hint) : nullptr;
+
+ if (state)
+ *state = (action->state) ? g_variant_ref (action->state) : nullptr;
+
+ return true;
+}
+
+static void
+g_lo_action_group_perform_submenu_action (GLOActionGroup *group,
+ const gchar *action_name,
+ GVariant *state)
+{
+ bool bState = g_variant_get_boolean (state);
+ SAL_INFO("vcl.unity", "g_lo_action_group_perform_submenu_action on " << group << " to " << bState);
+
+ if (bState)
+ GtkSalMenu::Activate(action_name);
+ else
+ GtkSalMenu::Deactivate(action_name);
+}
+
+static void
+g_lo_action_group_change_state (GActionGroup *group,
+ const gchar *action_name,
+ GVariant *value)
+{
+ SAL_INFO("vcl.unity", "g_lo_action_group_change_state on " << group );
+ g_return_if_fail (value != nullptr);
+
+ g_variant_ref_sink (value);
+
+ if (action_name != nullptr)
+ {
+ GLOActionGroup* lo_group = G_LO_ACTION_GROUP (group);
+ GLOAction* action = G_LO_ACTION (g_hash_table_lookup (lo_group->priv->table, action_name));
+
+ if (action != nullptr)
+ {
+ if (action->submenu)
+ g_lo_action_group_perform_submenu_action (lo_group, action_name, value);
+ else
+ {
+ bool is_new = false;
+
+ /* If action already exists but has no state, it should be removed and added again. */
+ if (action->state_type == nullptr)
+ {
+ g_action_group_action_removed (G_ACTION_GROUP (group), action_name);
+ action->state_type = g_variant_type_copy (g_variant_get_type(value));
+ is_new = true;
+ }
+
+ if (g_variant_is_of_type (value, action->state_type))
+ {
+ if (action->state)
+ g_variant_unref(action->state);
+
+ action->state = g_variant_ref (value);
+
+ if (is_new)
+ g_action_group_action_added (G_ACTION_GROUP (group), action_name);
+ else
+ g_action_group_action_state_changed (group, action_name, value);
+ }
+ }
+ }
+ }
+
+ g_variant_unref (value);
+}
+
+static void
+g_lo_action_group_activate (GActionGroup *group,
+ const gchar *action_name,
+ GVariant *parameter)
+{
+ if (parameter != nullptr)
+ g_action_group_change_action_state(group, action_name, parameter);
+ GtkSalMenu::DispatchCommand(action_name);
+}
+
+void
+g_lo_action_group_insert (GLOActionGroup *group,
+ const gchar *action_name,
+ gint item_id,
+ gboolean submenu)
+{
+ g_lo_action_group_insert_stateful (group, action_name, item_id, submenu, nullptr, nullptr, nullptr, nullptr);
+}
+
+void
+g_lo_action_group_insert_stateful (GLOActionGroup *group,
+ const gchar *action_name,
+ gint item_id,
+ gboolean submenu,
+ const GVariantType *parameter_type,
+ const GVariantType *state_type,
+ GVariant *state_hint,
+ GVariant *state)
+{
+ g_return_if_fail (G_IS_LO_ACTION_GROUP (group));
+
+ GLOAction* old_action = G_LO_ACTION (g_hash_table_lookup (group->priv->table, action_name));
+
+ if (old_action != nullptr && old_action->item_id == item_id)
+ return;
+
+ if (old_action != nullptr)
+ g_lo_action_group_remove (group, action_name);
+
+ GLOAction* action = g_lo_action_new();
+
+ g_hash_table_insert (group->priv->table, g_strdup (action_name), action);
+
+ action->item_id = item_id;
+ action->submenu = submenu;
+
+ if (parameter_type)
+ action->parameter_type = const_cast<GVariantType*>(parameter_type);
+
+ if (state_type)
+ action->state_type = const_cast<GVariantType*>(state_type);
+
+ if (state_hint)
+ action->state_hint = g_variant_ref_sink (state_hint);
+
+ if (state)
+ action->state = g_variant_ref_sink (state);
+
+ g_action_group_action_added (G_ACTION_GROUP (group), action_name);
+}
+
+static void
+g_lo_action_group_finalize (GObject *object)
+{
+ GLOActionGroup *lo_group = G_LO_ACTION_GROUP (object);
+
+ g_hash_table_unref (lo_group->priv->table);
+
+ G_OBJECT_CLASS (g_lo_action_group_parent_class)->finalize (object);
+}
+
+static void
+g_lo_action_group_init (GLOActionGroup *group)
+{
+ SAL_INFO("vcl.unity", "g_lo_action_group_init on " << group);
+ group->priv = static_cast<GLOActionGroupPrivate *>(g_lo_action_group_get_instance_private (group));
+ group->priv->table = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+}
+
+static void
+g_lo_action_group_class_init (GLOActionGroupClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = g_lo_action_group_finalize;
+}
+
+static void
+g_lo_action_group_iface_init (GActionGroupInterface *iface)
+{
+ iface->list_actions = g_lo_action_group_list_actions;
+ iface->query_action = g_lo_action_group_query_action;
+ iface->change_action_state = g_lo_action_group_change_state;
+ iface->activate_action = g_lo_action_group_activate;
+}
+
+GLOActionGroup *
+g_lo_action_group_new()
+{
+ GLOActionGroup* group = G_LO_ACTION_GROUP (g_object_new (G_TYPE_LO_ACTION_GROUP, nullptr));
+ return group;
+}
+
+void
+g_lo_action_group_set_action_enabled (GLOActionGroup *group,
+ const gchar *action_name,
+ gboolean enabled)
+{
+ SAL_INFO("vcl.unity", "g_lo_action_group_set_action_enabled on " << group);
+ g_return_if_fail (G_IS_LO_ACTION_GROUP (group));
+ g_return_if_fail (action_name != nullptr);
+
+ GLOAction* action = G_LO_ACTION (g_hash_table_lookup (group->priv->table, action_name));
+
+ if (action == nullptr)
+ return;
+
+ action->enabled = enabled;
+
+ g_action_group_action_enabled_changed (G_ACTION_GROUP (group), action_name, enabled);
+}
+
+void
+g_lo_action_group_remove (GLOActionGroup *group,
+ const gchar *action_name)
+{
+ SAL_INFO("vcl.unity", "g_lo_action_group_remove on " << group);
+ g_return_if_fail (G_IS_LO_ACTION_GROUP (group));
+
+ if (action_name != nullptr)
+ {
+ g_action_group_action_removed (G_ACTION_GROUP (group), action_name);
+ g_hash_table_remove (group->priv->table, action_name);
+ }
+}
+
+void
+g_lo_action_group_clear (GLOActionGroup *group)
+{
+ SAL_INFO("vcl.unity", "g_lo_action_group_clear on " << group);
+ g_return_if_fail (G_IS_LO_ACTION_GROUP (group));
+
+ GList* keys = g_hash_table_get_keys (group->priv->table);
+
+ for (GList* element = g_list_first (keys); element != nullptr; element = g_list_next (element))
+ {
+ g_lo_action_group_remove (group, static_cast<gchar*>(element->data));
+ }
+
+ g_list_free (keys);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/glomenu.cxx b/vcl/unx/gtk3/glomenu.cxx
new file mode 100644
index 000000000..a391649bb
--- /dev/null
+++ b/vcl/unx/gtk3/glomenu.cxx
@@ -0,0 +1,694 @@
+/* -*- 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 <o3tl/safeint.hxx>
+
+#include <unx/gtk/glomenu.h>
+
+struct GLOMenu
+{
+ GMenuModel const parent_instance;
+
+ GArray *items;
+};
+
+typedef GMenuModelClass GLOMenuClass;
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
+#if defined __clang__
+#if __has_warning("-Wdeprecated-volatile")
+#pragma clang diagnostic ignored "-Wdeprecated-volatile"
+#endif
+#endif
+#endif
+G_DEFINE_TYPE (GLOMenu, g_lo_menu, G_TYPE_MENU_MODEL);
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+namespace {
+
+struct item
+{
+ GHashTable* attributes; // Item attributes.
+ GHashTable* links; // Item links.
+};
+
+}
+
+static void
+g_lo_menu_struct_item_init (struct item *menu_item)
+{
+ menu_item->attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, reinterpret_cast<GDestroyNotify>(g_variant_unref));
+ menu_item->links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+}
+
+/* We treat attribute names the same as GSettings keys:
+ * - only lowercase ascii, digits and '-'
+ * - must start with lowercase
+ * - must not end with '-'
+ * - no consecutive '-'
+ * - not longer than 1024 chars
+ */
+static bool
+valid_attribute_name (const gchar *name)
+{
+ gint i;
+
+ if (!g_ascii_islower (name[0]))
+ return false;
+
+ for (i = 1; name[i]; i++)
+ {
+ if (name[i] != '-' &&
+ !g_ascii_islower (name[i]) &&
+ !g_ascii_isdigit (name[i]))
+ return false;
+
+ if (name[i] == '-' && name[i + 1] == '-')
+ return false;
+ }
+
+ if (name[i - 1] == '-')
+ return false;
+
+ if (i > 1024)
+ return false;
+
+ return true;
+}
+
+/*
+ * GLOMenu
+ */
+
+static gboolean
+g_lo_menu_is_mutable (GMenuModel*)
+{
+ // Menu is always mutable.
+ return true;
+}
+
+static gint
+g_lo_menu_get_n_items (GMenuModel *model)
+{
+ g_return_val_if_fail (model != nullptr, 0);
+ GLOMenu *menu = G_LO_MENU (model);
+ g_return_val_if_fail (menu->items != nullptr, 0);
+
+ return menu->items->len;
+}
+
+gint
+g_lo_menu_get_n_items_from_section (GLOMenu *menu,
+ gint section)
+{
+ g_return_val_if_fail (0 <= section && o3tl::make_unsigned(section) < menu->items->len, 0);
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_val_if_fail (model != nullptr, 0);
+
+ gint length = model->items->len;
+
+ g_object_unref (model);
+
+ return length;
+}
+
+static void
+g_lo_menu_get_item_attributes (GMenuModel *model,
+ gint position,
+ GHashTable **table)
+{
+ GLOMenu *menu = G_LO_MENU (model);
+ *table = g_hash_table_ref (g_array_index (menu->items, struct item, position).attributes);
+}
+
+static void
+g_lo_menu_get_item_links (GMenuModel *model,
+ gint position,
+ GHashTable **table)
+{
+ GLOMenu *menu = G_LO_MENU (model);
+ *table = g_hash_table_ref (g_array_index (menu->items, struct item, position).links);
+}
+
+void
+g_lo_menu_insert (GLOMenu *menu,
+ gint position,
+ const gchar *label)
+{
+ g_lo_menu_insert_section (menu, position, label, nullptr);
+}
+
+void
+g_lo_menu_insert_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *label)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+ g_return_if_fail (0 <= section && o3tl::make_unsigned(section) < menu->items->len);
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ g_lo_menu_insert (model, position, label);
+
+ g_object_unref (model);
+}
+
+GLOMenu *
+g_lo_menu_new()
+{
+ return G_LO_MENU( g_object_new (G_TYPE_LO_MENU, nullptr) );
+}
+
+static void
+g_lo_menu_set_attribute_value (GLOMenu *menu,
+ gint position,
+ const gchar *attribute,
+ GVariant *value)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+ g_return_if_fail (attribute != nullptr);
+ g_return_if_fail (valid_attribute_name (attribute));
+
+ if (position >= static_cast<gint>(menu->items->len))
+ return;
+
+ struct item menu_item = g_array_index (menu->items, struct item, position);
+
+ if (value != nullptr)
+ g_hash_table_insert (menu_item.attributes, g_strdup (attribute), g_variant_ref_sink (value));
+ else
+ g_hash_table_remove (menu_item.attributes, attribute);
+}
+
+static GVariant*
+g_lo_menu_get_attribute_value_from_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *attribute,
+ const GVariantType *type)
+{
+ GMenuModel *model = G_MENU_MODEL (g_lo_menu_get_section (menu, section));
+
+ g_return_val_if_fail (model != nullptr, nullptr);
+
+ GVariant *value = g_menu_model_get_item_attribute_value (model,
+ position,
+ attribute,
+ type);
+
+ g_object_unref (model);
+
+ return value;
+}
+
+void
+g_lo_menu_set_label (GLOMenu *menu,
+ gint position,
+ const gchar *label)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GVariant *value;
+
+ if (label != nullptr)
+ value = g_variant_new_string (label);
+ else
+ value = nullptr;
+
+ g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_LABEL, value);
+}
+
+void
+g_lo_menu_set_icon (GLOMenu *menu,
+ gint position,
+ const GIcon *icon)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GVariant *value;
+
+ if (icon != nullptr)
+ {
+#if GLIB_CHECK_VERSION(2,38,0)
+ value = g_icon_serialize (const_cast<GIcon*>(icon));
+#else
+ value = nullptr;
+#endif
+ }
+ else
+ value = nullptr;
+
+#ifndef G_MENU_ATTRIBUTE_ICON
+# define G_MENU_ATTRIBUTE_ICON "icon"
+#endif
+
+ g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_ICON, value);
+ if (value)
+ g_variant_unref (value);
+}
+
+void
+g_lo_menu_set_label_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *label)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ g_lo_menu_set_label (model, position, label);
+
+ // Notify the update.
+ g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);
+
+ g_object_unref (model);
+}
+
+void
+g_lo_menu_set_icon_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const GIcon *icon)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ g_lo_menu_set_icon (model, position, icon);
+
+ // Notify the update.
+ g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);
+
+ g_object_unref (model);
+}
+
+gchar *
+g_lo_menu_get_label_from_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position)
+{
+ g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);
+
+ GVariant *label_value = g_lo_menu_get_attribute_value_from_item_in_section (menu,
+ section,
+ position,
+ G_MENU_ATTRIBUTE_LABEL,
+ G_VARIANT_TYPE_STRING);
+
+ gchar *label = nullptr;
+
+ if (label_value)
+ {
+ label = g_variant_dup_string (label_value, nullptr);
+ g_variant_unref (label_value);
+ }
+
+ return label;
+}
+
+void
+g_lo_menu_set_action_and_target_value (GLOMenu *menu,
+ gint position,
+ const gchar *action,
+ GVariant *target_value)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GVariant *action_value;
+
+ if (action != nullptr)
+ {
+ action_value = g_variant_new_string (action);
+ }
+ else
+ {
+ action_value = nullptr;
+ target_value = nullptr;
+ }
+
+ g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_ACTION, action_value);
+ g_lo_menu_set_attribute_value (menu, position, G_MENU_ATTRIBUTE_TARGET, target_value);
+ g_lo_menu_set_attribute_value (menu, position, G_LO_MENU_ATTRIBUTE_SUBMENU_ACTION, nullptr);
+
+ g_menu_model_items_changed (G_MENU_MODEL (menu), position, 1, 1);
+}
+
+void
+g_lo_menu_set_action_and_target_value_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *command,
+ GVariant *target_value)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ g_lo_menu_set_action_and_target_value (model, position, command, target_value);
+
+ g_object_unref (model);
+}
+
+void
+g_lo_menu_set_accelerator_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *accelerator)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ GVariant *value;
+
+ if (accelerator != nullptr)
+ value = g_variant_new_string (accelerator);
+ else
+ value = nullptr;
+
+ g_lo_menu_set_attribute_value (model, position, G_LO_MENU_ATTRIBUTE_ACCELERATOR, value);
+
+ // Notify the update.
+ g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);
+
+ g_object_unref (model);
+}
+
+gchar *
+g_lo_menu_get_accelerator_from_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position)
+{
+ g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);
+
+ GVariant *accel_value = g_lo_menu_get_attribute_value_from_item_in_section (menu,
+ section,
+ position,
+ G_LO_MENU_ATTRIBUTE_ACCELERATOR,
+ G_VARIANT_TYPE_STRING);
+
+ gchar *accel = nullptr;
+
+ if (accel_value != nullptr)
+ {
+ accel = g_variant_dup_string (accel_value, nullptr);
+ g_variant_unref (accel_value);
+ }
+
+ return accel;
+}
+
+void
+g_lo_menu_set_command_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *command)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ GVariant *value;
+
+ if (command != nullptr)
+ value = g_variant_new_string (command);
+ else
+ value = nullptr;
+
+ g_lo_menu_set_attribute_value (model, position, G_LO_MENU_ATTRIBUTE_COMMAND, value);
+
+ // Notify the update.
+ g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);
+
+ g_object_unref (model);
+}
+
+gchar *
+g_lo_menu_get_command_from_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position)
+{
+ g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);
+
+ GVariant *command_value = g_lo_menu_get_attribute_value_from_item_in_section (menu,
+ section,
+ position,
+ G_LO_MENU_ATTRIBUTE_COMMAND,
+ G_VARIANT_TYPE_STRING);
+
+ gchar *command = nullptr;
+
+ if (command_value != nullptr)
+ {
+ command = g_variant_dup_string (command_value, nullptr);
+ g_variant_unref (command_value);
+ }
+
+ return command;
+}
+
+static void
+g_lo_menu_set_link (GLOMenu *menu,
+ gint position,
+ const gchar *link,
+ GMenuModel *model)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+ g_return_if_fail (link != nullptr);
+ g_return_if_fail (valid_attribute_name (link));
+
+ if (position < 0 || o3tl::make_unsigned(position) >= menu->items->len)
+ position = menu->items->len - 1;
+
+ struct item menu_item = g_array_index (menu->items, struct item, position);
+
+ if (model != nullptr)
+ g_hash_table_insert (menu_item.links, g_strdup (link), g_object_ref (model));
+ else
+ g_hash_table_remove (menu_item.links, link);
+}
+
+void
+g_lo_menu_insert_section (GLOMenu *menu,
+ gint position,
+ const gchar *label,
+ GMenuModel *section)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ if (position < 0 || o3tl::make_unsigned(position) > menu->items->len)
+ position = menu->items->len;
+
+ struct item menu_item;
+
+ g_lo_menu_struct_item_init(&menu_item);
+
+ g_array_insert_val (menu->items, position, menu_item);
+
+ g_lo_menu_set_label (menu, position, label);
+ g_lo_menu_set_link (menu, position, G_MENU_LINK_SECTION, section);
+
+ g_menu_model_items_changed (G_MENU_MODEL (menu), position, 0, 1);
+}
+
+void
+g_lo_menu_new_section (GLOMenu *menu,
+ gint position,
+ const gchar *label)
+{
+ GMenuModel *section = G_MENU_MODEL (g_lo_menu_new());
+
+ g_lo_menu_insert_section (menu, position, label, section);
+
+ g_object_unref (section);
+}
+
+GLOMenu *
+g_lo_menu_get_section (GLOMenu *menu,
+ gint section)
+{
+ g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);
+
+ return G_LO_MENU (G_MENU_MODEL_CLASS (g_lo_menu_parent_class)
+ ->get_item_link (G_MENU_MODEL (menu), section, G_MENU_LINK_SECTION));
+}
+
+void
+g_lo_menu_new_submenu_in_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+ g_return_if_fail (0 <= section && o3tl::make_unsigned(section) < menu->items->len);
+
+ GLOMenu* model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ if (0 <= position && o3tl::make_unsigned(position) < model->items->len) {
+ GMenuModel* submenu = G_MENU_MODEL (g_lo_menu_new());
+
+ g_lo_menu_set_link (model, position, G_MENU_LINK_SUBMENU, submenu);
+
+ g_object_unref (submenu);
+
+ g_menu_model_items_changed (G_MENU_MODEL (model), position, 1, 1);
+
+ g_object_unref (model);
+ }
+}
+
+GLOMenu *
+g_lo_menu_get_submenu_from_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position)
+{
+ g_return_val_if_fail (G_IS_LO_MENU (menu), nullptr);
+ g_return_val_if_fail (0 <= section && o3tl::make_unsigned(section) < menu->items->len, nullptr);
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_val_if_fail (model != nullptr, nullptr);
+
+ GLOMenu *submenu = nullptr;
+
+ if (0 <= position && o3tl::make_unsigned(position) < model->items->len)
+ submenu = G_LO_MENU (G_MENU_MODEL_CLASS (g_lo_menu_parent_class)
+ ->get_item_link (G_MENU_MODEL (model), position, G_MENU_LINK_SUBMENU));
+ //submenu = g_menu_model_get_item_link (G_MENU_MODEL (model), position, G_MENU_LINK_SUBMENU);
+
+ g_object_unref (model);
+
+ return submenu;
+}
+
+void
+g_lo_menu_set_submenu_action_to_item_in_section (GLOMenu *menu,
+ gint section,
+ gint position,
+ const gchar *action)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+
+ GMenuModel *model = G_MENU_MODEL (g_lo_menu_get_section (menu, section));
+
+ g_return_if_fail (model != nullptr);
+
+ GVariant *value;
+
+ if (action != nullptr)
+ value = g_variant_new_string (action);
+ else
+ value = nullptr;
+
+ g_lo_menu_set_attribute_value (G_LO_MENU (model), position, G_LO_MENU_ATTRIBUTE_SUBMENU_ACTION, value);
+
+ // Notify the update.
+ g_menu_model_items_changed (model, position, 1, 1);
+
+ g_object_unref (model);
+}
+
+static void
+g_lo_menu_clear_item (struct item *menu_item)
+{
+ if (menu_item->attributes != nullptr)
+ g_hash_table_unref (menu_item->attributes);
+ if (menu_item->links != nullptr)
+ g_hash_table_unref (menu_item->links);
+}
+
+void
+g_lo_menu_remove (GLOMenu *menu,
+ gint position)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+ g_return_if_fail (0 <= position && o3tl::make_unsigned(position) < menu->items->len);
+
+ g_lo_menu_clear_item (&g_array_index (menu->items, struct item, position));
+ g_array_remove_index (menu->items, position);
+ g_menu_model_items_changed (G_MENU_MODEL (menu), position, 1, 0);
+}
+
+void
+g_lo_menu_remove_from_section (GLOMenu *menu,
+ gint section,
+ gint position)
+{
+ g_return_if_fail (G_IS_LO_MENU (menu));
+ g_return_if_fail (0 <= section && o3tl::make_unsigned(section) < menu->items->len);
+
+ GLOMenu *model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ g_lo_menu_remove (model, position);
+
+ g_object_unref (model);
+}
+
+static void
+g_lo_menu_finalize (GObject *object)
+{
+ GLOMenu *menu = G_LO_MENU (object);
+ struct item *items;
+ gint n_items;
+ gint i;
+
+ n_items = menu->items->len;
+ items = reinterpret_cast<struct item *>(g_array_free (menu->items, FALSE));
+ for (i = 0; i < n_items; i++)
+ g_lo_menu_clear_item (&items[i]);
+ g_free (items);
+
+ G_OBJECT_CLASS (g_lo_menu_parent_class)
+ ->finalize (object);
+}
+
+static void
+g_lo_menu_init (GLOMenu *menu)
+{
+ menu->items = g_array_new (FALSE, FALSE, sizeof (struct item));
+}
+
+static void
+g_lo_menu_class_init (GLOMenuClass *klass)
+{
+ GMenuModelClass *model_class = G_MENU_MODEL_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = g_lo_menu_finalize;
+
+ model_class->is_mutable = g_lo_menu_is_mutable;
+ model_class->get_n_items = g_lo_menu_get_n_items;
+ model_class->get_item_attributes = g_lo_menu_get_item_attributes;
+ model_class->get_item_links = g_lo_menu_get_item_links;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtkcairo.cxx b/vcl/unx/gtk3/gtkcairo.cxx
new file mode 100644
index 000000000..202e3244d
--- /dev/null
+++ b/vcl/unx/gtk3/gtkcairo.cxx
@@ -0,0 +1,128 @@
+/* -*- 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 "gtkcairo.hxx"
+
+#include <vcl/sysdata.hxx>
+#include <vcl/virdev.hxx>
+
+#include <unx/gtk/gtkgdi.hxx>
+
+namespace
+{
+ Size get_surface_size(cairo_surface_t *surface)
+ {
+ cairo_t *cr = cairo_create(surface);
+ double x1, x2, y1, y2;
+ cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
+ cairo_destroy(cr);
+ return Size(x2 - x1, y2 - y1);
+ }
+}
+
+namespace cairo
+{
+ /**
+ * Surface::Surface: Create generic Canvas surface using given Cairo Surface
+ *
+ * @param pSurface Cairo Surface
+ *
+ * This constructor only stores data, it does no processing.
+ * It is used with e.g. cairo_image_surface_create_for_data()
+ *
+ * Set the mpSurface as pSurface
+ **/
+ Gtk3Surface::Gtk3Surface(const CairoSurfaceSharedPtr& pSurface)
+ : mpGraphics(nullptr)
+ , cr(nullptr)
+ , mpSurface(pSurface)
+ {}
+
+ /**
+ * Surface::Surface: Create Canvas surface from Window reference.
+ * @param x horizontal location of the new surface
+ * @param y vertical location of the new surface
+ * @param width width of the new surface
+ * @param height height of the new surface
+ *
+ * Set the mpSurface to the new surface or NULL
+ **/
+ Gtk3Surface::Gtk3Surface(const GtkSalGraphics* pGraphics, int x, int y, int width, int height)
+ : mpGraphics(pGraphics)
+ , cr(pGraphics->getCairoContext())
+ {
+ cairo_surface_t* surface = cairo_get_target(cr);
+ mpSurface.reset(
+ cairo_surface_create_for_rectangle(surface, x, y, width, height),
+ &cairo_surface_destroy);
+ }
+
+ Gtk3Surface::~Gtk3Surface()
+ {
+ if (cr)
+ cairo_destroy(cr);
+ }
+
+ /**
+ * Surface::getCairo: Create Cairo (drawing object) for the Canvas surface
+ *
+ * @return new Cairo or NULL
+ **/
+ CairoSharedPtr Gtk3Surface::getCairo() const
+ {
+ return CairoSharedPtr( cairo_create(mpSurface.get()),
+ &cairo_destroy );
+ }
+
+ /**
+ * Surface::getSimilar: Create new similar Canvas surface
+ * @param cairo_content_type format of the new surface (cairo_content_t from cairo/src/cairo.h)
+ * @param width width of the new surface
+ * @param height height of the new surface
+ *
+ * Creates a new Canvas surface. This normally creates platform native surface, even though
+ * generic function is used.
+ *
+ * Cairo surface from cairo_content_type (cairo_content_t)
+ *
+ * @return new surface or NULL
+ **/
+ SurfaceSharedPtr Gtk3Surface::getSimilar(int cairo_content_type, int width, int height ) const
+ {
+ return std::make_shared<Gtk3Surface>(
+ CairoSurfaceSharedPtr(
+ cairo_surface_create_similar( mpSurface.get(),
+ static_cast<cairo_content_t>(cairo_content_type), width, height ),
+ &cairo_surface_destroy ));
+ }
+
+ void Gtk3Surface::flush() const
+ {
+ cairo_surface_flush(mpSurface.get());
+ //Wonder if there is any benefit in using cairo_fill/stroke extents on
+ //every canvas call and only redrawing the union of those in a
+ //poor-mans-damage tracking
+ if (mpGraphics)
+ mpGraphics->WidgetQueueDraw();
+ }
+
+ VclPtr<VirtualDevice> Gtk3Surface::createVirtualDevice() const
+ {
+ SystemGraphicsData aSystemGraphicsData;
+
+ aSystemGraphicsData.nSize = sizeof(SystemGraphicsData);
+ aSystemGraphicsData.pSurface = mpSurface.get();
+
+ return VclPtr<VirtualDevice>::Create(aSystemGraphicsData,
+ get_surface_size(mpSurface.get()),
+ DeviceFormat::DEFAULT);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtkcairo.hxx b/vcl/unx/gtk3/gtkcairo.hxx
new file mode 100644
index 000000000..1b4ed6d32
--- /dev/null
+++ b/vcl/unx/gtk3/gtkcairo.hxx
@@ -0,0 +1,46 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <vcl/cairo.hxx>
+
+class GtkSalGraphics;
+class OutputDevice;
+
+namespace cairo {
+
+ class Gtk3Surface : public Surface
+ {
+ const GtkSalGraphics* mpGraphics;
+ cairo_t* cr;
+ CairoSurfaceSharedPtr mpSurface;
+ public:
+ /// takes over ownership of passed cairo_surface
+ explicit Gtk3Surface(const CairoSurfaceSharedPtr& pSurface);
+ /// create surface on subarea of given drawable
+ explicit Gtk3Surface(const GtkSalGraphics* pGraphics, int x, int y, int width, int height);
+
+ // Surface interface
+ virtual CairoSharedPtr getCairo() const override;
+ virtual CairoSurfaceSharedPtr getCairoSurface() const override { return mpSurface; }
+ virtual SurfaceSharedPtr getSimilar(int nContentType, int width, int height) const override;
+
+ virtual VclPtr<VirtualDevice> createVirtualDevice() const override;
+
+ virtual void flush() const override;
+
+ virtual ~Gtk3Surface() override;
+ };
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtkdata.cxx b/vcl/unx/gtk3/gtkdata.cxx
new file mode 100644
index 000000000..cc8535ff2
--- /dev/null
+++ b/vcl/unx/gtk3/gtkdata.cxx
@@ -0,0 +1,984 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unistd.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#if defined(FREEBSD) || defined(NETBSD)
+#include <sys/types.h>
+#include <sys/time.h>
+#endif
+#include <unx/gtk/gtkbackend.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include <unx/gtk/gtkframe.hxx>
+#include <bitmaps.hlst>
+#include <cursor_hotspots.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/thread.h>
+#include <osl/process.h>
+
+#include <vcl/svapp.hxx>
+#include <sal/log.hxx>
+
+#include <chrono>
+
+using namespace vcl_sal;
+
+/***************************************************************
+ * class GtkSalDisplay *
+ ***************************************************************/
+
+GtkSalDisplay::GtkSalDisplay( GdkDisplay* pDisplay ) :
+ m_pSys( GtkSalSystem::GetSingleton() ),
+ m_pGdkDisplay( pDisplay ),
+ m_bStartupCompleted( false )
+{
+ for(GdkCursor* & rpCsr : m_aCursors)
+ rpCsr = nullptr;
+
+ if ( getenv( "SAL_IGNOREXERRORS" ) )
+ GetGenericUnixSalData()->ErrorTrapPush(); // and leak the trap
+
+ gtk_widget_set_default_direction(AllSettings::GetLayoutRTL() ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
+}
+
+GtkSalDisplay::~GtkSalDisplay()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( !m_bStartupCompleted )
+ gdk_notify_startup_complete();
+
+ for(GdkCursor* & rpCsr : m_aCursors)
+ if( rpCsr )
+ gdk_cursor_unref( rpCsr );
+#endif
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+
+static void signalMonitorsChanged(GListModel*, gpointer data)
+{
+ GtkSalDisplay* pDisp = static_cast<GtkSalDisplay*>(data);
+ pDisp->emitDisplayChanged();
+}
+
+#else
+
+static void signalScreenSizeChanged( GdkScreen* pScreen, gpointer data )
+{
+ GtkSalDisplay* pDisp = static_cast<GtkSalDisplay*>(data);
+ pDisp->screenSizeChanged( pScreen );
+}
+
+static void signalMonitorsChanged( GdkScreen* pScreen, gpointer data )
+{
+ GtkSalDisplay* pDisp = static_cast<GtkSalDisplay*>(data);
+ pDisp->monitorsChanged( pScreen );
+}
+
+void GtkSalDisplay::screenSizeChanged( GdkScreen const * pScreen )
+{
+ m_pSys->countScreenMonitors();
+ if (pScreen)
+ emitDisplayChanged();
+}
+
+void GtkSalDisplay::monitorsChanged( GdkScreen const * pScreen )
+{
+ m_pSys->countScreenMonitors();
+ if (pScreen)
+ emitDisplayChanged();
+}
+#endif
+
+GdkCursor* GtkSalDisplay::getFromSvg(OUString const & name, int nXHot, int nYHot)
+{
+ GdkPixbuf* pPixBuf = load_icon_by_name(name);
+ assert(pPixBuf && "missing image?");
+ if (!pPixBuf)
+ return nullptr;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ guint nDefaultCursorSize = gdk_display_get_default_cursor_size( m_pGdkDisplay );
+ int nPixWidth = gdk_pixbuf_get_width(pPixBuf);
+ int nPixHeight = gdk_pixbuf_get_height(pPixBuf);
+ double fScalefactor = static_cast<double>(nDefaultCursorSize) / std::max(nPixWidth, nPixHeight);
+ GdkPixbuf* pScaledPixBuf = gdk_pixbuf_scale_simple(pPixBuf,
+ nPixWidth * fScalefactor,
+ nPixHeight * fScalefactor,
+ GDK_INTERP_HYPER);
+ g_object_unref(pPixBuf);
+ return gdk_cursor_new_from_pixbuf(m_pGdkDisplay, pScaledPixBuf,
+ nXHot * fScalefactor, nYHot * fScalefactor);
+#else
+ GdkTexture* pTexture = gdk_texture_new_for_pixbuf(pPixBuf);
+ g_object_unref(pPixBuf);
+ return gdk_cursor_new_from_texture(pTexture, nXHot, nYHot, nullptr);
+#endif
+}
+
+#define MAKE_CURSOR( vcl_name, name, name2 ) \
+ case vcl_name: \
+ pCursor = getFromSvg(name2, name##curs_x_hot, name##curs_y_hot); \
+ break
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+#define MAP_BUILTIN( vcl_name, gdk3_name, css_name ) \
+ case vcl_name: \
+ pCursor = gdk_cursor_new_for_display( m_pGdkDisplay, gdk3_name ); \
+ break
+#else
+#define MAP_BUILTIN( vcl_name, gdk3_name, css_name ) \
+ case vcl_name: \
+ pCursor = gdk_cursor_new_from_name(css_name, nullptr); \
+ break
+#endif
+
+GdkCursor *GtkSalDisplay::getCursor( PointerStyle ePointerStyle )
+{
+ if ( !m_aCursors[ ePointerStyle ] )
+ {
+ GdkCursor *pCursor = nullptr;
+
+ switch( ePointerStyle )
+ {
+ MAP_BUILTIN( PointerStyle::Arrow, GDK_LEFT_PTR, "default" );
+ MAP_BUILTIN( PointerStyle::Text, GDK_XTERM, "text" );
+ MAP_BUILTIN( PointerStyle::Help, GDK_QUESTION_ARROW, "help" );
+ MAP_BUILTIN( PointerStyle::Cross, GDK_CROSSHAIR, "crosshair" );
+ MAP_BUILTIN( PointerStyle::Wait, GDK_WATCH, "wait" );
+
+ MAP_BUILTIN( PointerStyle::NSize, GDK_SB_V_DOUBLE_ARROW, "n-resize" );
+ MAP_BUILTIN( PointerStyle::SSize, GDK_SB_V_DOUBLE_ARROW, "s-resize" );
+ MAP_BUILTIN( PointerStyle::WSize, GDK_SB_H_DOUBLE_ARROW, "w-resize" );
+ MAP_BUILTIN( PointerStyle::ESize, GDK_SB_H_DOUBLE_ARROW, "e-resize" );
+
+ MAP_BUILTIN( PointerStyle::NWSize, GDK_TOP_LEFT_CORNER, "nw-resize" );
+ MAP_BUILTIN( PointerStyle::NESize, GDK_TOP_RIGHT_CORNER, "ne-resize" );
+ MAP_BUILTIN( PointerStyle::SWSize, GDK_BOTTOM_LEFT_CORNER, "sw-resize" );
+ MAP_BUILTIN( PointerStyle::SESize, GDK_BOTTOM_RIGHT_CORNER, "se-resize" );
+
+ MAP_BUILTIN( PointerStyle::WindowNSize, GDK_TOP_SIDE, "n-resize" );
+ MAP_BUILTIN( PointerStyle::WindowSSize, GDK_BOTTOM_SIDE, "s-resize" );
+ MAP_BUILTIN( PointerStyle::WindowWSize, GDK_LEFT_SIDE, "w-resize" );
+ MAP_BUILTIN( PointerStyle::WindowESize, GDK_RIGHT_SIDE, "e-resize" );
+
+ MAP_BUILTIN( PointerStyle::WindowNWSize, GDK_TOP_LEFT_CORNER, "nw-resize" );
+ MAP_BUILTIN( PointerStyle::WindowNESize, GDK_TOP_RIGHT_CORNER, "ne-resize" );
+ MAP_BUILTIN( PointerStyle::WindowSWSize, GDK_BOTTOM_LEFT_CORNER, "sw-resize" );
+ MAP_BUILTIN( PointerStyle::WindowSESize, GDK_BOTTOM_RIGHT_CORNER, "se-resize" );
+
+ MAP_BUILTIN( PointerStyle::HSizeBar, GDK_SB_H_DOUBLE_ARROW, "col-resize" );
+ MAP_BUILTIN( PointerStyle::VSizeBar, GDK_SB_V_DOUBLE_ARROW, "row-resize" );
+
+ MAP_BUILTIN( PointerStyle::RefHand, GDK_HAND2, "grab" );
+ MAP_BUILTIN( PointerStyle::Hand, GDK_HAND2, "grab" );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ MAP_BUILTIN( PointerStyle::Pen, GDK_PENCIL, "" );
+#else
+ MAKE_CURSOR( PointerStyle::Pen, pen_, RID_CURSOR_PEN );
+#endif
+
+ MAP_BUILTIN( PointerStyle::HSplit, GDK_SB_H_DOUBLE_ARROW, "col-resize" );
+ MAP_BUILTIN( PointerStyle::VSplit, GDK_SB_V_DOUBLE_ARROW, "row-resize" );
+
+ MAP_BUILTIN( PointerStyle::Move, GDK_FLEUR, "move" );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ MAKE_CURSOR( PointerStyle::Null, null, RID_CURSOR_NULL );
+#else
+ MAP_BUILTIN( PointerStyle::Null, 0, "none" );
+#endif
+
+ MAKE_CURSOR( PointerStyle::Magnify, magnify_, RID_CURSOR_MAGNIFY );
+ MAKE_CURSOR( PointerStyle::Fill, fill_, RID_CURSOR_FILL );
+ MAKE_CURSOR( PointerStyle::MoveData, movedata_, RID_CURSOR_MOVE_DATA );
+ MAKE_CURSOR( PointerStyle::CopyData, copydata_, RID_CURSOR_COPY_DATA );
+ MAKE_CURSOR( PointerStyle::MoveFile, movefile_, RID_CURSOR_MOVE_FILE );
+ MAKE_CURSOR( PointerStyle::CopyFile, copyfile_, RID_CURSOR_COPY_FILE );
+ MAKE_CURSOR( PointerStyle::MoveFiles, movefiles_, RID_CURSOR_MOVE_FILES );
+ MAKE_CURSOR( PointerStyle::CopyFiles, copyfiles_, RID_CURSOR_COPY_FILES );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ MAKE_CURSOR( PointerStyle::NotAllowed, nodrop_, RID_CURSOR_NOT_ALLOWED );
+#else
+ MAP_BUILTIN( PointerStyle::NotAllowed, 0, "not-allowed" );
+#endif
+
+ MAKE_CURSOR( PointerStyle::Rotate, rotate_, RID_CURSOR_ROTATE );
+ MAKE_CURSOR( PointerStyle::HShear, hshear_, RID_CURSOR_H_SHEAR );
+ MAKE_CURSOR( PointerStyle::VShear, vshear_, RID_CURSOR_V_SHEAR );
+ MAKE_CURSOR( PointerStyle::DrawLine, drawline_, RID_CURSOR_DRAW_LINE );
+ MAKE_CURSOR( PointerStyle::DrawRect, drawrect_, RID_CURSOR_DRAW_RECT );
+ MAKE_CURSOR( PointerStyle::DrawPolygon, drawpolygon_, RID_CURSOR_DRAW_POLYGON );
+ MAKE_CURSOR( PointerStyle::DrawBezier, drawbezier_, RID_CURSOR_DRAW_BEZIER );
+ MAKE_CURSOR( PointerStyle::DrawArc, drawarc_, RID_CURSOR_DRAW_ARC );
+ MAKE_CURSOR( PointerStyle::DrawPie, drawpie_, RID_CURSOR_DRAW_PIE );
+ MAKE_CURSOR( PointerStyle::DrawCircleCut, drawcirclecut_, RID_CURSOR_DRAW_CIRCLE_CUT );
+ MAKE_CURSOR( PointerStyle::DrawEllipse, drawellipse_, RID_CURSOR_DRAW_ELLIPSE );
+ MAKE_CURSOR( PointerStyle::DrawConnect, drawconnect_, RID_CURSOR_DRAW_CONNECT );
+ MAKE_CURSOR( PointerStyle::DrawText, drawtext_, RID_CURSOR_DRAW_TEXT );
+ MAKE_CURSOR( PointerStyle::Mirror, mirror_, RID_CURSOR_MIRROR );
+ MAKE_CURSOR( PointerStyle::Crook, crook_, RID_CURSOR_CROOK );
+ MAKE_CURSOR( PointerStyle::Crop, crop_, RID_CURSOR_CROP );
+ MAKE_CURSOR( PointerStyle::MovePoint, movepoint_, RID_CURSOR_MOVE_POINT );
+ MAKE_CURSOR( PointerStyle::MoveBezierWeight, movebezierweight_, RID_CURSOR_MOVE_BEZIER_WEIGHT );
+ MAKE_CURSOR( PointerStyle::DrawFreehand, drawfreehand_, RID_CURSOR_DRAW_FREEHAND );
+ MAKE_CURSOR( PointerStyle::DrawCaption, drawcaption_, RID_CURSOR_DRAW_CAPTION );
+ MAKE_CURSOR( PointerStyle::LinkData, linkdata_, RID_CURSOR_LINK_DATA );
+ MAKE_CURSOR( PointerStyle::MoveDataLink, movedlnk_, RID_CURSOR_MOVE_DATA_LINK );
+ MAKE_CURSOR( PointerStyle::CopyDataLink, copydlnk_, RID_CURSOR_COPY_DATA_LINK );
+ MAKE_CURSOR( PointerStyle::LinkFile, linkfile_, RID_CURSOR_LINK_FILE );
+ MAKE_CURSOR( PointerStyle::MoveFileLink, moveflnk_, RID_CURSOR_MOVE_FILE_LINK );
+ MAKE_CURSOR( PointerStyle::CopyFileLink, copyflnk_, RID_CURSOR_COPY_FILE_LINK );
+ MAKE_CURSOR( PointerStyle::Chart, chart_, RID_CURSOR_CHART );
+ MAKE_CURSOR( PointerStyle::Detective, detective_, RID_CURSOR_DETECTIVE );
+ MAKE_CURSOR( PointerStyle::PivotCol, pivotcol_, RID_CURSOR_PIVOT_COLUMN );
+ MAKE_CURSOR( PointerStyle::PivotRow, pivotrow_, RID_CURSOR_PIVOT_ROW );
+ MAKE_CURSOR( PointerStyle::PivotField, pivotfld_, RID_CURSOR_PIVOT_FIELD );
+ MAKE_CURSOR( PointerStyle::PivotDelete, pivotdel_, RID_CURSOR_PIVOT_DELETE );
+ MAKE_CURSOR( PointerStyle::Chain, chain_, RID_CURSOR_CHAIN );
+ MAKE_CURSOR( PointerStyle::ChainNotAllowed, chainnot_, RID_CURSOR_CHAIN_NOT_ALLOWED );
+ MAKE_CURSOR( PointerStyle::AutoScrollN, asn_, RID_CURSOR_AUTOSCROLL_N );
+ MAKE_CURSOR( PointerStyle::AutoScrollS, ass_, RID_CURSOR_AUTOSCROLL_S );
+ MAKE_CURSOR( PointerStyle::AutoScrollW, asw_, RID_CURSOR_AUTOSCROLL_W );
+ MAKE_CURSOR( PointerStyle::AutoScrollE, ase_, RID_CURSOR_AUTOSCROLL_E );
+ MAKE_CURSOR( PointerStyle::AutoScrollNW, asnw_, RID_CURSOR_AUTOSCROLL_NW );
+ MAKE_CURSOR( PointerStyle::AutoScrollNE, asne_, RID_CURSOR_AUTOSCROLL_NE );
+ MAKE_CURSOR( PointerStyle::AutoScrollSW, assw_, RID_CURSOR_AUTOSCROLL_SW );
+ MAKE_CURSOR( PointerStyle::AutoScrollSE, asse_, RID_CURSOR_AUTOSCROLL_SE );
+ MAKE_CURSOR( PointerStyle::AutoScrollNS, asns_, RID_CURSOR_AUTOSCROLL_NS );
+ MAKE_CURSOR( PointerStyle::AutoScrollWE, aswe_, RID_CURSOR_AUTOSCROLL_WE );
+ MAKE_CURSOR( PointerStyle::AutoScrollNSWE, asnswe_, RID_CURSOR_AUTOSCROLL_NSWE );
+ MAKE_CURSOR( PointerStyle::TextVertical, vertcurs_, RID_CURSOR_TEXT_VERTICAL );
+
+ // #i32329#
+ MAKE_CURSOR( PointerStyle::TabSelectS, tblsels_, RID_CURSOR_TAB_SELECT_S );
+ MAKE_CURSOR( PointerStyle::TabSelectE, tblsele_, RID_CURSOR_TAB_SELECT_E );
+ MAKE_CURSOR( PointerStyle::TabSelectSE, tblselse_, RID_CURSOR_TAB_SELECT_SE );
+ MAKE_CURSOR( PointerStyle::TabSelectW, tblselw_, RID_CURSOR_TAB_SELECT_W );
+ MAKE_CURSOR( PointerStyle::TabSelectSW, tblselsw_, RID_CURSOR_TAB_SELECT_SW );
+
+ MAKE_CURSOR( PointerStyle::HideWhitespace, hidewhitespace_, RID_CURSOR_HIDE_WHITESPACE );
+ MAKE_CURSOR( PointerStyle::ShowWhitespace, showwhitespace_, RID_CURSOR_SHOW_WHITESPACE );
+ MAKE_CURSOR( PointerStyle::FatCross, fatcross_, RID_CURSOR_FATCROSS );
+
+ default:
+ SAL_WARN( "vcl.gtk", "pointer " << static_cast<int>(ePointerStyle) << "not implemented" );
+ break;
+ }
+ if( !pCursor )
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ pCursor = gdk_cursor_new_for_display( m_pGdkDisplay, GDK_LEFT_PTR );
+#else
+ pCursor = gdk_cursor_new_from_name("normal", nullptr);
+#endif
+ }
+
+ m_aCursors[ ePointerStyle ] = pCursor;
+ }
+
+ return m_aCursors[ ePointerStyle ];
+}
+
+int GtkSalDisplay::CaptureMouse( SalFrame* pSFrame )
+{
+ GtkSalFrame* pFrame = static_cast<GtkSalFrame*>(pSFrame);
+
+ if( !pFrame )
+ {
+ if( m_pCapture )
+ static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( false, false, false );
+ m_pCapture = nullptr;
+ return 0;
+ }
+
+ if( m_pCapture )
+ {
+ if( pFrame == m_pCapture )
+ return 1;
+ static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( false, false, false );
+ }
+
+ m_pCapture = pFrame;
+ pFrame->grabPointer( true, false, false );
+ return 1;
+}
+
+/**********************************************************************
+ * class GtkSalData *
+ **********************************************************************/
+
+GtkSalData::GtkSalData()
+ : GenericUnixSalData()
+{
+ m_pUserEvent = nullptr;
+}
+
+#if defined(GDK_WINDOWING_X11)
+static XIOErrorHandler aOrigXIOErrorHandler = nullptr;
+
+extern "C" {
+
+static int XIOErrorHdl(Display *)
+{
+ fprintf(stderr, "X IO Error\n");
+ _exit(1);
+ // avoid crashes in unrelated threads that still run while atexit
+ // handlers are in progress
+}
+
+}
+#endif
+
+GtkSalData::~GtkSalData()
+{
+ // sanity check: at this point nobody should be yielding, but wake them
+ // up anyway before the condition they're waiting on gets destroyed.
+ m_aDispatchCondition.set();
+
+ osl::MutexGuard g( m_aDispatchMutex );
+ if (m_pUserEvent)
+ {
+ g_source_destroy (m_pUserEvent);
+ g_source_unref (m_pUserEvent);
+ m_pUserEvent = nullptr;
+ }
+#if defined(GDK_WINDOWING_X11)
+ if (DLSYM_GDK_IS_X11_DISPLAY(gdk_display_get_default()))
+ XSetIOErrorHandler(aOrigXIOErrorHandler);
+#endif
+}
+
+void GtkSalData::Dispose()
+{
+ deInitNWF();
+}
+
+/// Allows events to be processed, returns true if we processed an event.
+bool GtkSalData::Yield( bool bWait, bool bHandleAllCurrentEvents )
+{
+ /* #i33212# only enter g_main_context_iteration in one thread at any one
+ * time, else one of them potentially will never end as long as there is
+ * another thread in there. Having only one yielding thread actually dispatch
+ * fits the vcl event model (see e.g. the generic plugin).
+ */
+ bool bDispatchThread = false;
+ bool bWasEvent = false;
+ {
+ // release YieldMutex (and re-acquire at block end)
+ SolarMutexReleaser aReleaser;
+ if( m_aDispatchMutex.tryToAcquire() )
+ bDispatchThread = true;
+ else if( ! bWait )
+ {
+ return false; // someone else is waiting already, return
+ }
+
+ if( bDispatchThread )
+ {
+ int nMaxEvents = bHandleAllCurrentEvents ? 100 : 1;
+ bool wasOneEvent = true;
+ while( nMaxEvents-- && wasOneEvent )
+ {
+ wasOneEvent = g_main_context_iteration( nullptr, bWait && !bWasEvent );
+ if( wasOneEvent )
+ bWasEvent = true;
+ }
+ if (m_aException)
+ std::rethrow_exception(m_aException);
+ }
+ else if( bWait )
+ {
+ /* #i41693# in case the dispatch thread hangs in join
+ * for this thread the condition will never be set
+ * workaround: timeout of 1 second an emergency exit
+ */
+ // we are the dispatch thread
+ m_aDispatchCondition.reset();
+ m_aDispatchCondition.wait(std::chrono::seconds(1));
+ }
+ }
+
+ if( bDispatchThread )
+ {
+ m_aDispatchMutex.release();
+ if( bWasEvent )
+ m_aDispatchCondition.set(); // trigger non dispatch thread yields
+ }
+
+ return bWasEvent;
+}
+
+static GtkStyleProvider* CreateStyleProvider()
+{
+ /*
+ set a provider to:
+
+ 1) allow certain widgets to have no padding
+
+ 1.a) little close button in menubar to close back to start-center
+ 1.b) and small buttons in view->data sources (button.small-button)
+ 1.c.1) gtk3 small toolbar button in infobars (toolbar.small-button button)
+ 1.c.2) gtk4 small toolbar button in infobars (box.small-button button)
+ 1.d) comboboxes in the data browser for tdf#137695 (box#combobox button.small-button,
+ which would instead be combobox button.small-button if we didn't replace GtkComboBox,
+ see GtkInstanceComboBox for an explanation for why we do that)
+ 1.e) entry in the data browser for tdf#137695 (entry.small-button)
+ 1.f) spinbutton in the data browser tdf#141633 (spinbutton.small-button)
+
+ 2) hide the unwanted active tab in an 'overflow' notebook of double-decker notebooks.
+ (tdf#122623) it's nigh impossible to have a GtkNotebook without an active (checked) tab,
+ so theme the unwanted tab into invisibility
+ */
+ GtkCssProvider* pStyleProvider = gtk_css_provider_new();
+ static const gchar data[] =
+ "button.small-button, toolbar.small-button button, box.small-button button, "
+ "combobox.small-button *.combo, box#combobox.small-button *.combo, entry.small-button, "
+ "spinbutton.small-button, spinbutton.small-button entry, spinbutton.small-button button { "
+ "padding: 0; margin-left: 0; margin-right: 0; margin-top: 0; margin-bottom: 0;"
+ "border-width: 0; min-height: 0; min-width: 0; }"
+#if GTK_CHECK_VERSION(4, 0, 0)
+ // we basically assumed during dialog design that the frame's were invisible, because
+ // they used to be in the default theme during gtk3
+ "frame { border-style: none; }"
+#endif
+ "notebook.overflow > header.top > tabs > tab:checked { "
+ "box-shadow: none; padding: 0 0 0 0; margin: 0 0 0 0;"
+ "border-image: none; border-image-width: 0 0 0 0;"
+ "background-image: none; background-color: transparent;"
+ "border-radius: 0 0 0 0; border-width: 0 0 0 0;"
+ "border-style: none; border-color: transparent;"
+ "opacity: 0; min-height: 0; min-width: 0; }"
+ // https://css-tricks.com/restart-css-animation/
+ // This animation appears twice with two different names so we can change
+ // the class from "call_attention_1" to "call_attention_2" to restart the
+ // animation
+ "@keyframes shinkandrestore1 { 50% { margin-left: 15px; margin-right: 15px; opacity: 0.5; } }"
+ "@keyframes shinkandrestore2 { 50% { margin-left: 15px; margin-right: 15px; opacity: 0.5; } }"
+ " *.call_attention_1 {"
+ "animation-name: shinkandrestore1; animation-duration: 1s; "
+ "animation-timing-function: linear; animation-iteration-count: 2; }"
+ " *.call_attention_2 {"
+ "animation-name: shinkandrestore2; animation-duration: 1s; "
+ "animation-timing-function: linear; animation-iteration-count: 2; }";
+ css_provider_load_from_data(pStyleProvider, data, -1);
+ return GTK_STYLE_PROVIDER(pStyleProvider);
+}
+
+void GtkSalData::Init()
+{
+ SAL_INFO( "vcl.gtk", "GtkMainloop::Init()" );
+
+ /*
+ * open connection to X11 Display
+ * try in this order:
+ * o -display command line parameter,
+ * o $DISPLAY environment variable
+ * o default display
+ */
+
+ GdkDisplay *pGdkDisp = nullptr;
+
+ // is there a -display command line parameter?
+ rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
+ int nParams = osl_getCommandArgCount();
+ OString aDisplay;
+ OUString aParam, aBin;
+ char** pCmdLineAry = new char*[ nParams+1 ];
+ osl_getExecutableFile( &aParam.pData );
+ osl_getSystemPathFromFileURL( aParam.pData, &aBin.pData );
+ pCmdLineAry[0] = g_strdup( OUStringToOString( aBin, aEnc ).getStr() );
+ for (int i = 0; i < nParams; ++i)
+ {
+ osl_getCommandArg(i, &aParam.pData );
+ OString aBParam( OUStringToOString( aParam, aEnc ) );
+
+ if( aParam == "-display" || aParam == "--display" )
+ {
+ pCmdLineAry[i+1] = g_strdup( "--display" );
+ osl_getCommandArg(i+1, &aParam.pData );
+ aDisplay = OUStringToOString( aParam, aEnc );
+ }
+ else
+ pCmdLineAry[i+1] = g_strdup( aBParam.getStr() );
+ }
+ // add executable
+ nParams++;
+
+ g_set_application_name(SalGenericSystem::getFrameClassName());
+
+ // Set consistent name of the root accessible
+ OUString aAppName = Application::GetAppName();
+ if( !aAppName.isEmpty() )
+ {
+ OString aPrgName = OUStringToOString(aAppName, aEnc);
+ g_set_prgname(aPrgName.getStr());
+ }
+
+ // init gtk/gdk
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_init_check();
+#else
+ gtk_init_check( &nParams, &pCmdLineAry );
+#endif
+
+ for (int i = 0; i < nParams; ++i)
+ g_free( pCmdLineAry[i] );
+ delete [] pCmdLineAry;
+
+#if OSL_DEBUG_LEVEL > 1
+ if (g_getenv("SAL_DEBUG_UPDATES"))
+ gdk_window_set_debug_updates (TRUE);
+#endif
+
+ pGdkDisp = gdk_display_get_default();
+ if ( !pGdkDisp )
+ {
+ OUString aProgramFileURL;
+ osl_getExecutableFile( &aProgramFileURL.pData );
+ OUString aProgramSystemPath;
+ osl_getSystemPathFromFileURL (aProgramFileURL.pData, &aProgramSystemPath.pData);
+ OString aProgramName = OUStringToOString(
+ aProgramSystemPath,
+ osl_getThreadTextEncoding() );
+ fprintf( stderr, "%s X11 error: Can't open display: %s\n",
+ aProgramName.getStr(), aDisplay.getStr());
+ fprintf( stderr, " Set DISPLAY environment variable, use -display option\n");
+ fprintf( stderr, " or check permissions of your X-Server\n");
+ fprintf( stderr, " (See \"man X\" resp. \"man xhost\" for details)\n");
+ fflush( stderr );
+ exit(0);
+ }
+
+ ErrorTrapPush();
+
+#if defined(GDK_WINDOWING_X11)
+ if (DLSYM_GDK_IS_X11_DISPLAY(pGdkDisp))
+ aOrigXIOErrorHandler = XSetIOErrorHandler(XIOErrorHdl);
+#endif
+
+ GtkSalDisplay *pDisplay = new GtkSalDisplay( pGdkDisp );
+ SetDisplay( pDisplay );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ pDisplay->emitDisplayChanged();
+ GListModel *pMonitors = gdk_display_get_monitors(pGdkDisp);
+ g_signal_connect(pMonitors, "items-changed", G_CALLBACK(signalMonitorsChanged), pDisplay);
+
+ gtk_style_context_add_provider_for_display(pGdkDisp, CreateStyleProvider(),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+#else
+ int nScreens = gdk_display_get_n_screens( pGdkDisp );
+ for( int n = 0; n < nScreens; n++ )
+ {
+ GdkScreen *pScreen = gdk_display_get_screen( pGdkDisp, n );
+ if (!pScreen)
+ continue;
+
+ pDisplay->screenSizeChanged( pScreen );
+ pDisplay->monitorsChanged( pScreen );
+ // add signal handler to notify screen size changes
+ g_signal_connect( G_OBJECT(pScreen), "size-changed",
+ G_CALLBACK(signalScreenSizeChanged), pDisplay );
+ g_signal_connect( G_OBJECT(pScreen), "monitors-changed",
+ G_CALLBACK(signalMonitorsChanged), pDisplay );
+
+ gtk_style_context_add_provider_for_screen(pScreen, CreateStyleProvider(),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+#endif
+}
+
+void GtkSalData::ErrorTrapPush()
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+# if defined(GDK_WINDOWING_X11)
+ GdkDisplay* pGdkDisp = gdk_display_get_default();
+ if (DLSYM_GDK_IS_X11_DISPLAY(pGdkDisp))
+ gdk_x11_display_error_trap_push(pGdkDisp);
+# endif
+#else
+ gdk_error_trap_push();
+#endif
+}
+
+bool GtkSalData::ErrorTrapPop( bool bIgnoreError )
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+# if defined(GDK_WINDOWING_X11)
+ GdkDisplay* pGdkDisp = gdk_display_get_default();
+ if (DLSYM_GDK_IS_X11_DISPLAY(pGdkDisp))
+ {
+ if (bIgnoreError)
+ {
+ gdk_x11_display_error_trap_pop_ignored(pGdkDisp); // faster
+ return false;
+ }
+ return gdk_x11_display_error_trap_pop(pGdkDisp) != 0;
+ }
+# endif
+ return false;
+#else
+ if (bIgnoreError)
+ {
+ gdk_error_trap_pop_ignored (); // faster
+ return false;
+ }
+ return gdk_error_trap_pop () != 0;
+#endif
+}
+
+#if !GLIB_CHECK_VERSION(2,32,0)
+#define G_SOURCE_REMOVE FALSE
+#endif
+
+extern "C" {
+
+ struct SalGtkTimeoutSource {
+ GSource aParent;
+ GTimeVal aFireTime;
+ GtkSalTimer *pInstance;
+ };
+
+ static void sal_gtk_timeout_defer( SalGtkTimeoutSource *pTSource )
+ {
+ g_get_current_time( &pTSource->aFireTime );
+ g_time_val_add( &pTSource->aFireTime, pTSource->pInstance->m_nTimeoutMS * 1000 );
+ }
+
+ static gboolean sal_gtk_timeout_expired( SalGtkTimeoutSource *pTSource,
+ gint *nTimeoutMS, GTimeVal const *pTimeNow )
+ {
+ glong nDeltaSec = pTSource->aFireTime.tv_sec - pTimeNow->tv_sec;
+ glong nDeltaUSec = pTSource->aFireTime.tv_usec - pTimeNow->tv_usec;
+ if( nDeltaSec < 0 || ( nDeltaSec == 0 && nDeltaUSec < 0) )
+ {
+ *nTimeoutMS = 0;
+ return true;
+ }
+ if( nDeltaUSec < 0 )
+ {
+ nDeltaUSec += 1000000;
+ nDeltaSec -= 1;
+ }
+ // if the clock changes backwards we need to cope ...
+ if( o3tl::make_unsigned(nDeltaSec) > 1 + ( pTSource->pInstance->m_nTimeoutMS / 1000 ) )
+ {
+ sal_gtk_timeout_defer( pTSource );
+ return true;
+ }
+
+ *nTimeoutMS = MIN( G_MAXINT, ( nDeltaSec * 1000 + (nDeltaUSec + 999) / 1000 ) );
+
+ return *nTimeoutMS == 0;
+ }
+
+ static gboolean sal_gtk_timeout_prepare( GSource *pSource, gint *nTimeoutMS )
+ {
+ SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource);
+
+ GTimeVal aTimeNow;
+ g_get_current_time( &aTimeNow );
+
+ return sal_gtk_timeout_expired( pTSource, nTimeoutMS, &aTimeNow );
+ }
+
+ static gboolean sal_gtk_timeout_check( GSource *pSource )
+ {
+ SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource);
+
+ GTimeVal aTimeNow;
+ g_get_current_time( &aTimeNow );
+
+ return ( pTSource->aFireTime.tv_sec < aTimeNow.tv_sec ||
+ ( pTSource->aFireTime.tv_sec == aTimeNow.tv_sec &&
+ pTSource->aFireTime.tv_usec < aTimeNow.tv_usec ) );
+ }
+
+ static gboolean sal_gtk_timeout_dispatch( GSource *pSource, GSourceFunc, gpointer )
+ {
+ SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource);
+
+ if( !pTSource->pInstance )
+ return FALSE;
+
+ SolarMutexGuard aGuard;
+
+ sal_gtk_timeout_defer( pTSource );
+
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->maSchedCtx.mpSalTimer )
+ pSVData->maSchedCtx.mpSalTimer->CallCallback();
+
+ return G_SOURCE_REMOVE;
+ }
+
+ static GSourceFuncs sal_gtk_timeout_funcs =
+ {
+ sal_gtk_timeout_prepare,
+ sal_gtk_timeout_check,
+ sal_gtk_timeout_dispatch,
+ nullptr, nullptr, nullptr
+ };
+}
+
+static SalGtkTimeoutSource *
+create_sal_gtk_timeout( GtkSalTimer *pTimer )
+{
+ GSource *pSource = g_source_new( &sal_gtk_timeout_funcs, sizeof( SalGtkTimeoutSource ) );
+ SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource);
+ pTSource->pInstance = pTimer;
+
+ // #i36226# timers should be executed with lower priority
+ // than XEvents like in generic plugin
+ g_source_set_priority( pSource, G_PRIORITY_LOW );
+ g_source_set_can_recurse( pSource, true );
+ g_source_set_callback( pSource,
+ /* unused dummy */ g_idle_remove_by_data,
+ nullptr, nullptr );
+ g_source_attach( pSource, g_main_context_default() );
+#ifdef DBG_UTIL
+ g_source_set_name( pSource, "VCL timeout source" );
+#endif
+
+ sal_gtk_timeout_defer( pTSource );
+
+ return pTSource;
+}
+
+GtkSalTimer::GtkSalTimer()
+ : m_pTimeout(nullptr)
+ , m_nTimeoutMS(0)
+{
+}
+
+GtkSalTimer::~GtkSalTimer()
+{
+ GetGtkInstance()->RemoveTimer();
+ Stop();
+}
+
+bool GtkSalTimer::Expired()
+{
+ if( !m_pTimeout || g_source_is_destroyed( &m_pTimeout->aParent ) )
+ return false;
+
+ gint nDummy = 0;
+ GTimeVal aTimeNow;
+ g_get_current_time( &aTimeNow );
+ return !!sal_gtk_timeout_expired( m_pTimeout, &nDummy, &aTimeNow);
+}
+
+void GtkSalTimer::Start( sal_uInt64 nMS )
+{
+ // glib is not 64bit safe in this regard.
+ assert( nMS <= G_MAXINT );
+ if ( nMS > G_MAXINT )
+ nMS = G_MAXINT;
+ m_nTimeoutMS = nMS; // for restarting
+ Stop(); // FIXME: ideally re-use an existing m_pTimeout
+ m_pTimeout = create_sal_gtk_timeout( this );
+}
+
+void GtkSalTimer::Stop()
+{
+ if( m_pTimeout )
+ {
+ g_source_destroy( &m_pTimeout->aParent );
+ g_source_unref( &m_pTimeout->aParent );
+ m_pTimeout = nullptr;
+ }
+}
+
+extern "C" {
+ static gboolean call_userEventFn( void *data )
+ {
+ SolarMutexGuard aGuard;
+ const SalGenericDisplay *pDisplay = GetGenericUnixSalData()->GetDisplay();
+ if ( pDisplay )
+ {
+ GtkSalDisplay *pThisDisplay = static_cast<GtkSalData *>(data)->GetGtkDisplay();
+ assert(static_cast<const SalGenericDisplay *>(pThisDisplay) == pDisplay);
+ pThisDisplay->DispatchInternalEvent();
+ }
+ return true;
+ }
+}
+
+void GtkSalData::TriggerUserEventProcessing()
+{
+ if (m_pUserEvent)
+ g_main_context_wakeup (nullptr); // really needed ?
+ else // nothing pending anyway
+ {
+ m_pUserEvent = g_idle_source_new();
+ // tdf#110737 set user-events to a lower priority than system redraw
+ // events, which is G_PRIORITY_HIGH_IDLE + 20, so presentations
+ // queue-redraw has a chance to be fulfilled
+ g_source_set_priority (m_pUserEvent, G_PRIORITY_HIGH_IDLE + 30);
+ g_source_set_can_recurse (m_pUserEvent, true);
+ g_source_set_callback (m_pUserEvent, call_userEventFn,
+ static_cast<gpointer>(this), nullptr);
+ g_source_attach (m_pUserEvent, g_main_context_default ());
+ }
+}
+
+void GtkSalData::TriggerAllUserEventsProcessed()
+{
+ assert( m_pUserEvent );
+ g_source_destroy( m_pUserEvent );
+ g_source_unref( m_pUserEvent );
+ m_pUserEvent = nullptr;
+}
+
+void GtkSalDisplay::TriggerUserEventProcessing()
+{
+ GetGtkSalData()->TriggerUserEventProcessing();
+}
+
+void GtkSalDisplay::TriggerAllUserEventsProcessed()
+{
+ GetGtkSalData()->TriggerAllUserEventsProcessed();
+}
+
+GtkWidget* GtkSalDisplay::findGtkWidgetForNativeHandle(sal_uIntPtr hWindow) const
+{
+ for (auto pSalFrame : m_aFrames )
+ {
+ const SystemEnvData* pEnvData = pSalFrame->GetSystemData();
+ if (pEnvData->GetWindowHandle(pSalFrame) == hWindow)
+ return GTK_WIDGET(pEnvData->pWidget);
+ }
+ return nullptr;
+}
+
+void GtkSalDisplay::deregisterFrame( SalFrame* pFrame )
+{
+ if( m_pCapture == pFrame )
+ {
+ static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( false, false, false );
+ m_pCapture = nullptr;
+ }
+ SalGenericDisplay::deregisterFrame( pFrame );
+}
+
+namespace {
+
+struct ButtonOrder
+{
+ std::string_view m_aType;
+ int m_nPriority;
+};
+
+}
+
+int getButtonPriority(std::string_view rType)
+{
+ static const size_t N_TYPES = 8;
+ static const ButtonOrder aDiscardCancelSave[N_TYPES] =
+ {
+ { "discard", 0 },
+ { "cancel", 1 },
+ { "close", 1 },
+ { "no", 2 },
+ { "open", 3 },
+ { "save", 3 },
+ { "yes", 3 },
+ { "ok", 3 }
+ };
+
+ static const ButtonOrder aSaveDiscardCancel[N_TYPES] =
+ {
+ { "open", 0 },
+ { "save", 0 },
+ { "yes", 0 },
+ { "ok", 0 },
+ { "discard", 1 },
+ { "no", 1 },
+ { "cancel", 2 },
+ { "close", 2 }
+ };
+
+ const ButtonOrder* pOrder = &aDiscardCancelSave[0];
+
+ const OUString &rEnv = Application::GetDesktopEnvironment();
+
+ if (rEnv.equalsIgnoreAsciiCase("windows") ||
+ rEnv.equalsIgnoreAsciiCase("tde") ||
+ rEnv.startsWithIgnoreAsciiCase("kde"))
+ {
+ pOrder = &aSaveDiscardCancel[0];
+ }
+
+ for (size_t i = 0; i < N_TYPES; ++i, ++pOrder)
+ {
+ if (rType == pOrder->m_aType)
+ return pOrder->m_nPriority;
+ }
+
+ return -1;
+}
+
+void container_remove(GtkWidget* pContainer, GtkWidget* pChild)
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_remove(GTK_CONTAINER(pContainer), pChild);
+#else
+ assert(GTK_IS_BOX(pContainer) || GTK_IS_GRID(pContainer) || GTK_IS_POPOVER(pContainer) ||
+ GTK_IS_FIXED(pContainer) || GTK_IS_WINDOW(pContainer));
+ if (GTK_IS_BOX(pContainer))
+ gtk_box_remove(GTK_BOX(pContainer), pChild);
+ else if (GTK_IS_GRID(pContainer))
+ gtk_grid_remove(GTK_GRID(pContainer), pChild);
+ else if (GTK_IS_POPOVER(pContainer))
+ gtk_popover_set_child(GTK_POPOVER(pContainer), nullptr);
+ else if (GTK_IS_WINDOW(pContainer))
+ gtk_window_set_child(GTK_WINDOW(pContainer), nullptr);
+ else if (GTK_IS_FIXED(pContainer))
+ gtk_fixed_remove(GTK_FIXED(pContainer), pChild);
+#endif
+}
+
+void container_add(GtkWidget* pContainer, GtkWidget* pChild)
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(pContainer), pChild);
+#else
+ assert(GTK_IS_BOX(pContainer) || GTK_IS_GRID(pContainer) || GTK_IS_POPOVER(pContainer) ||
+ GTK_IS_FIXED(pContainer) || GTK_IS_WINDOW(pContainer));
+ if (GTK_IS_BOX(pContainer))
+ gtk_box_append(GTK_BOX(pContainer), pChild);
+ else if (GTK_IS_GRID(pContainer))
+ gtk_grid_attach(GTK_GRID(pContainer), pChild, 0, 0, 1, 1);
+ else if (GTK_IS_POPOVER(pContainer))
+ gtk_popover_set_child(GTK_POPOVER(pContainer), pChild);
+ else if (GTK_IS_WINDOW(pContainer))
+ gtk_window_set_child(GTK_WINDOW(pContainer), pChild);
+ else if (GTK_IS_FIXED(pContainer))
+ gtk_fixed_put(GTK_FIXED(pContainer), pChild, 0, 0);
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtkframe.cxx b/vcl/unx/gtk3/gtkframe.cxx
new file mode 100644
index 000000000..3d8cce5e0
--- /dev/null
+++ b/vcl/unx/gtk3/gtkframe.cxx
@@ -0,0 +1,6081 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unx/gtk/gtkframe.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include <unx/gtk/gtkgdi.hxx>
+#include <unx/gtk/gtksalmenu.hxx>
+#include <unx/gtk/hudawareness.h>
+#include <vcl/event.hxx>
+#include <vcl/i18nhelp.hxx>
+#include <vcl/keycodes.hxx>
+#include <unx/geninst.h>
+#include <headless/svpgdi.hxx>
+#include <sal/log.hxx>
+#include <tools/diagnose_ex.h>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/window.hxx>
+#include <vcl/settings.hxx>
+
+#include <gtk/gtk.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <unx/gtk/gtkbackend.hxx>
+
+#include <window.h>
+
+#include <basegfx/vector/b2ivector.hxx>
+
+#include <dlfcn.h>
+
+#include <algorithm>
+
+#if OSL_DEBUG_LEVEL > 1
+# include <cstdio>
+#endif
+
+#include <i18nlangtag/mslangid.hxx>
+
+#include <cstdlib>
+#include <cmath>
+
+#include <com/sun/star/awt/MouseButton.hpp>
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+# define GDK_ALT_MASK GDK_MOD1_MASK
+# define GDK_TOPLEVEL_STATE_MAXIMIZED GDK_WINDOW_STATE_MAXIMIZED
+# define GDK_TOPLEVEL_STATE_MINIMIZED GDK_WINDOW_STATE_ICONIFIED
+# define gdk_wayland_surface_get_wl_surface gdk_wayland_window_get_wl_surface
+# define gdk_x11_surface_get_xid gdk_x11_window_get_xid
+#endif
+
+using namespace com::sun::star;
+
+int GtkSalFrame::m_nFloats = 0;
+
+static GDBusConnection* pSessionBus = nullptr;
+
+static void EnsureSessionBus()
+{
+ if (!pSessionBus)
+ pSessionBus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
+}
+
+sal_uInt16 GtkSalFrame::GetKeyModCode( guint state )
+{
+ sal_uInt16 nCode = 0;
+ if( state & GDK_SHIFT_MASK )
+ nCode |= KEY_SHIFT;
+ if( state & GDK_CONTROL_MASK )
+ nCode |= KEY_MOD1;
+ if (state & GDK_ALT_MASK)
+ nCode |= KEY_MOD2;
+ if( state & GDK_SUPER_MASK )
+ nCode |= KEY_MOD3;
+ return nCode;
+}
+
+sal_uInt16 GtkSalFrame::GetMouseModCode( guint state )
+{
+ sal_uInt16 nCode = GetKeyModCode( state );
+ if( state & GDK_BUTTON1_MASK )
+ nCode |= MOUSE_LEFT;
+ if( state & GDK_BUTTON2_MASK )
+ nCode |= MOUSE_MIDDLE;
+ if( state & GDK_BUTTON3_MASK )
+ nCode |= MOUSE_RIGHT;
+
+ return nCode;
+}
+
+// KEY_F26 is the last function key known to keycodes.hxx
+static bool IsFunctionKeyVal(guint keyval)
+{
+ return keyval >= GDK_KEY_F1 && keyval <= GDK_KEY_F26;
+}
+
+sal_uInt16 GtkSalFrame::GetKeyCode(guint keyval)
+{
+ sal_uInt16 nCode = 0;
+ if( keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9 )
+ nCode = KEY_0 + (keyval-GDK_KEY_0);
+ else if( keyval >= GDK_KEY_KP_0 && keyval <= GDK_KEY_KP_9 )
+ nCode = KEY_0 + (keyval-GDK_KEY_KP_0);
+ else if( keyval >= GDK_KEY_A && keyval <= GDK_KEY_Z )
+ nCode = KEY_A + (keyval-GDK_KEY_A );
+ else if( keyval >= GDK_KEY_a && keyval <= GDK_KEY_z )
+ nCode = KEY_A + (keyval-GDK_KEY_a );
+ else if (IsFunctionKeyVal(keyval))
+ {
+ switch( keyval )
+ {
+ // - - - - - Sun keyboard, see vcl/unx/source/app/saldisp.cxx
+ // although GDK_KEY_F1 ... GDK_KEY_L10 are known to
+ // gdk/gdkkeysyms.h, they are unlikely to be generated
+ // except possibly by Solaris systems
+ // this whole section needs review
+ case GDK_KEY_L2:
+ nCode = KEY_F12;
+ break;
+ case GDK_KEY_L3: nCode = KEY_PROPERTIES; break;
+ case GDK_KEY_L4: nCode = KEY_UNDO; break;
+ case GDK_KEY_L6: nCode = KEY_COPY; break; // KEY_F16
+ case GDK_KEY_L8: nCode = KEY_PASTE; break; // KEY_F18
+ case GDK_KEY_L10: nCode = KEY_CUT; break; // KEY_F20
+ default:
+ nCode = KEY_F1 + (keyval-GDK_KEY_F1); break;
+ }
+ }
+ else
+ {
+ switch( keyval )
+ {
+ case GDK_KEY_KP_Down:
+ case GDK_KEY_Down: nCode = KEY_DOWN; break;
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_Up: nCode = KEY_UP; break;
+ case GDK_KEY_KP_Left:
+ case GDK_KEY_Left: nCode = KEY_LEFT; break;
+ case GDK_KEY_KP_Right:
+ case GDK_KEY_Right: nCode = KEY_RIGHT; break;
+ case GDK_KEY_KP_Begin:
+ case GDK_KEY_KP_Home:
+ case GDK_KEY_Begin:
+ case GDK_KEY_Home: nCode = KEY_HOME; break;
+ case GDK_KEY_KP_End:
+ case GDK_KEY_End: nCode = KEY_END; break;
+ case GDK_KEY_KP_Page_Up:
+ case GDK_KEY_Page_Up: nCode = KEY_PAGEUP; break;
+ case GDK_KEY_KP_Page_Down:
+ case GDK_KEY_Page_Down: nCode = KEY_PAGEDOWN; break;
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_Return: nCode = KEY_RETURN; break;
+ case GDK_KEY_Escape: nCode = KEY_ESCAPE; break;
+ case GDK_KEY_ISO_Left_Tab:
+ case GDK_KEY_KP_Tab:
+ case GDK_KEY_Tab: nCode = KEY_TAB; break;
+ case GDK_KEY_BackSpace: nCode = KEY_BACKSPACE; break;
+ case GDK_KEY_KP_Space:
+ case GDK_KEY_space: nCode = KEY_SPACE; break;
+ case GDK_KEY_KP_Insert:
+ case GDK_KEY_Insert: nCode = KEY_INSERT; break;
+ case GDK_KEY_KP_Delete:
+ case GDK_KEY_Delete: nCode = KEY_DELETE; break;
+ case GDK_KEY_plus:
+ case GDK_KEY_KP_Add: nCode = KEY_ADD; break;
+ case GDK_KEY_minus:
+ case GDK_KEY_KP_Subtract: nCode = KEY_SUBTRACT; break;
+ case GDK_KEY_asterisk:
+ case GDK_KEY_KP_Multiply: nCode = KEY_MULTIPLY; break;
+ case GDK_KEY_slash:
+ case GDK_KEY_KP_Divide: nCode = KEY_DIVIDE; break;
+ case GDK_KEY_period: nCode = KEY_POINT; break;
+ case GDK_KEY_decimalpoint: nCode = KEY_POINT; break;
+ case GDK_KEY_comma: nCode = KEY_COMMA; break;
+ case GDK_KEY_less: nCode = KEY_LESS; break;
+ case GDK_KEY_greater: nCode = KEY_GREATER; break;
+ case GDK_KEY_KP_Equal:
+ case GDK_KEY_equal: nCode = KEY_EQUAL; break;
+ case GDK_KEY_Find: nCode = KEY_FIND; break;
+ case GDK_KEY_Menu: nCode = KEY_CONTEXTMENU;break;
+ case GDK_KEY_Help: nCode = KEY_HELP; break;
+ case GDK_KEY_Undo: nCode = KEY_UNDO; break;
+ case GDK_KEY_Redo: nCode = KEY_REPEAT; break;
+ // on a sun keyboard this actually is usually SunXK_Stop = 0x0000FF69 (XK_Cancel),
+ // but VCL doesn't have a key definition for that
+ case GDK_KEY_Cancel: nCode = KEY_F11; break;
+ case GDK_KEY_KP_Decimal:
+ case GDK_KEY_KP_Separator: nCode = KEY_DECIMAL; break;
+ case GDK_KEY_asciitilde: nCode = KEY_TILDE; break;
+ case GDK_KEY_leftsinglequotemark:
+ case GDK_KEY_quoteleft: nCode = KEY_QUOTELEFT; break;
+ case GDK_KEY_bracketleft: nCode = KEY_BRACKETLEFT; break;
+ case GDK_KEY_bracketright: nCode = KEY_BRACKETRIGHT; break;
+ case GDK_KEY_semicolon: nCode = KEY_SEMICOLON; break;
+ case GDK_KEY_quoteright: nCode = KEY_QUOTERIGHT; break;
+ // some special cases, also see saldisp.cxx
+ // - - - - - - - - - - - - - Apollo - - - - - - - - - - - - - 0x1000
+ // These can be found in ap_keysym.h
+ case 0x1000FF02: // apXK_Copy
+ nCode = KEY_COPY;
+ break;
+ case 0x1000FF03: // apXK_Cut
+ nCode = KEY_CUT;
+ break;
+ case 0x1000FF04: // apXK_Paste
+ nCode = KEY_PASTE;
+ break;
+ case 0x1000FF14: // apXK_Repeat
+ nCode = KEY_REPEAT;
+ break;
+ // Exit, Save
+ // - - - - - - - - - - - - - - D E C - - - - - - - - - - - - - 0x1000
+ // These can be found in DECkeysym.h
+ case 0x1000FF00:
+ nCode = KEY_DELETE;
+ break;
+ // - - - - - - - - - - - - - - H P - - - - - - - - - - - - - 0x1000
+ // These can be found in HPkeysym.h
+ case 0x1000FF73: // hpXK_DeleteChar
+ nCode = KEY_DELETE;
+ break;
+ case 0x1000FF74: // hpXK_BackTab
+ case 0x1000FF75: // hpXK_KP_BackTab
+ nCode = KEY_TAB;
+ break;
+ // - - - - - - - - - - - - - - I B M - - - - - - - - - - - - -
+ // - - - - - - - - - - - - - - O S F - - - - - - - - - - - - - 0x1004
+ // These also can be found in HPkeysym.h
+ case 0x1004FF02: // osfXK_Copy
+ nCode = KEY_COPY;
+ break;
+ case 0x1004FF03: // osfXK_Cut
+ nCode = KEY_CUT;
+ break;
+ case 0x1004FF04: // osfXK_Paste
+ nCode = KEY_PASTE;
+ break;
+ case 0x1004FF07: // osfXK_BackTab
+ nCode = KEY_TAB;
+ break;
+ case 0x1004FF08: // osfXK_BackSpace
+ nCode = KEY_BACKSPACE;
+ break;
+ case 0x1004FF1B: // osfXK_Escape
+ nCode = KEY_ESCAPE;
+ break;
+ // Up, Down, Left, Right, PageUp, PageDown
+ // - - - - - - - - - - - - - - S C O - - - - - - - - - - - - -
+ // - - - - - - - - - - - - - - S G I - - - - - - - - - - - - - 0x1007
+ // - - - - - - - - - - - - - - S N I - - - - - - - - - - - - -
+ // - - - - - - - - - - - - - - S U N - - - - - - - - - - - - - 0x1005
+ // These can be found in Sunkeysym.h
+ case 0x1005FF10: // SunXK_F36
+ nCode = KEY_F11;
+ break;
+ case 0x1005FF11: // SunXK_F37
+ nCode = KEY_F12;
+ break;
+ case 0x1005FF70: // SunXK_Props
+ nCode = KEY_PROPERTIES;
+ break;
+ case 0x1005FF71: // SunXK_Front
+ nCode = KEY_FRONT;
+ break;
+ case 0x1005FF72: // SunXK_Copy
+ nCode = KEY_COPY;
+ break;
+ case 0x1005FF73: // SunXK_Open
+ nCode = KEY_OPEN;
+ break;
+ case 0x1005FF74: // SunXK_Paste
+ nCode = KEY_PASTE;
+ break;
+ case 0x1005FF75: // SunXK_Cut
+ nCode = KEY_CUT;
+ break;
+ // - - - - - - - - - - - - - X F 8 6 - - - - - - - - - - - - - 0x1008
+ // These can be found in XF86keysym.h
+ // but more importantly they are also available GTK/Gdk version 3
+ // and hence are already provided in gdk/gdkkeysyms.h, and hence
+ // in gdk/gdk.h
+ case GDK_KEY_Copy: nCode = KEY_COPY; break; // 0x1008ff57
+ case GDK_KEY_Cut: nCode = KEY_CUT; break; // 0x1008ff58
+ case GDK_KEY_Open: nCode = KEY_OPEN; break; // 0x1008ff6b
+ case GDK_KEY_Paste: nCode = KEY_PASTE; break; // 0x1008ff6d
+ }
+ }
+
+ return nCode;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+guint GtkSalFrame::GetKeyValFor(GdkKeymap* pKeyMap, guint16 hardware_keycode, guint8 group)
+{
+ guint updated_keyval = 0;
+ gdk_keymap_translate_keyboard_state(pKeyMap, hardware_keycode,
+ GdkModifierType(0), group, &updated_keyval, nullptr, nullptr, nullptr);
+ return updated_keyval;
+}
+#endif
+
+namespace {
+
+// F10 means either KEY_F10 or KEY_MENU, which has to be decided
+// in the independent part.
+struct KeyAlternate
+{
+ sal_uInt16 nKeyCode;
+ sal_Unicode nCharCode;
+ KeyAlternate() : nKeyCode( 0 ), nCharCode( 0 ) {}
+ KeyAlternate( sal_uInt16 nKey, sal_Unicode nChar = 0 ) : nKeyCode( nKey ), nCharCode( nChar ) {}
+};
+
+}
+
+static KeyAlternate
+GetAlternateKeyCode( const sal_uInt16 nKeyCode )
+{
+ KeyAlternate aAlternate;
+
+ switch( nKeyCode )
+ {
+ case KEY_F10: aAlternate = KeyAlternate( KEY_MENU );break;
+ case KEY_F24: aAlternate = KeyAlternate( KEY_SUBTRACT, '-' );break;
+ }
+
+ return aAlternate;
+}
+
+#if OSL_DEBUG_LEVEL > 0
+static bool dumpframes = false;
+#endif
+
+bool GtkSalFrame::doKeyCallback( guint state,
+ guint keyval,
+ guint16 hardware_keycode,
+ guint8 group,
+ sal_Unicode aOrigCode,
+ bool bDown,
+ bool bSendRelease
+ )
+{
+ SalKeyEvent aEvent;
+
+ aEvent.mnCharCode = aOrigCode;
+ aEvent.mnRepeat = 0;
+
+ vcl::DeletionListener aDel( this );
+
+#if OSL_DEBUG_LEVEL > 0
+ const char* pKeyDebug = getenv("VCL_GTK3_PAINTDEBUG");
+
+ if (pKeyDebug && *pKeyDebug == '1')
+ {
+ if (bDown)
+ {
+ // shift-zero forces a re-draw and event is swallowed
+ if (keyval == GDK_KEY_0)
+ {
+ SAL_INFO("vcl.gtk3", "force widget_queue_draw.");
+ gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
+ return false;
+ }
+ else if (keyval == GDK_KEY_1)
+ {
+ SAL_INFO("vcl.gtk3", "force repaint all.");
+ TriggerPaintEvent();
+ return false;
+ }
+ else if (keyval == GDK_KEY_2)
+ {
+ dumpframes = !dumpframes;
+ SAL_INFO("vcl.gtk3", "toggle dump frames to " << dumpframes);
+ return false;
+ }
+ }
+ }
+#endif
+
+ /*
+ * #i42122# translate all keys with Ctrl and/or Alt to group 0 else
+ * shortcuts (e.g. Ctrl-o) will not work but be inserted by the
+ * application
+ *
+ * #i52338# do this for all keys that the independent part has no key code
+ * for
+ *
+ * fdo#41169 rather than use group 0, detect if there is a group which can
+ * be used to input Latin text and use that if possible
+ */
+ aEvent.mnCode = GetKeyCode( keyval );
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( aEvent.mnCode == 0 )
+ {
+ gint best_group = SAL_MAX_INT32;
+
+ // Try and find Latin layout
+ GdkKeymap* keymap = gdk_keymap_get_default();
+ GdkKeymapKey *keys;
+ gint n_keys;
+ if (gdk_keymap_get_entries_for_keyval(keymap, GDK_KEY_A, &keys, &n_keys))
+ {
+ // Find the lowest group that supports Latin layout
+ for (gint i = 0; i < n_keys; ++i)
+ {
+ if (keys[i].level != 0 && keys[i].level != 1)
+ continue;
+ best_group = std::min(best_group, keys[i].group);
+ if (best_group == 0)
+ break;
+ }
+ g_free(keys);
+ }
+
+ //Unavailable, go with original group then I suppose
+ if (best_group == SAL_MAX_INT32)
+ best_group = group;
+
+ guint updated_keyval = GetKeyValFor(keymap, hardware_keycode, best_group);
+ aEvent.mnCode = GetKeyCode(updated_keyval);
+ }
+#else
+ (void)hardware_keycode;
+ (void)group;
+#endif
+
+ aEvent.mnCode |= GetKeyModCode( state );
+
+ bool bStopProcessingKey;
+ if (bDown)
+ {
+ bStopProcessingKey = CallCallbackExc(SalEvent::KeyInput, &aEvent);
+ // #i46889# copy AlternateKeyCode handling from generic plugin
+ if (!bStopProcessingKey)
+ {
+ KeyAlternate aAlternate = GetAlternateKeyCode( aEvent.mnCode );
+ if( aAlternate.nKeyCode )
+ {
+ aEvent.mnCode = aAlternate.nKeyCode;
+ if( aAlternate.nCharCode )
+ aEvent.mnCharCode = aAlternate.nCharCode;
+ bStopProcessingKey = CallCallbackExc(SalEvent::KeyInput, &aEvent);
+ }
+ }
+ if( bSendRelease && ! aDel.isDeleted() )
+ {
+ CallCallbackExc(SalEvent::KeyUp, &aEvent);
+ }
+ }
+ else
+ bStopProcessingKey = CallCallbackExc(SalEvent::KeyUp, &aEvent);
+ return bStopProcessingKey;
+}
+
+GtkSalFrame::GtkSalFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
+ : m_nXScreen( getDisplay()->GetDefaultXScreen() )
+ , m_pHeaderBar(nullptr)
+ , m_bGraphics(false)
+ , m_nSetFocusSignalId(0)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_aSmoothScrollIdle("GtkSalFrame m_aSmoothScrollIdle")
+#endif
+{
+ getDisplay()->registerFrame( this );
+ m_bDefaultPos = true;
+ m_bDefaultSize = ( (nStyle & SalFrameStyleFlags::SIZEABLE) && ! pParent );
+ Init( pParent, nStyle );
+}
+
+GtkSalFrame::GtkSalFrame( SystemParentData* pSysData )
+ : m_nXScreen( getDisplay()->GetDefaultXScreen() )
+ , m_pHeaderBar(nullptr)
+ , m_bGraphics(false)
+ , m_nSetFocusSignalId(0)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_aSmoothScrollIdle("GtkSalFrame m_aSmoothScrollIdle")
+#endif
+{
+ getDisplay()->registerFrame( this );
+ // permanently ignore errors from our unruly children ...
+ GetGenericUnixSalData()->ErrorTrapPush();
+ m_bDefaultPos = true;
+ m_bDefaultSize = true;
+ Init( pSysData );
+}
+
+// AppMenu watch functions.
+
+static void ObjectDestroyedNotify( gpointer data )
+{
+ if ( data ) {
+ g_object_unref( data );
+ }
+}
+
+#if !GTK_CHECK_VERSION(4,0,0)
+static void hud_activated( gboolean hud_active, gpointer user_data )
+{
+ if ( hud_active )
+ {
+ SolarMutexGuard aGuard;
+ GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
+ GtkSalMenu* pSalMenu = reinterpret_cast< GtkSalMenu* >( pSalFrame->GetMenu() );
+
+ if ( pSalMenu )
+ pSalMenu->UpdateFull();
+ }
+}
+#endif
+
+static void attach_menu_model(GtkSalFrame* pSalFrame)
+{
+ GtkWidget* pWidget = pSalFrame->getWindow();
+ GdkSurface* gdkWindow = widget_get_surface(pWidget);
+
+ if ( gdkWindow == nullptr || g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) != nullptr )
+ return;
+
+ // Create menu model and action group attached to this frame.
+ GMenuModel* pMenuModel = G_MENU_MODEL( g_lo_menu_new() );
+ GActionGroup* pActionGroup = reinterpret_cast<GActionGroup*>(g_lo_action_group_new());
+
+ // Set window properties.
+ g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-menubar", pMenuModel, ObjectDestroyedNotify );
+ g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-action-group", pActionGroup, ObjectDestroyedNotify );
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ // Get a DBus session connection.
+ EnsureSessionBus();
+ if (!pSessionBus)
+ return;
+
+ // Generate menu paths.
+ sal_uIntPtr windowId = GtkSalFrame::GetNativeWindowHandle(pWidget);
+ gchar* aDBusWindowPath = g_strdup_printf( "/org/libreoffice/window/%lu", windowId );
+ gchar* aDBusMenubarPath = g_strdup_printf( "/org/libreoffice/window/%lu/menus/menubar", windowId );
+
+ GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
+#if defined(GDK_WINDOWING_X11)
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_ID", "org.libreoffice" );
+ gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_MENUBAR_OBJECT_PATH", aDBusMenubarPath );
+ gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_WINDOW_OBJECT_PATH", aDBusWindowPath );
+ gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_OBJECT_PATH", "/org/libreoffice" );
+ gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_UNIQUE_BUS_NAME", g_dbus_connection_get_unique_name( pSessionBus ) );
+ }
+#endif
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
+ {
+ gdk_wayland_window_set_dbus_properties_libgtk_only(gdkWindow, "org.libreoffice",
+ nullptr,
+ aDBusMenubarPath,
+ aDBusWindowPath,
+ "/org/libreoffice",
+ g_dbus_connection_get_unique_name( pSessionBus ));
+ }
+#endif
+ // Publish the menu model and the action group.
+ SAL_INFO("vcl.unity", "exporting menu model at " << pMenuModel << " for window " << windowId);
+ pSalFrame->m_nMenuExportId = g_dbus_connection_export_menu_model (pSessionBus, aDBusMenubarPath, pMenuModel, nullptr);
+ SAL_INFO("vcl.unity", "exporting action group at " << pActionGroup << " for window " << windowId);
+ pSalFrame->m_nActionGroupExportId = g_dbus_connection_export_action_group( pSessionBus, aDBusWindowPath, pActionGroup, nullptr);
+ pSalFrame->m_nHudAwarenessId = hud_awareness_register( pSessionBus, aDBusMenubarPath, hud_activated, pSalFrame, nullptr, nullptr );
+
+ g_free( aDBusWindowPath );
+ g_free( aDBusMenubarPath );
+#endif
+}
+
+void on_registrar_available( GDBusConnection * /*connection*/,
+ const gchar * /*name*/,
+ const gchar * /*name_owner*/,
+ gpointer user_data )
+{
+ SolarMutexGuard aGuard;
+
+ GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
+
+ SalMenu* pSalMenu = pSalFrame->GetMenu();
+
+ if ( pSalMenu != nullptr )
+ {
+ GtkSalMenu* pGtkSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
+ pGtkSalMenu->EnableUnity(true);
+ }
+}
+
+// This is called when the registrar becomes unavailable. It shows the menubar.
+void on_registrar_unavailable( GDBusConnection * /*connection*/,
+ const gchar * /*name*/,
+ gpointer user_data )
+{
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.unity", "on_registrar_unavailable");
+
+ //pSessionBus = NULL;
+ GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
+
+ SalMenu* pSalMenu = pSalFrame->GetMenu();
+
+ if ( pSalMenu ) {
+ GtkSalMenu* pGtkSalMenu = static_cast< GtkSalMenu* >( pSalMenu );
+ pGtkSalMenu->EnableUnity(false);
+ }
+}
+
+void GtkSalFrame::EnsureAppMenuWatch()
+{
+ if ( m_nWatcherId )
+ return;
+
+ // Get a DBus session connection.
+ EnsureSessionBus();
+ if (!pSessionBus)
+ return;
+
+ // Publish the menu only if AppMenu registrar is available.
+ m_nWatcherId = g_bus_watch_name_on_connection( pSessionBus,
+ "com.canonical.AppMenu.Registrar",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ on_registrar_available,
+ on_registrar_unavailable,
+ this,
+ nullptr );
+}
+
+void GtkSalFrame::InvalidateGraphics()
+{
+ if( m_pGraphics )
+ {
+ m_bGraphics = false;
+ }
+}
+
+GtkSalFrame::~GtkSalFrame()
+{
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_aSmoothScrollIdle.Stop();
+ m_aSmoothScrollIdle.ClearInvokeHandler();
+#endif
+
+ if (m_pDropTarget)
+ {
+ m_pDropTarget->deinitialize();
+ m_pDropTarget = nullptr;
+ }
+
+ if (m_pDragSource)
+ {
+ m_pDragSource->deinitialize();
+ m_pDragSource= nullptr;
+ }
+
+ InvalidateGraphics();
+
+ if (m_pParent)
+ {
+ m_pParent->m_aChildren.remove( this );
+ }
+
+ getDisplay()->deregisterFrame( this );
+
+ if( m_pRegion )
+ {
+ cairo_region_destroy( m_pRegion );
+ }
+
+ m_pIMHandler.reset();
+
+ //tdf#108705 remove grabs on event widget before
+ //destroying event widget
+ while (m_nGrabLevel)
+ removeGrabLevel();
+
+ {
+ SolarMutexGuard aGuard;
+
+ if (m_nWatcherId)
+ g_bus_unwatch_name(m_nWatcherId);
+
+ if (m_nPortalSettingChangedSignalId)
+ g_signal_handler_disconnect(m_pSettingsPortal, m_nPortalSettingChangedSignalId);
+
+ if (m_pSettingsPortal)
+ g_object_unref(m_pSettingsPortal);
+ }
+
+ GtkWidget *pEventWidget = getMouseEventWidget();
+ for (auto handler_id : m_aMouseSignalIds)
+ g_signal_handler_disconnect(G_OBJECT(pEventWidget), handler_id);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( m_pFixedContainer )
+ gtk_widget_destroy( GTK_WIDGET( m_pFixedContainer ) );
+ if( m_pEventBox )
+ gtk_widget_destroy( GTK_WIDGET(m_pEventBox) );
+ if( m_pTopLevelGrid )
+ gtk_widget_destroy( GTK_WIDGET(m_pTopLevelGrid) );
+#else
+ g_signal_handler_disconnect(G_OBJECT(gtk_widget_get_display(pEventWidget)), m_nSettingChangedSignalId);
+#endif
+ {
+ SolarMutexGuard aGuard;
+
+ if( m_pWindow )
+ {
+ g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", nullptr );
+
+ if ( pSessionBus )
+ {
+ if ( m_nHudAwarenessId )
+ hud_awareness_unregister( pSessionBus, m_nHudAwarenessId );
+ if ( m_nMenuExportId )
+ g_dbus_connection_unexport_menu_model( pSessionBus, m_nMenuExportId );
+ if ( m_nActionGroupExportId )
+ g_dbus_connection_unexport_action_group( pSessionBus, m_nActionGroupExportId );
+ }
+ m_xFrameWeld.reset();
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_widget_destroy( m_pWindow );
+#else
+ if (GTK_IS_WINDOW(m_pWindow))
+ gtk_window_destroy(GTK_WINDOW(m_pWindow));
+ else
+ g_clear_pointer(&m_pWindow, gtk_widget_unparent);
+#endif
+ }
+ }
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ if( m_pForeignParent )
+ g_object_unref( G_OBJECT( m_pForeignParent ) );
+ if( m_pForeignTopLevel )
+ g_object_unref( G_OBJECT( m_pForeignTopLevel) );
+#endif
+
+ m_pGraphics.reset();
+
+ if (m_pSurface)
+ cairo_surface_destroy(m_pSurface);
+}
+
+void GtkSalFrame::moveWindow( tools::Long nX, tools::Long nY )
+{
+ if( isChild( false ) )
+ {
+ GtkWidget* pParent = m_pParent ? gtk_widget_get_parent(m_pWindow) : nullptr;
+ // tdf#130414 it's possible that we were reparented and are no longer inside
+ // our original GtkFixed parent
+ if (pParent && GTK_IS_FIXED(pParent))
+ {
+ gtk_fixed_move( GTK_FIXED(pParent),
+ m_pWindow,
+ nX - m_pParent->maGeometry.nX, nY - m_pParent->maGeometry.nY );
+ }
+ }
+#if !GTK_CHECK_VERSION(4,0,0)
+ else
+ gtk_window_move( GTK_WINDOW(m_pWindow), nX, nY );
+#endif
+}
+
+void GtkSalFrame::widget_set_size_request(tools::Long nWidth, tools::Long nHeight)
+{
+ gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), nWidth, nHeight );
+}
+
+void GtkSalFrame::window_resize(tools::Long nWidth, tools::Long nHeight)
+{
+ m_nWidthRequest = nWidth;
+ m_nHeightRequest = nHeight;
+ gtk_window_set_default_size(GTK_WINDOW(m_pWindow), nWidth, nHeight);
+#if !GTK_CHECK_VERSION(4,0,0)
+ if (gtk_widget_get_visible(m_pWindow))
+ gtk_window_resize(GTK_WINDOW(m_pWindow), nWidth, nHeight);
+#endif
+}
+
+void GtkSalFrame::resizeWindow( tools::Long nWidth, tools::Long nHeight )
+{
+ if( isChild( false ) )
+ {
+ widget_set_size_request(nWidth, nHeight);
+ }
+ else if( ! isChild( true, false ) )
+ window_resize(nWidth, nHeight);
+}
+
+#if !GTK_CHECK_VERSION(4,0,0)
+// tdf#124694 GtkFixed takes the max size of all its children as its
+// preferred size, causing it to not clip its child, but grow instead.
+
+static void
+ooo_fixed_get_preferred_height(GtkWidget*, gint *minimum, gint *natural)
+{
+ *minimum = 0;
+ *natural = 0;
+}
+
+static void
+ooo_fixed_get_preferred_width(GtkWidget*, gint *minimum, gint *natural)
+{
+ *minimum = 0;
+ *natural = 0;
+}
+#endif
+
+static void
+ooo_fixed_class_init(GtkFixedClass *klass)
+{
+#if !GTK_CHECK_VERSION(4,0,0)
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+ widget_class->get_accessible = ooo_fixed_get_accessible;
+ widget_class->get_preferred_height = ooo_fixed_get_preferred_height;
+ widget_class->get_preferred_width = ooo_fixed_get_preferred_width;
+#else
+ (void)klass;
+#endif
+}
+
+/*
+ * Always use a sub-class of GtkFixed we can tag for a11y. This allows us to
+ * utilize GAIL for the toplevel window and toolkit implementation incl.
+ * key event listener support ..
+ */
+
+GType
+ooo_fixed_get_type()
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo tinfo =
+ {
+ sizeof (GtkFixedClass),
+ nullptr, /* base init */
+ nullptr, /* base finalize */
+ reinterpret_cast<GClassInitFunc>(ooo_fixed_class_init), /* class init */
+ nullptr, /* class finalize */
+ nullptr, /* class data */
+ sizeof (GtkFixed), /* instance size */
+ 0, /* nb preallocs */
+ nullptr, /* instance init */
+ nullptr /* value table */
+ };
+
+ type = g_type_register_static( GTK_TYPE_FIXED, "OOoFixed",
+ &tinfo, GTypeFlags(0));
+ }
+
+ return type;
+}
+
+void GtkSalFrame::updateScreenNumber()
+{
+#if !GTK_CHECK_VERSION(4,0,0)
+ int nScreen = 0;
+ GdkScreen *pScreen = gtk_widget_get_screen( m_pWindow );
+ if( pScreen )
+ nScreen = getDisplay()->getSystem()->getScreenMonitorIdx( pScreen, maGeometry.nX, maGeometry.nY );
+ maGeometry.nDisplayScreenNumber = nScreen;
+#endif
+}
+
+GtkWidget *GtkSalFrame::getMouseEventWidget() const
+{
+#if !GTK_CHECK_VERSION(4,0,0)
+ return GTK_WIDGET(m_pEventBox);
+#else
+ return GTK_WIDGET(m_pFixedContainer);
+#endif
+}
+
+static void damaged(void *handle,
+ sal_Int32 nExtentsX, sal_Int32 nExtentsY,
+ sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(handle);
+ pThis->damaged(nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight);
+}
+
+void GtkSalFrame::InitCommon()
+{
+ m_pSurface = nullptr;
+ m_nGrabLevel = 0;
+ m_bSalObjectSetPosSize = false;
+ m_nPortalSettingChangedSignalId = 0;
+ m_pSettingsPortal = nullptr;
+
+ m_aDamageHandler.handle = this;
+ m_aDamageHandler.damaged = ::damaged;
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_aSmoothScrollIdle.SetInvokeHandler(LINK(this, GtkSalFrame, AsyncScroll));
+#endif
+
+ m_pTopLevelGrid = GTK_GRID(gtk_grid_new());
+ container_add(m_pWindow, GTK_WIDGET(m_pTopLevelGrid));
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_pEventBox = GTK_EVENT_BOX(gtk_event_box_new());
+ gtk_widget_add_events( GTK_WIDGET(m_pEventBox),
+ GDK_ALL_EVENTS_MASK );
+ gtk_widget_set_vexpand(GTK_WIDGET(m_pEventBox), true);
+ gtk_widget_set_hexpand(GTK_WIDGET(m_pEventBox), true);
+ gtk_grid_attach(m_pTopLevelGrid, GTK_WIDGET(m_pEventBox), 0, 0, 1, 1);
+#endif
+
+ // add the fixed container child,
+ // fixed is needed since we have to position plugin windows
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_pFixedContainer = GTK_FIXED(g_object_new( ooo_fixed_get_type(), nullptr ));
+ m_pDrawingArea = m_pFixedContainer;
+#else
+ m_pOverlay = GTK_OVERLAY(gtk_overlay_new());
+ m_pFixedContainer = GTK_FIXED(gtk_fixed_new());
+ m_pDrawingArea = GTK_DRAWING_AREA(gtk_drawing_area_new());
+#endif
+ gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), true);
+ gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), 1, 1);
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_container_add( GTK_CONTAINER(m_pEventBox), GTK_WIDGET(m_pFixedContainer) );
+#else
+ gtk_widget_set_vexpand(GTK_WIDGET(m_pOverlay), true);
+ gtk_widget_set_hexpand(GTK_WIDGET(m_pOverlay), true);
+ gtk_grid_attach(m_pTopLevelGrid, GTK_WIDGET(m_pOverlay), 0, 0, 1, 1);
+ gtk_overlay_set_child(m_pOverlay, GTK_WIDGET(m_pDrawingArea));
+ gtk_overlay_add_overlay(m_pOverlay, GTK_WIDGET(m_pFixedContainer));
+#endif
+
+ GtkWidget *pEventWidget = getMouseEventWidget();
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_widget_set_app_paintable(GTK_WIDGET(m_pFixedContainer), true);
+ gtk_widget_set_redraw_on_allocate(GTK_WIDGET(m_pFixedContainer), false);
+#endif
+
+#if GTK_CHECK_VERSION(4,0,0)
+ m_nSettingChangedSignalId = g_signal_connect(G_OBJECT(gtk_widget_get_display(pEventWidget)), "setting-changed", G_CALLBACK(signalStyleUpdated), this);
+#else
+ // use pEventWidget instead of m_pWindow to avoid infinite event loop under Linux Mint Mate 18.3
+ g_signal_connect(G_OBJECT(pEventWidget), "style-updated", G_CALLBACK(signalStyleUpdated), this);
+#endif
+ gtk_widget_set_has_tooltip(pEventWidget, true);
+ // connect signals
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "query-tooltip", G_CALLBACK(signalTooltipQuery), this ));
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-press-event", G_CALLBACK(signalButton), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-release-event", G_CALLBACK(signalButton), this ));
+
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "motion-notify-event", G_CALLBACK(signalMotion), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "leave-notify-event", G_CALLBACK(signalCrossing), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "enter-notify-event", G_CALLBACK(signalCrossing), this ));
+
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "scroll-event", G_CALLBACK(signalScroll), this ));
+#else
+ GtkGesture *pClick = gtk_gesture_click_new();
+ gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pClick), 0);
+ // use GTK_PHASE_TARGET instead of default GTK_PHASE_BUBBLE because I don't
+ // want click events in gtk widgets inside the overlay, e.g. the font size
+ // combobox GtkEntry in the toolbar, to be propagated down to this widget
+ gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(pClick), GTK_PHASE_TARGET);
+ gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pClick));
+ g_signal_connect(pClick, "pressed", G_CALLBACK(gesturePressed), this);
+ g_signal_connect(pClick, "released", G_CALLBACK(gestureReleased), this);
+
+ GtkEventController* pMotionController = gtk_event_controller_motion_new();
+ g_signal_connect(pMotionController, "motion", G_CALLBACK(signalMotion), this);
+ g_signal_connect(pMotionController, "enter", G_CALLBACK(signalEnter), this);
+ g_signal_connect(pMotionController, "leave", G_CALLBACK(signalLeave), this);
+ gtk_event_controller_set_propagation_phase(pMotionController, GTK_PHASE_TARGET);
+ gtk_widget_add_controller(pEventWidget, pMotionController);
+
+ GtkEventController* pScrollController = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
+ g_signal_connect(pScrollController, "scroll", G_CALLBACK(signalScroll), this);
+ gtk_widget_add_controller(pEventWidget, pScrollController);
+#endif
+
+ //Drop Target Stuff
+#if GTK_CHECK_VERSION(4,0,0)
+ GtkDropTargetAsync* pDropTarget = gtk_drop_target_async_new(nullptr, GdkDragAction(GDK_ACTION_ALL));
+ g_signal_connect(G_OBJECT(pDropTarget), "drag-enter", G_CALLBACK(signalDragMotion), this);
+ g_signal_connect(G_OBJECT(pDropTarget), "drag-motion", G_CALLBACK(signalDragMotion), this);
+ g_signal_connect(G_OBJECT(pDropTarget), "drag-leave", G_CALLBACK(signalDragLeave), this);
+ g_signal_connect(G_OBJECT(pDropTarget), "drop", G_CALLBACK(signalDragDrop), this);
+ gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pDropTarget));
+#else
+ gtk_drag_dest_set(GTK_WIDGET(pEventWidget), GtkDestDefaults(0), nullptr, 0, GdkDragAction(0));
+ gtk_drag_dest_set_track_motion(GTK_WIDGET(pEventWidget), true);
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-motion", G_CALLBACK(signalDragMotion), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-drop", G_CALLBACK(signalDragDrop), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-received", G_CALLBACK(signalDragDropReceived), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-leave", G_CALLBACK(signalDragLeave), this ));
+#endif
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ //Drag Source Stuff
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-end", G_CALLBACK(signalDragEnd), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-failed", G_CALLBACK(signalDragFailed), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-delete", G_CALLBACK(signalDragDelete), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-get", G_CALLBACK(signalDragDataGet), this ));
+#endif
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ g_signal_connect( G_OBJECT(m_pFixedContainer), "draw", G_CALLBACK(signalDraw), this );
+ g_signal_connect( G_OBJECT(m_pFixedContainer), "size-allocate", G_CALLBACK(sizeAllocated), this );
+#else
+ gtk_drawing_area_set_draw_func(m_pDrawingArea, signalDraw, this, nullptr);
+ g_signal_connect(G_OBJECT(m_pDrawingArea), "resize", G_CALLBACK(sizeAllocated), this);
+#endif
+
+ g_signal_connect(G_OBJECT(m_pFixedContainer), "realize", G_CALLBACK(signalRealize), this);
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ GtkGesture *pSwipe = gtk_gesture_swipe_new(pEventWidget);
+ g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pSwipe);
+#else
+ GtkGesture *pSwipe = gtk_gesture_swipe_new();
+ gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pSwipe));
+#endif
+ g_signal_connect(pSwipe, "swipe", G_CALLBACK(gestureSwipe), this);
+ gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pSwipe), GTK_PHASE_TARGET);
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ GtkGesture *pLongPress = gtk_gesture_long_press_new(pEventWidget);
+ g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pLongPress);
+#else
+ GtkGesture *pLongPress = gtk_gesture_long_press_new();
+ gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pLongPress));
+#endif
+ g_signal_connect(pLongPress, "pressed", G_CALLBACK(gestureLongPress), this);
+ gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pLongPress), GTK_PHASE_TARGET);
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ g_signal_connect_after( G_OBJECT(m_pWindow), "focus-in-event", G_CALLBACK(signalFocus), this );
+ g_signal_connect_after( G_OBJECT(m_pWindow), "focus-out-event", G_CALLBACK(signalFocus), this );
+ if (GTK_IS_WINDOW(m_pWindow)) // i.e. not if it's a GtkEventBox which doesn't have the signal
+ m_nSetFocusSignalId = g_signal_connect( G_OBJECT(m_pWindow), "set-focus", G_CALLBACK(signalSetFocus), this );
+#else
+ GtkEventController* pFocusController = gtk_event_controller_focus_new();
+ g_signal_connect(pFocusController, "enter", G_CALLBACK(signalFocusEnter), this);
+ g_signal_connect(pFocusController, "leave", G_CALLBACK(signalFocusLeave), this);
+ gtk_widget_set_focusable(pEventWidget, true);
+ gtk_widget_add_controller(pEventWidget, pFocusController);
+ if (GTK_IS_WINDOW(m_pWindow)) // i.e. not if it's a GtkEventBox which doesn't have the property (presumably?)
+ m_nSetFocusSignalId = g_signal_connect( G_OBJECT(m_pWindow), "notify::focus-widget", G_CALLBACK(signalSetFocus), this );
+#endif
+#if !GTK_CHECK_VERSION(4,0,0)
+ g_signal_connect( G_OBJECT(m_pWindow), "map-event", G_CALLBACK(signalMap), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "unmap-event", G_CALLBACK(signalUnmap), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "delete-event", G_CALLBACK(signalDelete), this );
+#else
+ g_signal_connect( G_OBJECT(m_pWindow), "map", G_CALLBACK(signalMap), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "unmap", G_CALLBACK(signalUnmap), this );
+ if (GTK_IS_WINDOW(m_pWindow))
+ g_signal_connect( G_OBJECT(m_pWindow), "close-request", G_CALLBACK(signalDelete), this );
+#endif
+#if !GTK_CHECK_VERSION(4,0,0)
+ g_signal_connect( G_OBJECT(m_pWindow), "configure-event", G_CALLBACK(signalConfigure), this );
+#endif
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ g_signal_connect( G_OBJECT(m_pWindow), "key-press-event", G_CALLBACK(signalKey), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "key-release-event", G_CALLBACK(signalKey), this );
+#else
+ m_pKeyController = GTK_EVENT_CONTROLLER_KEY(gtk_event_controller_key_new());
+ g_signal_connect(m_pKeyController, "key-pressed", G_CALLBACK(signalKeyPressed), this);
+ g_signal_connect(m_pKeyController, "key-released", G_CALLBACK(signalKeyReleased), this);
+ gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(m_pKeyController));
+
+#endif
+ g_signal_connect( G_OBJECT(m_pWindow), "destroy", G_CALLBACK(signalDestroy), this );
+
+ // init members
+ m_nKeyModifiers = ModKeyFlags::NONE;
+ m_bFullscreen = false;
+#if GTK_CHECK_VERSION(4,0,0)
+ m_nState = static_cast<GdkToplevelState>(0);
+#else
+ m_nState = GDK_WINDOW_STATE_WITHDRAWN;
+#endif
+ m_pIMHandler = nullptr;
+ m_pRegion = nullptr;
+ m_pDropTarget = nullptr;
+ m_pDragSource = nullptr;
+ m_bGeometryIsProvisional = false;
+ m_bIconSetWhileUnmapped = false;
+ m_bTooltipBlocked = false;
+ m_ePointerStyle = static_cast<PointerStyle>(0xffff);
+ m_pSalMenu = nullptr;
+ m_nWatcherId = 0;
+ m_nMenuExportId = 0;
+ m_nActionGroupExportId = 0;
+ m_nHudAwarenessId = 0;
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_widget_add_events( m_pWindow,
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
+ GDK_SCROLL_MASK
+ );
+#endif
+
+ // show the widgets
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_widget_show_all(GTK_WIDGET(m_pTopLevelGrid));
+#else
+ gtk_widget_show(GTK_WIDGET(m_pTopLevelGrid));
+#endif
+
+ // realize the window, we need an XWindow id
+ gtk_widget_realize( m_pWindow );
+
+ if (GTK_IS_WINDOW(m_pWindow))
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ g_signal_connect(G_OBJECT(m_pWindow), "window-state-event", G_CALLBACK(signalWindowState), this);
+#else
+ GdkSurface* gdkWindow = widget_get_surface(m_pWindow);
+ g_signal_connect(G_OBJECT(gdkWindow), "notify::state", G_CALLBACK(signalWindowState), this);
+#endif
+ }
+
+ //system data
+ m_aSystemData.SetWindowHandle(GetNativeWindowHandle(m_pWindow));
+ m_aSystemData.aShellWindow = reinterpret_cast<sal_IntPtr>(this);
+ m_aSystemData.pSalFrame = this;
+ m_aSystemData.pWidget = m_pWindow;
+ m_aSystemData.nScreen = m_nXScreen.getXScreen();
+ m_aSystemData.toolkit = SystemEnvData::Toolkit::Gtk;
+
+#if defined(GDK_WINDOWING_X11)
+ GdkDisplay *pDisplay = getGdkDisplay();
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ m_aSystemData.pDisplay = gdk_x11_display_get_xdisplay(pDisplay);
+ m_aSystemData.platform = SystemEnvData::Platform::Xcb;
+#if !GTK_CHECK_VERSION(4,0,0)
+ GdkScreen* pScreen = gtk_widget_get_screen(m_pWindow);
+ GdkVisual* pVisual = gdk_screen_get_system_visual(pScreen);
+ m_aSystemData.pVisual = gdk_x11_visual_get_xvisual(pVisual);
+#endif
+ }
+#endif
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
+ {
+ m_aSystemData.pDisplay = gdk_wayland_display_get_wl_display(pDisplay);
+ m_aSystemData.platform = SystemEnvData::Platform::Wayland;
+ }
+#endif
+
+ m_bGraphics = false;
+ m_pGraphics = nullptr;
+
+ m_nFloatFlags = FloatWinPopupFlags::NONE;
+ m_bFloatPositioned = false;
+
+ m_nWidthRequest = 0;
+ m_nHeightRequest = 0;
+
+ // fake an initial geometry, gets updated via configure event or SetPosSize
+ if (m_bDefaultPos || m_bDefaultSize)
+ {
+ Size aDefSize = calcDefaultSize();
+ maGeometry.nX = -1;
+ maGeometry.nY = -1;
+ maGeometry.nWidth = aDefSize.Width();
+ maGeometry.nHeight = aDefSize.Height();
+ maGeometry.nTopDecoration = 0;
+ maGeometry.nBottomDecoration = 0;
+ maGeometry.nLeftDecoration = 0;
+ maGeometry.nRightDecoration = 0;
+ }
+ updateScreenNumber();
+
+ SetIcon(SV_ICON_ID_OFFICE);
+}
+
+GtkSalFrame *GtkSalFrame::getFromWindow( GtkWidget *pWindow )
+{
+ return static_cast<GtkSalFrame *>(g_object_get_data( G_OBJECT( pWindow ), "SalFrame" ));
+}
+
+void GtkSalFrame::DisallowCycleFocusOut()
+{
+ if (!m_nSetFocusSignalId)
+ return;
+ // don't enable/disable can-focus as control enters and leaves
+ // embedded native gtk widgets
+ g_signal_handler_disconnect(G_OBJECT(m_pWindow), m_nSetFocusSignalId);
+ m_nSetFocusSignalId = 0;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // gtk3: set container without can-focus and focus will tab between
+ // the native embedded widgets using the default gtk handling for
+ // that
+ // gtk4: no need because the native widgets are the only
+ // thing in the overlay and the drawing widget is underneath so
+ // the natural focus cycle is sufficient
+ gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), false);
+#endif
+}
+
+bool GtkSalFrame::IsCycleFocusOutDisallowed() const
+{
+ return m_nSetFocusSignalId == 0;
+}
+
+void GtkSalFrame::AllowCycleFocusOut()
+{
+ if (m_nSetFocusSignalId)
+ return;
+#if !GTK_CHECK_VERSION(4,0,0)
+ // enable/disable can-focus as control enters and leaves
+ // embedded native gtk widgets
+ m_nSetFocusSignalId = g_signal_connect(G_OBJECT(m_pWindow), "set-focus", G_CALLBACK(signalSetFocus), this);
+#else
+ m_nSetFocusSignalId = g_signal_connect(G_OBJECT(m_pWindow), "notify::focus-widget", G_CALLBACK(signalSetFocus), this);
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // set container without can-focus and focus will tab between
+ // the native embedded widgets using the default gtk handling for
+ // that
+ // gtk4: no need because the native widgets are the only
+ // thing in the overlay and the drawing widget is underneath so
+ // the natural focus cycle is sufficient
+ gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), true);
+#endif
+}
+
+namespace
+{
+ enum ColorScheme
+ {
+ DEFAULT,
+ PREFER_DARK,
+ PREFER_LIGHT
+ };
+
+ bool ReadColorScheme(GDBusProxy* proxy, GVariant** out)
+ {
+ g_autoptr (GVariant) ret =
+ g_dbus_proxy_call_sync(proxy, "Read",
+ g_variant_new ("(ss)", "org.freedesktop.appearance", "color-scheme"),
+ G_DBUS_CALL_FLAGS_NONE, G_MAXINT, nullptr, nullptr);
+ if (!ret)
+ return false;
+
+ g_autoptr (GVariant) child = nullptr;
+ g_variant_get(ret, "(v)", &child);
+ g_variant_get(child, "v", out);
+
+ return true;
+ }
+}
+
+void GtkSalFrame::SetColorScheme(GVariant* variant)
+{
+ if (!m_pWindow)
+ return;
+
+ guint32 color_scheme = g_variant_get_uint32(variant);
+ if (color_scheme > PREFER_LIGHT)
+ color_scheme = DEFAULT;
+
+ bool bDarkIconTheme(color_scheme == PREFER_DARK);
+ GtkSettings* pSettings = gtk_widget_get_settings(m_pWindow);
+ g_object_set(pSettings, "gtk-application-prefer-dark-theme", bDarkIconTheme, nullptr);
+}
+
+static void settings_portal_changed_cb(GDBusProxy*, const char*, const char* signal_name,
+ GVariant* parameters, gpointer frame)
+{
+ if (g_strcmp0(signal_name, "SettingChanged"))
+ return;
+
+ g_autoptr (GVariant) value = nullptr;
+ const char *name_space;
+ const char *name;
+ g_variant_get(parameters, "(&s&sv)", &name_space, &name, &value);
+
+ if (g_strcmp0(name_space, "org.freedesktop.appearance") ||
+ g_strcmp0(name, "color-scheme"))
+ return;
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->SetColorScheme(value);
+}
+
+void GtkSalFrame::ListenPortalSettings()
+{
+ EnsureSessionBus();
+
+ if (!pSessionBus)
+ return;
+
+ m_pSettingsPortal = g_dbus_proxy_new_sync(pSessionBus,
+ G_DBUS_PROXY_FLAGS_NONE,
+ nullptr,
+ "org.freedesktop.portal.Desktop",
+ "/org/freedesktop/portal/desktop",
+ "org.freedesktop.portal.Settings",
+ nullptr,
+ nullptr);
+ if (!m_pSettingsPortal)
+ return;
+
+ g_autoptr (GVariant) value = nullptr;
+
+ if (!ReadColorScheme(m_pSettingsPortal, &value))
+ return;
+
+ SetColorScheme(value);
+ m_nPortalSettingChangedSignalId = g_signal_connect(m_pSettingsPortal, "g-signal", G_CALLBACK(settings_portal_changed_cb), this);
+}
+
+void GtkSalFrame::Init( SalFrame* pParent, SalFrameStyleFlags nStyle )
+{
+ if( nStyle & SalFrameStyleFlags::DEFAULT ) // ensure default style
+ {
+ nStyle |= SalFrameStyleFlags::MOVEABLE | SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::CLOSEABLE;
+ nStyle &= ~SalFrameStyleFlags::FLOAT;
+ }
+
+ m_pParent = static_cast<GtkSalFrame*>(pParent);
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_pForeignParent = nullptr;
+ m_aForeignParentWindow = None;
+ m_pForeignTopLevel = nullptr;
+ m_aForeignTopLevelWindow = None;
+#endif
+ m_nStyle = nStyle;
+
+ bool bPopup = ((nStyle & SalFrameStyleFlags::FLOAT) &&
+ !(nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION));
+
+ if( nStyle & SalFrameStyleFlags::SYSTEMCHILD )
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_pWindow = gtk_event_box_new();
+#else
+ m_pWindow = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+#endif
+ if( m_pParent )
+ {
+ // insert into container
+ gtk_fixed_put( m_pParent->getFixedContainer(),
+ m_pWindow, 0, 0 );
+ }
+ }
+ else
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_pWindow = gtk_window_new(bPopup ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL);
+#else
+ m_pWindow = gtk_window_new();
+#endif
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ // hook up F1 to show help for embedded native gtk widgets
+ GtkAccelGroup *pGroup = gtk_accel_group_new();
+ GClosure* closure = g_cclosure_new(G_CALLBACK(GtkSalFrame::NativeWidgetHelpPressed), GTK_WINDOW(m_pWindow), nullptr);
+ gtk_accel_group_connect(pGroup, GDK_KEY_F1, static_cast<GdkModifierType>(0), GTK_ACCEL_LOCKED, closure);
+ gtk_window_add_accel_group(GTK_WINDOW(m_pWindow), pGroup);
+#endif
+ }
+
+ g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", this );
+ g_object_set_data( G_OBJECT( m_pWindow ), "libo-version", const_cast<char *>(LIBO_VERSION_DOTTED));
+
+ // force wm class hint
+ if (!isChild())
+ {
+ if (m_pParent)
+ m_sWMClass = m_pParent->m_sWMClass;
+ updateWMClass();
+ }
+
+ if (GTK_IS_WINDOW(m_pWindow))
+ {
+ if (m_pParent)
+ {
+ GtkWidget* pTopLevel = widget_get_toplevel(m_pParent->m_pWindow);
+#if !GTK_CHECK_VERSION(4,0,0)
+ if (!isChild())
+ gtk_window_set_screen(GTK_WINDOW(m_pWindow), gtk_widget_get_screen(pTopLevel));
+#endif
+
+ if (!(m_pParent->m_nStyle & SalFrameStyleFlags::PLUG))
+ gtk_window_set_transient_for(GTK_WINDOW(m_pWindow), GTK_WINDOW(pTopLevel));
+ m_pParent->m_aChildren.push_back( this );
+ gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pTopLevel)), GTK_WINDOW(m_pWindow));
+ }
+ else
+ {
+ gtk_window_group_add_window(gtk_window_group_new(), GTK_WINDOW(m_pWindow));
+ g_object_unref(gtk_window_get_group(GTK_WINDOW(m_pWindow)));
+ }
+ }
+
+ // set window type
+ bool bDecoHandling =
+ ! isChild() &&
+ ( ! (nStyle & SalFrameStyleFlags::FLOAT) ||
+ (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) );
+
+ if( bDecoHandling )
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ GdkWindowTypeHint eType = GDK_WINDOW_TYPE_HINT_NORMAL;
+ if( (nStyle & SalFrameStyleFlags::DIALOG) && m_pParent != nullptr )
+ eType = GDK_WINDOW_TYPE_HINT_DIALOG;
+#endif
+ if( nStyle & SalFrameStyleFlags::INTRO )
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_window_set_role( GTK_WINDOW(m_pWindow), "splashscreen" );
+ eType = GDK_WINDOW_TYPE_HINT_SPLASHSCREEN;
+#endif
+ }
+ else if( nStyle & SalFrameStyleFlags::TOOLWINDOW )
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ eType = GDK_WINDOW_TYPE_HINT_DIALOG;
+ gtk_window_set_skip_taskbar_hint( GTK_WINDOW(m_pWindow), true );
+#endif
+ }
+ else if( nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ eType = GDK_WINDOW_TYPE_HINT_TOOLBAR;
+ gtk_window_set_focus_on_map(GTK_WINDOW(m_pWindow), false);
+#endif
+ gtk_window_set_decorated(GTK_WINDOW(m_pWindow), false);
+ }
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), eType );
+ gtk_window_set_gravity( GTK_WINDOW(m_pWindow), GDK_GRAVITY_STATIC );
+#endif
+ gtk_window_set_resizable( GTK_WINDOW(m_pWindow), bool(nStyle & SalFrameStyleFlags::SIZEABLE) );
+
+#if !GTK_CHECK_VERSION(4,0,0)
+#if defined(GDK_WINDOWING_WAYLAND)
+ //rhbz#1392145 under wayland/csd if we've overridden the default widget direction in order to set LibreOffice's
+ //UI to the configured ui language but the system ui locale is a different text direction, then the toplevel
+ //built-in close button of the titlebar follows the overridden direction rather than continue in the same
+ //direction as every other titlebar on the user's desktop. So if they don't match set an explicit
+ //header bar with the desired 'outside' direction
+ if ((eType == GDK_WINDOW_TYPE_HINT_NORMAL || eType == GDK_WINDOW_TYPE_HINT_DIALOG) && DLSYM_GDK_IS_WAYLAND_DISPLAY(GtkSalFrame::getGdkDisplay()))
+ {
+ const bool bDesktopIsRTL = MsLangId::isRightToLeft(MsLangId::getConfiguredSystemUILanguage());
+ const bool bAppIsRTL = gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL;
+ if (bDesktopIsRTL != bAppIsRTL)
+ {
+ m_pHeaderBar = GTK_HEADER_BAR(gtk_header_bar_new());
+ gtk_widget_set_direction(GTK_WIDGET(m_pHeaderBar), bDesktopIsRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
+ gtk_header_bar_set_show_close_button(m_pHeaderBar, true);
+ gtk_window_set_titlebar(GTK_WINDOW(m_pWindow), GTK_WIDGET(m_pHeaderBar));
+ gtk_widget_show(GTK_WIDGET(m_pHeaderBar));
+ }
+ }
+#endif
+#endif
+ }
+#if !GTK_CHECK_VERSION(4,0,0)
+ else if( nStyle & SalFrameStyleFlags::FLOAT )
+ gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), GDK_WINDOW_TYPE_HINT_POPUP_MENU );
+#endif
+
+ InitCommon();
+
+ if (!bPopup)
+ {
+ // Enable GMenuModel native menu
+ attach_menu_model(this);
+
+ // Listen to portal settings for e.g. prefer dark theme
+ ListenPortalSettings();
+ }
+}
+
+#if !GTK_CHECK_VERSION(4,0,0)
+GdkNativeWindow GtkSalFrame::findTopLevelSystemWindow( GdkNativeWindow )
+{
+ //FIXME: no findToplevelSystemWindow
+ return 0;
+}
+#endif
+
+void GtkSalFrame::Init( SystemParentData* pSysData )
+{
+ m_pParent = nullptr;
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_aForeignParentWindow = pSysData->aWindow;
+ m_pForeignParent = nullptr;
+ m_aForeignTopLevelWindow = findTopLevelSystemWindow(pSysData->aWindow);
+ m_pForeignTopLevel = gdk_x11_window_foreign_new_for_display( getGdkDisplay(), m_aForeignTopLevelWindow );
+ gdk_window_set_events( m_pForeignTopLevel, GDK_STRUCTURE_MASK );
+
+ if( pSysData->nSize > sizeof(pSysData->nSize)+sizeof(pSysData->aWindow) && pSysData->bXEmbedSupport )
+ {
+ m_pWindow = gtk_plug_new_for_display( getGdkDisplay(), pSysData->aWindow );
+ gtk_widget_set_can_default(m_pWindow, true);
+ gtk_widget_set_can_focus(m_pWindow, true);
+ gtk_widget_set_sensitive(m_pWindow, true);
+ }
+ else
+ {
+ m_pWindow = gtk_window_new( GTK_WINDOW_POPUP );
+ }
+#endif
+ m_nStyle = SalFrameStyleFlags::PLUG;
+ InitCommon();
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ m_pForeignParent = gdk_x11_window_foreign_new_for_display( getGdkDisplay(), m_aForeignParentWindow );
+ gdk_window_set_events( m_pForeignParent, GDK_STRUCTURE_MASK );
+#else
+ (void)pSysData;
+#endif
+
+ //FIXME: Handling embedded windows, is going to be fun ...
+}
+
+void GtkSalFrame::SetExtendedFrameStyle(SalExtStyle)
+{
+}
+
+SalGraphics* GtkSalFrame::AcquireGraphics()
+{
+ if( m_bGraphics )
+ return nullptr;
+
+ if( !m_pGraphics )
+ {
+ m_pGraphics.reset( new GtkSalGraphics( this, m_pWindow ) );
+ if (!m_pSurface)
+ {
+ AllocateFrame();
+ TriggerPaintEvent();
+ }
+ m_pGraphics->setSurface(m_pSurface, m_aFrameSize);
+ }
+ m_bGraphics = true;
+ return m_pGraphics.get();
+}
+
+void GtkSalFrame::ReleaseGraphics( SalGraphics* pGraphics )
+{
+ (void) pGraphics;
+ assert( pGraphics == m_pGraphics.get() );
+ m_bGraphics = false;
+}
+
+bool GtkSalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
+{
+ getDisplay()->SendInternalEvent( this, pData.release() );
+ return true;
+}
+
+void GtkSalFrame::SetTitle( const OUString& rTitle )
+{
+ if( m_pWindow && ! isChild() )
+ {
+ OString sTitle(OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8));
+ gtk_window_set_title(GTK_WINDOW(m_pWindow), sTitle.getStr());
+#if !GTK_CHECK_VERSION(4,0,0)
+ if (m_pHeaderBar)
+ gtk_header_bar_set_title(m_pHeaderBar, sTitle.getStr());
+#endif
+ }
+}
+
+void GtkSalFrame::SetIcon(const char* appicon)
+{
+ gtk_window_set_icon_name(GTK_WINDOW(m_pWindow), appicon);
+
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (!DLSYM_GDK_IS_WAYLAND_DISPLAY(getGdkDisplay()))
+ return;
+
+#if GTK_CHECK_VERSION(4,0,0)
+ GdkSurface* gdkWindow = gtk_native_get_surface(gtk_widget_get_native(m_pWindow));
+ gdk_wayland_toplevel_set_application_id((GDK_TOPLEVEL(gdkWindow)), appicon);
+#else
+ static auto set_application_id = reinterpret_cast<void (*) (GdkWindow*, const char*)>(
+ dlsym(nullptr, "gdk_wayland_window_set_application_id"));
+ if (set_application_id)
+ {
+ GdkSurface* gdkWindow = widget_get_surface(m_pWindow);
+ set_application_id(gdkWindow, appicon);
+ }
+#endif
+ // gdk_wayland_window_set_application_id doesn't seem to work before
+ // the window is mapped, so set this for real when/if we are mapped
+ m_bIconSetWhileUnmapped = !gtk_widget_get_mapped(m_pWindow);
+#endif
+}
+
+void GtkSalFrame::SetIcon( sal_uInt16 nIcon )
+{
+ if( (m_nStyle & (SalFrameStyleFlags::PLUG|SalFrameStyleFlags::SYSTEMCHILD|SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::INTRO|SalFrameStyleFlags::OWNERDRAWDECORATION))
+ || ! m_pWindow )
+ return;
+
+ gchar* appicon;
+
+ if (nIcon == SV_ICON_ID_TEXT)
+ appicon = g_strdup ("libreoffice-writer");
+ else if (nIcon == SV_ICON_ID_SPREADSHEET)
+ appicon = g_strdup ("libreoffice-calc");
+ else if (nIcon == SV_ICON_ID_DRAWING)
+ appicon = g_strdup ("libreoffice-draw");
+ else if (nIcon == SV_ICON_ID_PRESENTATION)
+ appicon = g_strdup ("libreoffice-impress");
+ else if (nIcon == SV_ICON_ID_DATABASE)
+ appicon = g_strdup ("libreoffice-base");
+ else if (nIcon == SV_ICON_ID_FORMULA)
+ appicon = g_strdup ("libreoffice-math");
+ else
+ appicon = g_strdup ("libreoffice-startcenter");
+
+ SetIcon(appicon);
+
+ g_free (appicon);
+}
+
+void GtkSalFrame::SetMenu( SalMenu* pSalMenu )
+{
+ m_pSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
+}
+
+SalMenu* GtkSalFrame::GetMenu()
+{
+ return m_pSalMenu;
+}
+
+void GtkSalFrame::DrawMenuBar()
+{
+}
+
+void GtkSalFrame::Center()
+{
+ if (!GTK_IS_WINDOW(m_pWindow))
+ return;
+#if !GTK_CHECK_VERSION(4,0,0)
+ if (m_pParent)
+ gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER_ON_PARENT);
+ else
+ gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER);
+#endif
+}
+
+Size GtkSalFrame::calcDefaultSize()
+{
+ Size aScreenSize(getDisplay()->GetScreenSize(GetDisplayScreen()));
+ int scale = gtk_widget_get_scale_factor(m_pWindow);
+ aScreenSize.setWidth( aScreenSize.Width() / scale );
+ aScreenSize.setHeight( aScreenSize.Height() / scale );
+ return bestmaxFrameSizeForScreenSize(aScreenSize);
+}
+
+void GtkSalFrame::SetDefaultSize()
+{
+ Size aDefSize = calcDefaultSize();
+
+ SetPosSize( 0, 0, aDefSize.Width(), aDefSize.Height(),
+ SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
+
+ if( (m_nStyle & SalFrameStyleFlags::DEFAULT) && m_pWindow )
+ gtk_window_maximize( GTK_WINDOW(m_pWindow) );
+}
+
+void GtkSalFrame::Show( bool bVisible, bool /*bNoActivate*/ )
+{
+ if( !m_pWindow )
+ return;
+
+ if( bVisible )
+ {
+ getDisplay()->startupNotificationCompleted();
+
+ if( m_bDefaultPos )
+ Center();
+ if( m_bDefaultSize )
+ SetDefaultSize();
+ setMinMaxSize();
+
+ if (isFloatGrabWindow() && !getDisplay()->GetCaptureFrame())
+ {
+ m_pParent->grabPointer(true, true, true);
+ m_pParent->addGrabLevel();
+ }
+
+#if defined(GDK_WINDOWING_WAYLAND) && !GTK_CHECK_VERSION(4,0,0)
+ /*
+ rhbz#1334915, gnome#779143, tdf#100158
+ https://gitlab.gnome.org/GNOME/gtk/-/issues/767
+
+ before gdk_wayland_window_set_application_id was available gtk
+ under wayland lacked a way to change the app_id of a window, so
+ brute force everything as a startcenter when initially shown to at
+ least get the default LibreOffice icon and not the broken app icon
+ */
+ static bool bAppIdImmutable = DLSYM_GDK_IS_WAYLAND_DISPLAY(getGdkDisplay()) &&
+ !dlsym(nullptr, "gdk_wayland_window_set_application_id");
+ if (bAppIdImmutable)
+ {
+ OString sOrigName(g_get_prgname());
+ g_set_prgname("libreoffice-startcenter");
+ gtk_widget_show(m_pWindow);
+ g_set_prgname(sOrigName.getStr());
+ }
+ else
+ {
+ gtk_widget_show(m_pWindow);
+ }
+#else
+ gtk_widget_show(m_pWindow);
+#endif
+
+ if( isFloatGrabWindow() )
+ {
+ m_nFloats++;
+ if (!getDisplay()->GetCaptureFrame())
+ {
+ grabPointer(true, true, true);
+ addGrabLevel();
+ }
+ // #i44068# reset parent's IM context
+ if( m_pParent )
+ m_pParent->EndExtTextInput(EndExtTextInputFlags::NONE);
+ }
+ }
+ else
+ {
+ if( isFloatGrabWindow() )
+ {
+ m_nFloats--;
+ if (!getDisplay()->GetCaptureFrame())
+ {
+ removeGrabLevel();
+ grabPointer(false, true, false);
+ m_pParent->removeGrabLevel();
+ bool bParentIsFloatGrabWindow = m_pParent->isFloatGrabWindow();
+ m_pParent->grabPointer(bParentIsFloatGrabWindow, true, bParentIsFloatGrabWindow);
+ }
+ }
+ gtk_widget_hide( m_pWindow );
+ if( m_pIMHandler )
+ m_pIMHandler->focusChanged( false );
+ }
+}
+
+void GtkSalFrame::setMinMaxSize()
+{
+ /* #i34504# metacity (and possibly others) do not treat
+ * _NET_WM_STATE_FULLSCREEN and max_width/height independently;
+ * whether they should is undefined. So don't set the max size hint
+ * for a full screen window.
+ */
+ if( !m_pWindow || isChild() )
+ return;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkGeometry aGeo;
+ int aHints = 0;
+ if( m_nStyle & SalFrameStyleFlags::SIZEABLE )
+ {
+ if( m_aMinSize.Width() && m_aMinSize.Height() && ! m_bFullscreen )
+ {
+ aGeo.min_width = m_aMinSize.Width();
+ aGeo.min_height = m_aMinSize.Height();
+ aHints |= GDK_HINT_MIN_SIZE;
+ }
+ if( m_aMaxSize.Width() && m_aMaxSize.Height() && ! m_bFullscreen )
+ {
+ aGeo.max_width = m_aMaxSize.Width();
+ aGeo.max_height = m_aMaxSize.Height();
+ aHints |= GDK_HINT_MAX_SIZE;
+ }
+ }
+ else
+ {
+ if (!m_bFullscreen && m_nWidthRequest && m_nHeightRequest)
+ {
+ aGeo.min_width = m_nWidthRequest;
+ aGeo.min_height = m_nHeightRequest;
+ aHints |= GDK_HINT_MIN_SIZE;
+
+ aGeo.max_width = m_nWidthRequest;
+ aGeo.max_height = m_nHeightRequest;
+ aHints |= GDK_HINT_MAX_SIZE;
+ }
+ }
+
+ if( m_bFullscreen && m_aMaxSize.Width() && m_aMaxSize.Height() )
+ {
+ aGeo.max_width = m_aMaxSize.Width();
+ aGeo.max_height = m_aMaxSize.Height();
+ aHints |= GDK_HINT_MAX_SIZE;
+ }
+ if( aHints )
+ {
+ gtk_window_set_geometry_hints( GTK_WINDOW(m_pWindow),
+ nullptr,
+ &aGeo,
+ GdkWindowHints( aHints ) );
+ }
+#endif
+}
+
+void GtkSalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight )
+{
+ if( ! isChild() )
+ {
+ m_aMaxSize = Size( nWidth, nHeight );
+ setMinMaxSize();
+ }
+}
+void GtkSalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight )
+{
+ if( ! isChild() )
+ {
+ m_aMinSize = Size( nWidth, nHeight );
+ if( m_pWindow )
+ {
+ widget_set_size_request(nWidth, nHeight);
+ setMinMaxSize();
+ }
+ }
+}
+
+void GtkSalFrame::AllocateFrame()
+{
+ basegfx::B2IVector aFrameSize( maGeometry.nWidth, maGeometry.nHeight );
+ if (m_pSurface && m_aFrameSize.getX() == aFrameSize.getX() &&
+ m_aFrameSize.getY() == aFrameSize.getY() )
+ return;
+
+ if( aFrameSize.getX() == 0 )
+ aFrameSize.setX( 1 );
+ if( aFrameSize.getY() == 0 )
+ aFrameSize.setY( 1 );
+
+ if (m_pSurface)
+ cairo_surface_destroy(m_pSurface);
+
+ m_pSurface = surface_create_similar_surface(widget_get_surface(m_pWindow),
+ CAIRO_CONTENT_COLOR_ALPHA,
+ aFrameSize.getX(),
+ aFrameSize.getY());
+ m_aFrameSize = aFrameSize;
+
+ cairo_surface_set_user_data(m_pSurface, SvpSalGraphics::getDamageKey(), &m_aDamageHandler, nullptr);
+ SAL_INFO("vcl.gtk3", "allocated Frame size of " << maGeometry.nWidth << " x " << maGeometry.nHeight);
+
+ if (m_pGraphics)
+ m_pGraphics->setSurface(m_pSurface, m_aFrameSize);
+}
+
+void GtkSalFrame::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags )
+{
+ if( !m_pWindow || isChild( true, false ) )
+ return;
+
+ if( (nFlags & ( SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT )) &&
+ (nWidth > 0 && nHeight > 0 ) // sometimes stupid things happen
+ )
+ {
+ m_bDefaultSize = false;
+
+ // tdf#131031 Just setting maGeometry.nWidth/nHeight will delete
+ // the evtl. implicitly existing space at top for the gtk native MenuBar,
+ // will make the Window too big and the StatusBar at the bottom vanish
+ // and thus breaks the fix in tdf#130841.
+ const int nImplicitMenuBarHeight(m_pSalMenu ? m_pSalMenu->GetMenuBarHeight() : 0);
+ maGeometry.nWidth = nWidth;
+ maGeometry.nHeight = nHeight - nImplicitMenuBarHeight;
+
+ if( isChild( false ) )
+ widget_set_size_request(nWidth, nHeight);
+ else if( ! ( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED ) )
+ window_resize(nWidth, nHeight);
+
+ setMinMaxSize();
+ }
+ else if( m_bDefaultSize )
+ SetDefaultSize();
+
+ m_bDefaultSize = false;
+
+ if( nFlags & ( SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ) )
+ {
+ if( m_pParent )
+ {
+ if( AllSettings::GetLayoutRTL() )
+ nX = m_pParent->maGeometry.nWidth-m_nWidthRequest-1-nX;
+ nX += m_pParent->maGeometry.nX;
+ nY += m_pParent->maGeometry.nY;
+ }
+
+ if (nFlags & SAL_FRAME_POSSIZE_X)
+ maGeometry.nX = nX;
+ if (nFlags & SAL_FRAME_POSSIZE_Y)
+ maGeometry.nY = nY;
+ m_bGeometryIsProvisional = true;
+
+ m_bDefaultPos = false;
+
+ moveWindow(maGeometry.nX, maGeometry.nY);
+
+ updateScreenNumber();
+ }
+ else if( m_bDefaultPos )
+ Center();
+
+ m_bDefaultPos = false;
+}
+
+void GtkSalFrame::GetClientSize( tools::Long& rWidth, tools::Long& rHeight )
+{
+ if( m_pWindow && !(m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) )
+ {
+ rWidth = maGeometry.nWidth;
+ rHeight = maGeometry.nHeight;
+ }
+ else
+ rWidth = rHeight = 0;
+}
+
+void GtkSalFrame::GetWorkArea( tools::Rectangle& rRect )
+{
+ GdkRectangle aRect;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkScreen *pScreen = gtk_widget_get_screen(m_pWindow);
+ tools::Rectangle aRetRect;
+ int max = gdk_screen_get_n_monitors (pScreen);
+ for (int i = 0; i < max; ++i)
+ {
+ gdk_screen_get_monitor_workarea(pScreen, i, &aRect);
+ tools::Rectangle aMonitorRect(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height);
+ aRetRect.Union(aMonitorRect);
+ }
+ rRect = aRetRect;
+#else
+ GdkDisplay* pDisplay = gtk_widget_get_display(m_pWindow);
+ GdkSurface* gdkWindow = widget_get_surface(m_pWindow);
+ GdkMonitor* pMonitor = gdk_display_get_monitor_at_surface(pDisplay, gdkWindow);
+ gdk_monitor_get_geometry(pMonitor, &aRect);
+ rRect = tools::Rectangle(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height);
+#endif
+}
+
+SalFrame* GtkSalFrame::GetParent() const
+{
+ return m_pParent;
+}
+
+void GtkSalFrame::SetWindowState( const SalFrameState* pState )
+{
+ if( ! m_pWindow || ! pState || isChild( true, false ) )
+ return;
+
+ const WindowStateMask nMaxGeometryMask =
+ WindowStateMask::X | WindowStateMask::Y |
+ WindowStateMask::Width | WindowStateMask::Height |
+ WindowStateMask::MaximizedX | WindowStateMask::MaximizedY |
+ WindowStateMask::MaximizedWidth | WindowStateMask::MaximizedHeight;
+
+ if( (pState->mnMask & WindowStateMask::State) &&
+ ! ( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED ) &&
+ (pState->mnState & WindowStateState::Maximized) &&
+ (pState->mnMask & nMaxGeometryMask) == nMaxGeometryMask )
+ {
+ resizeWindow( pState->mnWidth, pState->mnHeight );
+ moveWindow( pState->mnX, pState->mnY );
+ m_bDefaultPos = m_bDefaultSize = false;
+
+ updateScreenNumber();
+
+ m_nState = GdkToplevelState(m_nState | GDK_TOPLEVEL_STATE_MAXIMIZED);
+ m_aRestorePosSize = tools::Rectangle( Point( pState->mnX, pState->mnY ),
+ Size( pState->mnWidth, pState->mnHeight ) );
+ }
+ else if( pState->mnMask & (WindowStateMask::X | WindowStateMask::Y |
+ WindowStateMask::Width | WindowStateMask::Height ) )
+ {
+ sal_uInt16 nPosSizeFlags = 0;
+ tools::Long nX = pState->mnX - (m_pParent ? m_pParent->maGeometry.nX : 0);
+ tools::Long nY = pState->mnY - (m_pParent ? m_pParent->maGeometry.nY : 0);
+ if( pState->mnMask & WindowStateMask::X )
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
+ else
+ nX = maGeometry.nX - (m_pParent ? m_pParent->maGeometry.nX : 0);
+ if( pState->mnMask & WindowStateMask::Y )
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
+ else
+ nY = maGeometry.nY - (m_pParent ? m_pParent->maGeometry.nY : 0);
+ if( pState->mnMask & WindowStateMask::Width )
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
+ if( pState->mnMask & WindowStateMask::Height )
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
+ SetPosSize( nX, nY, pState->mnWidth, pState->mnHeight, nPosSizeFlags );
+ }
+
+ if( pState->mnMask & WindowStateMask::State && ! isChild() )
+ {
+ if( pState->mnState & WindowStateState::Maximized )
+ gtk_window_maximize( GTK_WINDOW(m_pWindow) );
+ else
+ gtk_window_unmaximize( GTK_WINDOW(m_pWindow) );
+ /* #i42379# there is no rollup state in GDK; and rolled up windows are
+ * (probably depending on the WM) reported as iconified. If we iconify a
+ * window here that was e.g. a dialog, then it will be unmapped but still
+ * not be displayed in the task list, so it's an iconified window that
+ * the user cannot get out of this state. So do not set the iconified state
+ * on windows with a parent (that is transient frames) since these tend
+ * to not be represented in an icon task list.
+ */
+ bool bMinimize = pState->mnState & WindowStateState::Minimized && !m_pParent;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (bMinimize)
+ gtk_window_minimize(GTK_WINDOW(m_pWindow));
+ else
+ gtk_window_unminimize(GTK_WINDOW(m_pWindow));
+#else
+ if (bMinimize)
+ gtk_window_iconify(GTK_WINDOW(m_pWindow));
+ else
+ gtk_window_deiconify(GTK_WINDOW(m_pWindow));
+#endif
+ }
+ TriggerPaintEvent();
+}
+
+namespace
+{
+ void GetPosAndSize(GtkWindow *pWindow, tools::Long& rX, tools::Long &rY, tools::Long &rWidth, tools::Long &rHeight)
+ {
+ gint width, height;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gint root_x, root_y;
+ gtk_window_get_position(GTK_WINDOW(pWindow), &root_x, &root_y);
+ rX = root_x;
+ rY = root_y;
+
+ gtk_window_get_size(GTK_WINDOW(pWindow), &width, &height);
+#else
+ rX = 0;
+ rY = 0;
+ gtk_window_get_default_size(GTK_WINDOW(pWindow), &width, &height);
+#endif
+ rWidth = width;
+ rHeight = height;
+ }
+
+ tools::Rectangle GetPosAndSize(GtkWindow *pWindow)
+ {
+ tools::Long nX, nY, nWidth, nHeight;
+ GetPosAndSize(pWindow, nX, nY, nWidth, nHeight);
+ return tools::Rectangle(nX, nY, nX + nWidth, nY + nHeight);
+ }
+}
+
+bool GtkSalFrame::GetWindowState( SalFrameState* pState )
+{
+ pState->mnState = WindowStateState::Normal;
+ pState->mnMask = WindowStateMask::State;
+
+ // rollup ? gtk 2.2 does not seem to support the shaded state
+ if( m_nState & GDK_TOPLEVEL_STATE_MINIMIZED )
+ pState->mnState |= WindowStateState::Minimized;
+ if( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED )
+ {
+ pState->mnState |= WindowStateState::Maximized;
+ pState->mnX = m_aRestorePosSize.Left();
+ pState->mnY = m_aRestorePosSize.Top();
+ pState->mnWidth = m_aRestorePosSize.GetWidth();
+ pState->mnHeight = m_aRestorePosSize.GetHeight();
+ GetPosAndSize(GTK_WINDOW(m_pWindow), pState->mnMaximizedX, pState->mnMaximizedY,
+ pState->mnMaximizedWidth, pState->mnMaximizedHeight);
+ pState->mnMask |= WindowStateMask::MaximizedX |
+ WindowStateMask::MaximizedY |
+ WindowStateMask::MaximizedWidth |
+ WindowStateMask::MaximizedHeight;
+ }
+ else
+ {
+ GetPosAndSize(GTK_WINDOW(m_pWindow), pState->mnX, pState->mnY,
+ pState->mnWidth, pState->mnHeight);
+ }
+
+ pState->mnMask |= WindowStateMask::X |
+ WindowStateMask::Y |
+ WindowStateMask::Width |
+ WindowStateMask::Height;
+
+ return true;
+}
+
+void GtkSalFrame::SetScreen( unsigned int nNewScreen, SetType eType, tools::Rectangle const *pSize )
+{
+ if( !m_pWindow )
+ return;
+
+ if (maGeometry.nDisplayScreenNumber == nNewScreen && eType == SetType::RetainSize)
+ return;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ int nX = maGeometry.nX, nY = maGeometry.nY,
+ nWidth = maGeometry.nWidth, nHeight = maGeometry.nHeight;
+ GdkScreen *pScreen = nullptr;
+ GdkRectangle aNewMonitor;
+
+ bool bSpanAllScreens = nNewScreen == static_cast<unsigned int>(-1);
+ bool bSpanMonitorsWhenFullscreen = bSpanAllScreens && getDisplay()->getSystem()->GetDisplayScreenCount() > 1;
+ gint nMonitor = -1;
+ if (bSpanMonitorsWhenFullscreen) //span all screens
+ {
+ pScreen = gtk_widget_get_screen( m_pWindow );
+ aNewMonitor.x = 0;
+ aNewMonitor.y = 0;
+ aNewMonitor.width = gdk_screen_get_width(pScreen);
+ aNewMonitor.height = gdk_screen_get_height(pScreen);
+ }
+ else
+ {
+ bool bSameMonitor = false;
+
+ if (!bSpanAllScreens)
+ {
+ pScreen = getDisplay()->getSystem()->getScreenMonitorFromIdx( nNewScreen, nMonitor );
+ if (!pScreen)
+ {
+ g_warning ("Attempt to move GtkSalFrame to invalid screen %d => "
+ "fallback to current\n", nNewScreen);
+ }
+ }
+
+ if (!pScreen)
+ {
+ pScreen = gtk_widget_get_screen( m_pWindow );
+ bSameMonitor = true;
+ }
+
+ // Heavy lifting, need to move screen ...
+ if( pScreen != gtk_widget_get_screen( m_pWindow ))
+ gtk_window_set_screen( GTK_WINDOW( m_pWindow ), pScreen );
+
+ gint nOldMonitor = gdk_screen_get_monitor_at_window(
+ pScreen, widget_get_surface( m_pWindow ) );
+ if (bSameMonitor)
+ nMonitor = nOldMonitor;
+
+ #if OSL_DEBUG_LEVEL > 1
+ if( nMonitor == nOldMonitor )
+ g_warning( "An apparently pointless SetScreen - should we elide it ?" );
+ #endif
+
+ GdkRectangle aOldMonitor;
+ gdk_screen_get_monitor_geometry( pScreen, nOldMonitor, &aOldMonitor );
+ gdk_screen_get_monitor_geometry( pScreen, nMonitor, &aNewMonitor );
+
+ nX = aNewMonitor.x + nX - aOldMonitor.x;
+ nY = aNewMonitor.y + nY - aOldMonitor.y;
+ }
+
+ bool bResize = false;
+ bool bVisible = gtk_widget_get_mapped( m_pWindow );
+ if( bVisible )
+ Show( false );
+
+ if( eType == SetType::Fullscreen )
+ {
+ nX = aNewMonitor.x;
+ nY = aNewMonitor.y;
+ nWidth = aNewMonitor.width;
+ nHeight = aNewMonitor.height;
+ m_nStyle |= SalFrameStyleFlags::PARTIAL_FULLSCREEN;
+ bResize = true;
+
+ // #i110881# for the benefit of compiz set a max size here
+ // else setting to fullscreen fails for unknown reasons
+ m_aMaxSize.setWidth( aNewMonitor.width );
+ m_aMaxSize.setHeight( aNewMonitor.height );
+ }
+
+ if( pSize && eType == SetType::UnFullscreen )
+ {
+ nX = pSize->Left();
+ nY = pSize->Top();
+ nWidth = pSize->GetWidth();
+ nHeight = pSize->GetHeight();
+ m_nStyle &= ~SalFrameStyleFlags::PARTIAL_FULLSCREEN;
+ bResize = true;
+ }
+
+ if (bResize)
+ {
+ // temporarily re-sizeable
+ if( !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
+ gtk_window_set_resizable( GTK_WINDOW(m_pWindow), true );
+ window_resize(nWidth, nHeight);
+ }
+
+ gtk_window_move(GTK_WINDOW(m_pWindow), nX, nY);
+
+ GdkFullscreenMode eMode =
+ bSpanMonitorsWhenFullscreen ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR;
+
+ gdk_window_set_fullscreen_mode(widget_get_surface(m_pWindow), eMode);
+
+ GtkWidget* pMenuBarContainerWidget = m_pSalMenu ? m_pSalMenu->GetMenuBarContainerWidget() : nullptr;
+ if( eType == SetType::Fullscreen )
+ {
+ if (pMenuBarContainerWidget)
+ gtk_widget_hide(pMenuBarContainerWidget);
+ if (bSpanMonitorsWhenFullscreen)
+ gtk_window_fullscreen(GTK_WINDOW(m_pWindow));
+ else
+ gtk_window_fullscreen_on_monitor(GTK_WINDOW(m_pWindow), pScreen, nMonitor);
+
+ }
+ else if( eType == SetType::UnFullscreen )
+ {
+ if (pMenuBarContainerWidget)
+ gtk_widget_show(pMenuBarContainerWidget);
+ gtk_window_unfullscreen( GTK_WINDOW( m_pWindow ) );
+ }
+
+ if( eType == SetType::UnFullscreen &&
+ !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
+ gtk_window_set_resizable( GTK_WINDOW( m_pWindow ), FALSE );
+
+ // FIXME: we should really let gtk+ handle our widget hierarchy ...
+ if( m_pParent && gtk_widget_get_screen( m_pParent->m_pWindow ) != pScreen )
+ SetParent( nullptr );
+
+ std::list< GtkSalFrame* > aChildren = m_aChildren;
+ for (auto const& child : aChildren)
+ child->SetScreen( nNewScreen, SetType::RetainSize );
+
+ m_bDefaultPos = m_bDefaultSize = false;
+ updateScreenNumber();
+
+ if( bVisible )
+ Show( true );
+
+#else
+ (void)pSize; // assume restore will restore the original size without our help
+
+ bool bSpanMonitorsWhenFullscreen = nNewScreen == static_cast<unsigned int>(-1);
+
+ GdkFullscreenMode eMode =
+ bSpanMonitorsWhenFullscreen ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR;
+
+ g_object_set(widget_get_surface(m_pWindow), "fullscreen-mode", eMode, nullptr);
+
+ GtkWidget* pMenuBarContainerWidget = m_pSalMenu ? m_pSalMenu->GetMenuBarContainerWidget() : nullptr;
+ if (eType == SetType::Fullscreen)
+ {
+ if (!(m_nStyle & SalFrameStyleFlags::SIZEABLE))
+ {
+ // temp make it resizable, restore when unfullscreened
+ gtk_window_set_resizable(GTK_WINDOW(m_pWindow), true);
+ }
+
+ m_nStyle |= SalFrameStyleFlags::PARTIAL_FULLSCREEN;
+
+ if (pMenuBarContainerWidget)
+ gtk_widget_hide(pMenuBarContainerWidget);
+ if (bSpanMonitorsWhenFullscreen)
+ gtk_window_fullscreen(GTK_WINDOW(m_pWindow));
+ else
+ {
+ GdkDisplay* pDisplay = gtk_widget_get_display(m_pWindow);
+ GListModel* pList = gdk_display_get_monitors(pDisplay);
+ GdkMonitor* pMonitor = static_cast<GdkMonitor*>(g_list_model_get_item(pList, nNewScreen));
+ if (!pMonitor)
+ pMonitor = gdk_display_get_monitor_at_surface(pDisplay, widget_get_surface(m_pWindow));
+ gtk_window_fullscreen_on_monitor(GTK_WINDOW(m_pWindow), pMonitor);
+ }
+ }
+ else if (eType == SetType::UnFullscreen)
+ {
+ m_nStyle &= ~SalFrameStyleFlags::PARTIAL_FULLSCREEN;
+
+ if (pMenuBarContainerWidget)
+ gtk_widget_show(pMenuBarContainerWidget);
+ gtk_window_unfullscreen(GTK_WINDOW(m_pWindow));
+
+ if (!(m_nStyle & SalFrameStyleFlags::SIZEABLE))
+ {
+ // restore temp resizability
+ gtk_window_set_resizable(GTK_WINDOW(m_pWindow), false);
+ }
+ }
+
+ for (auto const& child : m_aChildren)
+ child->SetScreen(nNewScreen, SetType::RetainSize);
+
+ m_bDefaultPos = m_bDefaultSize = false;
+ updateScreenNumber();
+#endif
+}
+
+void GtkSalFrame::SetScreenNumber( unsigned int nNewScreen )
+{
+ SetScreen( nNewScreen, SetType::RetainSize );
+}
+
+void GtkSalFrame::updateWMClass()
+{
+ if (!DLSYM_GDK_IS_X11_DISPLAY(getGdkDisplay()))
+ return;
+
+ if (!gtk_widget_get_realized(m_pWindow))
+ return;
+
+ OString aResClass = OUStringToOString(m_sWMClass, RTL_TEXTENCODING_ASCII_US);
+ const char *pResClass = !aResClass.isEmpty() ? aResClass.getStr() :
+ SalGenericSystem::getFrameClassName();
+ XClassHint* pClass = XAllocClassHint();
+ OString aResName = SalGenericSystem::getFrameResName();
+ pClass->res_name = const_cast<char*>(aResName.getStr());
+ pClass->res_class = const_cast<char*>(pResClass);
+ Display *display = gdk_x11_display_get_xdisplay(getGdkDisplay());
+ XSetClassHint( display,
+ GtkSalFrame::GetNativeWindowHandle(m_pWindow),
+ pClass );
+ XFree( pClass );
+}
+
+void GtkSalFrame::SetApplicationID( const OUString &rWMClass )
+{
+ if( rWMClass != m_sWMClass && ! isChild() )
+ {
+ m_sWMClass = rWMClass;
+ updateWMClass();
+
+ for (auto const& child : m_aChildren)
+ child->SetApplicationID(rWMClass);
+ }
+}
+
+void GtkSalFrame::ShowFullScreen( bool bFullScreen, sal_Int32 nScreen )
+{
+ m_bFullscreen = bFullScreen;
+
+ if( !m_pWindow || isChild() )
+ return;
+
+ if( bFullScreen )
+ {
+ m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(m_pWindow));
+ SetScreen( nScreen, SetType::Fullscreen );
+ }
+ else
+ {
+ SetScreen( nScreen, SetType::UnFullscreen,
+ !m_aRestorePosSize.IsEmpty() ? &m_aRestorePosSize : nullptr );
+ m_aRestorePosSize = tools::Rectangle();
+ }
+}
+
+void GtkSalFrame::StartPresentation( bool bStart )
+{
+ std::optional<guint> aWindow;
+ std::optional<Display*> aDisplay;
+
+ bool bX11 = DLSYM_GDK_IS_X11_DISPLAY(getGdkDisplay());
+ if (bX11)
+ {
+ aWindow = GtkSalFrame::GetNativeWindowHandle(m_pWindow);
+ aDisplay = gdk_x11_display_get_xdisplay(getGdkDisplay());
+ }
+
+ m_ScreenSaverInhibitor.inhibit( bStart,
+ u"presentation",
+ bX11,
+ aWindow,
+ aDisplay );
+}
+
+void GtkSalFrame::SetAlwaysOnTop( bool bOnTop )
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( m_pWindow )
+ gtk_window_set_keep_above( GTK_WINDOW( m_pWindow ), bOnTop );
+#else
+ (void)bOnTop;
+#endif
+}
+
+static guint32 nLastUserInputTime = GDK_CURRENT_TIME;
+
+guint32 GtkSalFrame::GetLastInputEventTime()
+{
+ return nLastUserInputTime;
+}
+
+void GtkSalFrame::UpdateLastInputEventTime(guint32 nUserInputTime)
+{
+ //gtk3 can generate a synthetic crossing event with a useless 0
+ //(GDK_CURRENT_TIME) timestamp on showing a menu from the main
+ //menubar, which is unhelpful, so ignore the 0 timestamps
+ if (nUserInputTime == GDK_CURRENT_TIME)
+ return;
+ nLastUserInputTime = nUserInputTime;
+}
+
+void GtkSalFrame::ToTop( SalFrameToTop nFlags )
+{
+ if( !m_pWindow )
+ return;
+
+ if( isChild( false ) )
+ GrabFocus();
+ else if( gtk_widget_get_mapped( m_pWindow ) )
+ {
+ auto nTimestamp = GetLastInputEventTime();
+#ifdef GDK_WINDOWING_X11
+ GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ nTimestamp = gdk_x11_display_get_user_time(pDisplay);
+#endif
+ if (!(nFlags & SalFrameToTop::GrabFocusOnly))
+ gtk_window_present_with_time(GTK_WINDOW(m_pWindow), nTimestamp);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ else
+ gdk_window_focus(widget_get_surface(m_pWindow), nTimestamp);
+#endif
+ GrabFocus();
+ }
+ else
+ {
+ if( nFlags & SalFrameToTop::RestoreWhenMin )
+ gtk_window_present( GTK_WINDOW(m_pWindow) );
+ }
+}
+
+void GtkSalFrame::SetPointer( PointerStyle ePointerStyle )
+{
+ if( !m_pWindow || ePointerStyle == m_ePointerStyle )
+ return;
+
+ m_ePointerStyle = ePointerStyle;
+ GdkCursor *pCursor = getDisplay()->getCursor( ePointerStyle );
+ widget_set_cursor(GTK_WIDGET(m_pWindow), pCursor);
+}
+
+void GtkSalFrame::grabPointer( bool bGrab, bool bKeyboardAlso, bool bOwnerEvents )
+{
+ if (bGrab)
+ {
+ // tdf#135779 move focus back inside usual input window out of any
+ // other gtk widgets before grabbing the pointer
+ GrabFocus();
+ }
+
+ static const char* pEnv = getenv( "SAL_NO_MOUSEGRABS" );
+ if (pEnv && *pEnv)
+ return;
+
+ if (!m_pWindow)
+ return;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkSeat* pSeat = gdk_display_get_default_seat(getGdkDisplay());
+ if (bGrab)
+ {
+ GdkSeatCapabilities eCapability = bKeyboardAlso ? GDK_SEAT_CAPABILITY_ALL : GDK_SEAT_CAPABILITY_ALL_POINTING;
+ gdk_seat_grab(pSeat, widget_get_surface(getMouseEventWidget()), eCapability,
+ bOwnerEvents, nullptr, nullptr, nullptr, nullptr);
+ }
+ else
+ {
+ gdk_seat_ungrab(pSeat);
+ }
+#else
+ (void)bKeyboardAlso;
+ (void)bOwnerEvents;
+#endif
+}
+
+void GtkSalFrame::CaptureMouse( bool bCapture )
+{
+ getDisplay()->CaptureMouse( bCapture ? this : nullptr );
+}
+
+void GtkSalFrame::SetPointerPos( tools::Long nX, tools::Long nY )
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkSalFrame* pFrame = this;
+ while( pFrame && pFrame->isChild( false ) )
+ pFrame = pFrame->m_pParent;
+ if( ! pFrame )
+ return;
+
+ GdkScreen *pScreen = gtk_widget_get_screen(pFrame->m_pWindow);
+ GdkDisplay *pDisplay = gdk_screen_get_display( pScreen );
+
+ /* when the application tries to center the mouse in the dialog the
+ * window isn't mapped already. So use coordinates relative to the root window.
+ */
+ unsigned int nWindowLeft = maGeometry.nX + nX;
+ unsigned int nWindowTop = maGeometry.nY + nY;
+
+ GdkDeviceManager* pManager = gdk_display_get_device_manager(pDisplay);
+ gdk_device_warp(gdk_device_manager_get_client_pointer(pManager), pScreen, nWindowLeft, nWindowTop);
+
+ // #i38648# ask for the next motion hint
+ gint x, y;
+ GdkModifierType mask;
+ gdk_window_get_pointer( widget_get_surface(pFrame->m_pWindow) , &x, &y, &mask );
+#else
+ (void)nX;
+ (void)nY;
+#endif
+}
+
+void GtkSalFrame::Flush()
+{
+ gdk_display_flush( getGdkDisplay() );
+}
+
+void GtkSalFrame::KeyCodeToGdkKey(const vcl::KeyCode& rKeyCode,
+ guint* pGdkKeyCode, GdkModifierType *pGdkModifiers)
+{
+ if ( pGdkKeyCode == nullptr || pGdkModifiers == nullptr )
+ return;
+
+ // Get GDK key modifiers
+ GdkModifierType nModifiers = GdkModifierType(0);
+
+ if ( rKeyCode.IsShift() )
+ nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_SHIFT_MASK );
+
+ if ( rKeyCode.IsMod1() )
+ nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_CONTROL_MASK );
+
+ if ( rKeyCode.IsMod2() )
+ nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_ALT_MASK );
+
+ *pGdkModifiers = nModifiers;
+
+ // Get GDK keycode.
+ guint nKeyCode = 0;
+
+ guint nCode = rKeyCode.GetCode();
+
+ if ( nCode >= KEY_0 && nCode <= KEY_9 )
+ nKeyCode = ( nCode - KEY_0 ) + GDK_KEY_0;
+ else if ( nCode >= KEY_A && nCode <= KEY_Z )
+ nKeyCode = ( nCode - KEY_A ) + GDK_KEY_A;
+ else if ( nCode >= KEY_F1 && nCode <= KEY_F26 )
+ nKeyCode = ( nCode - KEY_F1 ) + GDK_KEY_F1;
+ else
+ {
+ switch (nCode)
+ {
+ case KEY_DOWN: nKeyCode = GDK_KEY_Down; break;
+ case KEY_UP: nKeyCode = GDK_KEY_Up; break;
+ case KEY_LEFT: nKeyCode = GDK_KEY_Left; break;
+ case KEY_RIGHT: nKeyCode = GDK_KEY_Right; break;
+ case KEY_HOME: nKeyCode = GDK_KEY_Home; break;
+ case KEY_END: nKeyCode = GDK_KEY_End; break;
+ case KEY_PAGEUP: nKeyCode = GDK_KEY_Page_Up; break;
+ case KEY_PAGEDOWN: nKeyCode = GDK_KEY_Page_Down; break;
+ case KEY_RETURN: nKeyCode = GDK_KEY_Return; break;
+ case KEY_ESCAPE: nKeyCode = GDK_KEY_Escape; break;
+ case KEY_TAB: nKeyCode = GDK_KEY_Tab; break;
+ case KEY_BACKSPACE: nKeyCode = GDK_KEY_BackSpace; break;
+ case KEY_SPACE: nKeyCode = GDK_KEY_space; break;
+ case KEY_INSERT: nKeyCode = GDK_KEY_Insert; break;
+ case KEY_DELETE: nKeyCode = GDK_KEY_Delete; break;
+ case KEY_ADD: nKeyCode = GDK_KEY_plus; break;
+ case KEY_SUBTRACT: nKeyCode = GDK_KEY_minus; break;
+ case KEY_MULTIPLY: nKeyCode = GDK_KEY_asterisk; break;
+ case KEY_DIVIDE: nKeyCode = GDK_KEY_slash; break;
+ case KEY_POINT: nKeyCode = GDK_KEY_period; break;
+ case KEY_COMMA: nKeyCode = GDK_KEY_comma; break;
+ case KEY_LESS: nKeyCode = GDK_KEY_less; break;
+ case KEY_GREATER: nKeyCode = GDK_KEY_greater; break;
+ case KEY_EQUAL: nKeyCode = GDK_KEY_equal; break;
+ case KEY_FIND: nKeyCode = GDK_KEY_Find; break;
+ case KEY_CONTEXTMENU: nKeyCode = GDK_KEY_Menu; break;
+ case KEY_HELP: nKeyCode = GDK_KEY_Help; break;
+ case KEY_UNDO: nKeyCode = GDK_KEY_Undo; break;
+ case KEY_REPEAT: nKeyCode = GDK_KEY_Redo; break;
+ case KEY_DECIMAL: nKeyCode = GDK_KEY_KP_Decimal; break;
+ case KEY_TILDE: nKeyCode = GDK_KEY_asciitilde; break;
+ case KEY_QUOTELEFT: nKeyCode = GDK_KEY_quoteleft; break;
+ case KEY_BRACKETLEFT: nKeyCode = GDK_KEY_bracketleft; break;
+ case KEY_BRACKETRIGHT: nKeyCode = GDK_KEY_bracketright; break;
+ case KEY_SEMICOLON: nKeyCode = GDK_KEY_semicolon; break;
+ case KEY_QUOTERIGHT: nKeyCode = GDK_KEY_quoteright; break;
+
+ // Special cases
+ case KEY_COPY: nKeyCode = GDK_KEY_Copy; break;
+ case KEY_CUT: nKeyCode = GDK_KEY_Cut; break;
+ case KEY_PASTE: nKeyCode = GDK_KEY_Paste; break;
+ case KEY_OPEN: nKeyCode = GDK_KEY_Open; break;
+ }
+ }
+
+ *pGdkKeyCode = nKeyCode;
+}
+
+OUString GtkSalFrame::GetKeyName( sal_uInt16 nKeyCode )
+{
+ guint nGtkKeyCode;
+ GdkModifierType nGtkModifiers;
+ KeyCodeToGdkKey(nKeyCode, &nGtkKeyCode, &nGtkModifiers );
+
+ gchar* pName = gtk_accelerator_get_label(nGtkKeyCode, nGtkModifiers);
+ OUString aRet(pName, rtl_str_getLength(pName), RTL_TEXTENCODING_UTF8);
+ g_free(pName);
+ return aRet;
+}
+
+GdkDisplay *GtkSalFrame::getGdkDisplay()
+{
+ return GetGtkSalData()->GetGdkDisplay();
+}
+
+GtkSalDisplay *GtkSalFrame::getDisplay()
+{
+ return GetGtkSalData()->GetGtkDisplay();
+}
+
+SalFrame::SalPointerState GtkSalFrame::GetPointerState()
+{
+ SalPointerState aState;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkScreen* pScreen;
+ gint x, y;
+ GdkModifierType aMask;
+ gdk_display_get_pointer( getGdkDisplay(), &pScreen, &x, &y, &aMask );
+ aState.maPos = Point( x - maGeometry.nX, y - maGeometry.nY );
+ aState.mnState = GetMouseModCode( aMask );
+#endif
+ return aState;
+}
+
+KeyIndicatorState GtkSalFrame::GetIndicatorState()
+{
+ KeyIndicatorState nState = KeyIndicatorState::NONE;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkKeymap *pKeyMap = gdk_keymap_get_for_display(getGdkDisplay());
+
+ if (gdk_keymap_get_caps_lock_state(pKeyMap))
+ nState |= KeyIndicatorState::CAPSLOCK;
+ if (gdk_keymap_get_num_lock_state(pKeyMap))
+ nState |= KeyIndicatorState::NUMLOCK;
+ if (gdk_keymap_get_scroll_lock_state(pKeyMap))
+ nState |= KeyIndicatorState::SCROLLLOCK;
+#endif
+
+ return nState;
+}
+
+void GtkSalFrame::SimulateKeyPress( sal_uInt16 nKeyCode )
+{
+ g_warning ("missing simulate keypress %d", nKeyCode);
+}
+
+void GtkSalFrame::SetInputContext( SalInputContext* pContext )
+{
+ if( ! pContext )
+ return;
+
+ if( ! (pContext->mnOptions & InputContextFlags::Text) )
+ return;
+
+ // create a new im context
+ if( ! m_pIMHandler )
+ m_pIMHandler.reset( new IMHandler( this ) );
+}
+
+void GtkSalFrame::EndExtTextInput( EndExtTextInputFlags nFlags )
+{
+ if( m_pIMHandler )
+ m_pIMHandler->endExtTextInput( nFlags );
+}
+
+bool GtkSalFrame::MapUnicodeToKeyCode( sal_Unicode , LanguageType , vcl::KeyCode& )
+{
+ // not supported yet
+ return false;
+}
+
+LanguageType GtkSalFrame::GetInputLanguage()
+{
+ return LANGUAGE_DONTKNOW;
+}
+
+void GtkSalFrame::UpdateSettings( AllSettings& rSettings )
+{
+ if( ! m_pWindow )
+ return;
+
+ GtkSalGraphics* pGraphics = m_pGraphics.get();
+ bool bFreeGraphics = false;
+ if( ! pGraphics )
+ {
+ pGraphics = static_cast<GtkSalGraphics*>(AcquireGraphics());
+ if ( !pGraphics )
+ {
+ SAL_WARN("vcl.gtk3", "Could not get graphics - unable to update settings");
+ return;
+ }
+ bFreeGraphics = true;
+ }
+
+ pGraphics->UpdateSettings( rSettings );
+
+ if( bFreeGraphics )
+ ReleaseGraphics( pGraphics );
+}
+
+void GtkSalFrame::Beep()
+{
+ gdk_display_beep( getGdkDisplay() );
+}
+
+const SystemEnvData* GtkSalFrame::GetSystemData() const
+{
+ return &m_aSystemData;
+}
+
+void GtkSalFrame::ResolveWindowHandle(SystemEnvData& rData) const
+{
+ if (!rData.pWidget)
+ return;
+ SAL_WARN("vcl.gtk3", "its undesirable to need the NativeWindowHandle, see tdf#139609");
+ rData.SetWindowHandle(GetNativeWindowHandle(static_cast<GtkWidget*>(rData.pWidget)));
+}
+
+void GtkSalFrame::SetParent( SalFrame* pNewParent )
+{
+ GtkWindow* pWindow = GTK_IS_WINDOW(m_pWindow) ? GTK_WINDOW(m_pWindow) : nullptr;
+ if (m_pParent)
+ {
+ if (pWindow && GTK_IS_WINDOW(m_pParent->m_pWindow))
+ gtk_window_group_remove_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), pWindow);
+ m_pParent->m_aChildren.remove(this);
+ }
+ m_pParent = static_cast<GtkSalFrame*>(pNewParent);
+ if (m_pParent)
+ {
+ m_pParent->m_aChildren.push_back(this);
+ if (pWindow && GTK_IS_WINDOW(m_pParent->m_pWindow))
+ gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), pWindow);
+ }
+ if (!isChild() && pWindow)
+ gtk_window_set_transient_for( pWindow,
+ (m_pParent && ! m_pParent->isChild(true,false)) ? GTK_WINDOW(m_pParent->m_pWindow) : nullptr
+ );
+}
+
+void GtkSalFrame::SetPluginParent( SystemParentData* )
+{
+ //FIXME: no SetPluginParent impl. for gtk3
+}
+
+void GtkSalFrame::ResetClipRegion()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( m_pWindow )
+ gdk_window_shape_combine_region( widget_get_surface( m_pWindow ), nullptr, 0, 0 );
+#endif
+}
+
+void GtkSalFrame::BeginSetClipRegion( sal_uInt32 )
+{
+ if( m_pRegion )
+ cairo_region_destroy( m_pRegion );
+ m_pRegion = cairo_region_create();
+}
+
+void GtkSalFrame::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ if( m_pRegion )
+ {
+ GdkRectangle aRect;
+ aRect.x = nX;
+ aRect.y = nY;
+ aRect.width = nWidth;
+ aRect.height = nHeight;
+ cairo_region_union_rectangle( m_pRegion, &aRect );
+ }
+}
+
+void GtkSalFrame::EndSetClipRegion()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( m_pWindow && m_pRegion )
+ gdk_window_shape_combine_region( widget_get_surface(m_pWindow), m_pRegion, 0, 0 );
+#endif
+}
+
+void GtkSalFrame::PositionByToolkit(const tools::Rectangle& rRect, FloatWinPopupFlags nFlags)
+{
+ if (ImplGetSVData()->maNWFData.mbCanDetermineWindowPosition)
+ return;
+
+ m_aFloatRect = rRect;
+ m_nFloatFlags = nFlags;
+ m_bFloatPositioned = true;
+}
+
+void GtkSalFrame::SetModal(bool bModal)
+{
+ if (!m_pWindow)
+ return;
+ gtk_window_set_modal(GTK_WINDOW(m_pWindow), bModal);
+}
+
+bool GtkSalFrame::GetModal() const
+{
+ if (!m_pWindow)
+ return false;
+ return gtk_window_get_modal(GTK_WINDOW(m_pWindow));
+}
+
+gboolean GtkSalFrame::signalTooltipQuery(GtkWidget*, gint /*x*/, gint /*y*/,
+ gboolean /*keyboard_mode*/, GtkTooltip *tooltip,
+ gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (pThis->m_aTooltip.isEmpty() || pThis->m_bTooltipBlocked)
+ return false;
+ gtk_tooltip_set_text(tooltip,
+ OUStringToOString(pThis->m_aTooltip, RTL_TEXTENCODING_UTF8).getStr());
+ GdkRectangle aHelpArea;
+ aHelpArea.x = pThis->m_aHelpArea.Left();
+ aHelpArea.y = pThis->m_aHelpArea.Top();
+ aHelpArea.width = pThis->m_aHelpArea.GetWidth();
+ aHelpArea.height = pThis->m_aHelpArea.GetHeight();
+ if (AllSettings::GetLayoutRTL())
+ aHelpArea.x = pThis->maGeometry.nWidth-aHelpArea.width-1-aHelpArea.x;
+ gtk_tooltip_set_tip_area(tooltip, &aHelpArea);
+ return true;
+}
+
+bool GtkSalFrame::ShowTooltip(const OUString& rHelpText, const tools::Rectangle& rHelpArea)
+{
+ m_aTooltip = rHelpText;
+ m_aHelpArea = rHelpArea;
+ gtk_widget_trigger_tooltip_query(getMouseEventWidget());
+ return true;
+}
+
+void GtkSalFrame::BlockTooltip()
+{
+ m_bTooltipBlocked = true;
+}
+
+void GtkSalFrame::UnblockTooltip()
+{
+ m_bTooltipBlocked = false;
+}
+
+void GtkSalFrame::HideTooltip()
+{
+ m_aTooltip.clear();
+ GtkWidget* pEventWidget = getMouseEventWidget();
+ gtk_widget_trigger_tooltip_query(pEventWidget);
+}
+
+namespace
+{
+ void set_pointing_to(GtkPopover *pPopOver, vcl::Window* pParent, const tools::Rectangle& rHelpArea, const SalFrameGeometry& rGeometry)
+ {
+ GdkRectangle aRect;
+ aRect.x = FloatingWindow::ImplConvertToAbsPos(pParent, rHelpArea).Left() - rGeometry.nX;
+ aRect.y = rHelpArea.Top();
+ aRect.width = 1;
+ aRect.height = 1;
+
+ GtkPositionType ePos = gtk_popover_get_position(pPopOver);
+ switch (ePos)
+ {
+ case GTK_POS_BOTTOM:
+ case GTK_POS_TOP:
+ aRect.width = rHelpArea.GetWidth();
+ break;
+ case GTK_POS_RIGHT:
+ case GTK_POS_LEFT:
+ aRect.height = rHelpArea.GetHeight();
+ break;
+ }
+
+ gtk_popover_set_pointing_to(pPopOver, &aRect);
+ }
+}
+
+void* GtkSalFrame::ShowPopover(const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea, QuickHelpFlags nFlags)
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget *pWidget = gtk_popover_new(getMouseEventWidget());
+#else
+ GtkWidget *pWidget = gtk_popover_new();
+ gtk_widget_set_parent(pWidget, getMouseEventWidget());
+#endif
+ OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
+ GtkWidget *pLabel = gtk_label_new(sUTF.getStr());
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(pWidget), pLabel);
+#else
+ gtk_popover_set_child(GTK_POPOVER(pWidget), pLabel);
+#endif
+
+ if (nFlags & QuickHelpFlags::Top)
+ gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_BOTTOM);
+ else if (nFlags & QuickHelpFlags::Bottom)
+ gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_TOP);
+ else if (nFlags & QuickHelpFlags::Left)
+ gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_RIGHT);
+ else if (nFlags & QuickHelpFlags::Right)
+ gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_LEFT);
+
+ set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_popover_set_modal(GTK_POPOVER(pWidget), false);
+#else
+ gtk_popover_set_autohide(GTK_POPOVER(pWidget), false);
+#endif
+
+ gtk_widget_show(pLabel);
+ gtk_widget_show(pWidget);
+
+ return pWidget;
+}
+
+bool GtkSalFrame::UpdatePopover(void* nId, const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea)
+{
+ GtkWidget *pWidget = static_cast<GtkWidget*>(nId);
+
+ set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget *pLabel = gtk_bin_get_child(GTK_BIN(pWidget));
+#else
+ GtkWidget *pLabel = gtk_popover_get_child(GTK_POPOVER(pWidget));
+#endif
+ OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
+ gtk_label_set_text(GTK_LABEL(pLabel), sUTF.getStr());
+
+ return true;
+}
+
+bool GtkSalFrame::HidePopover(void* nId)
+{
+ GtkWidget *pWidget = static_cast<GtkWidget*>(nId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(pWidget);
+#else
+ g_clear_pointer(&pWidget, gtk_widget_unparent);
+#endif
+ return true;
+}
+
+void GtkSalFrame::addGrabLevel()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (m_nGrabLevel == 0)
+ gtk_grab_add(getMouseEventWidget());
+#endif
+ ++m_nGrabLevel;
+}
+
+void GtkSalFrame::removeGrabLevel()
+{
+ if (m_nGrabLevel > 0)
+ {
+ --m_nGrabLevel;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (m_nGrabLevel == 0)
+ gtk_grab_remove(getMouseEventWidget());
+#endif
+ }
+}
+
+void GtkSalFrame::closePopup()
+{
+ if (!m_nFloats)
+ return;
+ ImplSVData* pSVData = ImplGetSVData();
+ if (!pSVData->mpWinData->mpFirstFloat)
+ return;
+ if (pSVData->mpWinData->mpFirstFloat->ImplGetFrame() != this)
+ return;
+ pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll);
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+namespace
+{
+ //tdf#117981 translate embedded video window mouse events to parent coordinates
+ void translate_coords(GdkWindow* pSourceWindow, GtkWidget* pTargetWidget, int& rEventX, int& rEventY)
+ {
+ gpointer user_data=nullptr;
+ gdk_window_get_user_data(pSourceWindow, &user_data);
+ GtkWidget* pRealEventWidget = static_cast<GtkWidget*>(user_data);
+ if (pRealEventWidget)
+ {
+ gtk_coord fX(0.0), fY(0.0);
+ gtk_widget_translate_coordinates(pRealEventWidget, pTargetWidget, rEventX, rEventY, &fX, &fY);
+ rEventX = fX;
+ rEventY = fY;
+ }
+ }
+}
+#endif
+
+void GtkSalFrame::GrabFocus()
+{
+ GtkWidget* pGrabWidget;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_EVENT_BOX(m_pWindow))
+ pGrabWidget = GTK_WIDGET(m_pWindow);
+ else
+ pGrabWidget = GTK_WIDGET(m_pFixedContainer);
+ // m_nSetFocusSignalId is 0 for the DisallowCycleFocusOut case where
+ // we don't allow focus to enter the toplevel, but expect it to
+ // stay in some embedded native gtk widget
+ if (!gtk_widget_get_can_focus(pGrabWidget) && m_nSetFocusSignalId)
+ gtk_widget_set_can_focus(pGrabWidget, true);
+#else
+ pGrabWidget = GTK_WIDGET(m_pFixedContainer);
+#endif
+ if (!gtk_widget_has_focus(pGrabWidget))
+ {
+ gtk_widget_grab_focus(pGrabWidget);
+ if (m_pIMHandler)
+ m_pIMHandler->focusChanged(true);
+ }
+}
+
+bool GtkSalFrame::DrawingAreaButton(SalEvent nEventType, int nEventX, int nEventY, int nButton, guint32 nTime, guint nState)
+{
+ UpdateLastInputEventTime(nTime);
+
+ SalMouseEvent aEvent;
+ switch(nButton)
+ {
+ case 1: aEvent.mnButton = MOUSE_LEFT; break;
+ case 2: aEvent.mnButton = MOUSE_MIDDLE; break;
+ case 3: aEvent.mnButton = MOUSE_RIGHT; break;
+ default: return false;
+ }
+
+ aEvent.mnTime = nTime;
+ aEvent.mnX = nEventX;
+ aEvent.mnY = nEventY;
+ aEvent.mnCode = GetMouseModCode(nState);
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = maGeometry.nWidth-1-aEvent.mnX;
+
+ CallCallbackExc(nEventType, &aEvent);
+
+ return true;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalFrame::signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ GtkWidget* pEventWidget = pThis->getMouseEventWidget();
+ bool bDifferentEventWindow = pEvent->window != widget_get_surface(pEventWidget);
+
+ if (pEvent->type == GDK_BUTTON_PRESS)
+ {
+ // tdf#120764 It isn't allowed under wayland to have two visible popups that share
+ // the same top level parent. The problem is that since gtk 3.24 tooltips are also
+ // implemented as popups, which means that we cannot show any popup if there is a
+ // visible tooltip. In fact, gtk will hide the tooltip by itself after this handler,
+ // in case of a button press event. But if we intend to show a popup on button press
+ // it will be too late, so just do it here:
+ pThis->HideTooltip();
+
+ // focus on click
+ if (!bDifferentEventWindow)
+ pThis->GrabFocus();
+ }
+
+ SalEvent nEventType = SalEvent::NONE;
+ switch( pEvent->type )
+ {
+ case GDK_BUTTON_PRESS:
+ nEventType = SalEvent::MouseButtonDown;
+ break;
+ case GDK_BUTTON_RELEASE:
+ nEventType = SalEvent::MouseButtonUp;
+ break;
+ default:
+ return false;
+ }
+
+ vcl::DeletionListener aDel( pThis );
+
+ if (pThis->isFloatGrabWindow())
+ {
+ //rhbz#1505379 if the window that got the event isn't our one, or there's none
+ //of our windows under the mouse then close this popup window
+ if (bDifferentEventWindow ||
+ gdk_device_get_window_at_position(pEvent->device, nullptr, nullptr) == nullptr)
+ {
+ if (pEvent->type == GDK_BUTTON_PRESS)
+ pThis->closePopup();
+ else if (pEvent->type == GDK_BUTTON_RELEASE)
+ return true;
+ }
+ }
+
+ int nEventX = pEvent->x;
+ int nEventY = pEvent->y;
+
+ if (bDifferentEventWindow)
+ translate_coords(pEvent->window, pEventWidget, nEventX, nEventY);
+
+ if (!aDel.isDeleted() && !(pThis->m_nStyle & SalFrameStyleFlags::SYSTEMCHILD))
+ {
+ int frame_x = static_cast<int>(pEvent->x_root - nEventX);
+ int frame_y = static_cast<int>(pEvent->y_root - nEventY);
+ if (pThis->m_bGeometryIsProvisional || frame_x != pThis->maGeometry.nX || frame_y != pThis->maGeometry.nY)
+ {
+ pThis->m_bGeometryIsProvisional = false;
+ pThis->maGeometry.nX = frame_x;
+ pThis->maGeometry.nY = frame_y;
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->maNWFData.mbCanDetermineWindowPosition)
+ pThis->CallCallbackExc(SalEvent::Move, nullptr);
+ }
+ }
+
+ bool bRet = false;
+ if (!aDel.isDeleted())
+ {
+ bRet = pThis->DrawingAreaButton(nEventType,
+ nEventX,
+ nEventY,
+ pEvent->button,
+ pEvent->time,
+ pEvent->state);
+ }
+
+ return bRet;
+}
+#else
+void GtkSalFrame::gesturePressed(GtkGestureClick* pGesture, int /*n_press*/, gdouble x, gdouble y, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->gestureButton(pGesture, SalEvent::MouseButtonDown, x, y);
+}
+
+void GtkSalFrame::gestureReleased(GtkGestureClick* pGesture, int /*n_press*/, gdouble x, gdouble y, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->gestureButton(pGesture, SalEvent::MouseButtonUp, x, y);
+}
+
+void GtkSalFrame::gestureButton(GtkGestureClick* pGesture, SalEvent nEventType, gdouble x, gdouble y)
+{
+ GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pGesture));
+ GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pGesture));
+ int nButton = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(pGesture));
+ DrawingAreaButton(nEventType, x, y, nButton, gdk_event_get_time(pEvent), eType);
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::LaunchAsyncScroll(GdkEvent const * pEvent)
+{
+ //if we don't match previous pending states, flush that queue now
+ if (!m_aPendingScrollEvents.empty() && pEvent->scroll.state != m_aPendingScrollEvents.back()->scroll.state)
+ {
+ m_aSmoothScrollIdle.Stop();
+ m_aSmoothScrollIdle.Invoke();
+ assert(m_aPendingScrollEvents.empty());
+ }
+ //add scroll event to queue
+ m_aPendingScrollEvents.push_back(gdk_event_copy(pEvent));
+ if (!m_aSmoothScrollIdle.IsActive())
+ m_aSmoothScrollIdle.Start();
+}
+#endif
+
+void GtkSalFrame::DrawingAreaScroll(double delta_x, double delta_y, int nEventX, int nEventY, guint32 nTime, guint nState)
+{
+ SalWheelMouseEvent aEvent;
+
+ aEvent.mnTime = nTime;
+ aEvent.mnX = nEventX;
+ // --- RTL --- (mirror mouse pos)
+ if (AllSettings::GetLayoutRTL())
+ aEvent.mnX = maGeometry.nWidth - 1 - aEvent.mnX;
+ aEvent.mnY = nEventY;
+ aEvent.mnCode = GetMouseModCode(nState);
+
+ // rhbz#1344042 "Traditionally" in gtk3 we tool a single up/down event as
+ // equating to 3 scroll lines and a delta of 120. So scale the delta here
+ // by 120 where a single mouse wheel click is an incoming delta_x of 1
+ // and divide that by 40 to get the number of scroll lines
+ if (delta_x != 0.0)
+ {
+ aEvent.mnDelta = -delta_x * 120;
+ aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
+ if (aEvent.mnDelta == 0)
+ aEvent.mnDelta = aEvent.mnNotchDelta;
+ aEvent.mbHorz = true;
+ aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0;
+ CallCallbackExc(SalEvent::WheelMouse, &aEvent);
+ }
+
+ if (delta_y != 0.0)
+ {
+ aEvent.mnDelta = -delta_y * 120;
+ aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
+ if (aEvent.mnDelta == 0)
+ aEvent.mnDelta = aEvent.mnNotchDelta;
+ aEvent.mbHorz = false;
+ aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0;
+ CallCallbackExc(SalEvent::WheelMouse, &aEvent);
+ }
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+IMPL_LINK_NOARG(GtkSalFrame, AsyncScroll, Timer *, void)
+{
+ assert(!m_aPendingScrollEvents.empty());
+
+ GdkEvent* pEvent = m_aPendingScrollEvents.back();
+ auto nEventX = pEvent->scroll.x;
+ auto nEventY = pEvent->scroll.y;
+ auto nTime = pEvent->scroll.time;
+ auto nState = pEvent->scroll.state;
+
+ double delta_x(0.0), delta_y(0.0);
+ for (auto pSubEvent : m_aPendingScrollEvents)
+ {
+ delta_x += pSubEvent->scroll.delta_x;
+ delta_y += pSubEvent->scroll.delta_y;
+ gdk_event_free(pSubEvent);
+ }
+ m_aPendingScrollEvents.clear();
+
+ DrawingAreaScroll(delta_x, delta_y, nEventX, nEventY, nTime, nState);
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+SalWheelMouseEvent GtkSalFrame::GetWheelEvent(const GdkEventScroll& rEvent)
+{
+ SalWheelMouseEvent aEvent;
+
+ aEvent.mnTime = rEvent.time;
+ aEvent.mnX = static_cast<sal_uLong>(rEvent.x);
+ aEvent.mnY = static_cast<sal_uLong>(rEvent.y);
+ aEvent.mnCode = GetMouseModCode(rEvent.state);
+
+ switch (rEvent.direction)
+ {
+ case GDK_SCROLL_UP:
+ aEvent.mnDelta = 120;
+ aEvent.mnNotchDelta = 1;
+ aEvent.mnScrollLines = 3;
+ aEvent.mbHorz = false;
+ break;
+
+ case GDK_SCROLL_DOWN:
+ aEvent.mnDelta = -120;
+ aEvent.mnNotchDelta = -1;
+ aEvent.mnScrollLines = 3;
+ aEvent.mbHorz = false;
+ break;
+
+ case GDK_SCROLL_LEFT:
+ aEvent.mnDelta = 120;
+ aEvent.mnNotchDelta = 1;
+ aEvent.mnScrollLines = 3;
+ aEvent.mbHorz = true;
+ break;
+
+ case GDK_SCROLL_RIGHT:
+ aEvent.mnDelta = -120;
+ aEvent.mnNotchDelta = -1;
+ aEvent.mnScrollLines = 3;
+ aEvent.mbHorz = true;
+ break;
+ default:
+ break;
+ }
+
+ return aEvent;
+}
+
+gboolean GtkSalFrame::signalScroll(GtkWidget*, GdkEvent* pInEvent, gpointer frame)
+{
+ GdkEventScroll& rEvent = pInEvent->scroll;
+
+ UpdateLastInputEventTime(rEvent.time);
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ if (rEvent.direction == GDK_SCROLL_SMOOTH)
+ {
+ pThis->LaunchAsyncScroll(pInEvent);
+ return true;
+ }
+
+ //if we have smooth scrolling previous pending states, flush that queue now
+ if (!pThis->m_aPendingScrollEvents.empty())
+ {
+ pThis->m_aSmoothScrollIdle.Stop();
+ pThis->m_aSmoothScrollIdle.Invoke();
+ assert(pThis->m_aPendingScrollEvents.empty());
+ }
+
+ SalWheelMouseEvent aEvent(GetWheelEvent(rEvent));
+
+ // --- RTL --- (mirror mouse pos)
+ if (AllSettings::GetLayoutRTL())
+ aEvent.mnX = pThis->maGeometry.nWidth - 1 - aEvent.mnX;
+
+ pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
+
+ return true;
+}
+#else
+gboolean GtkSalFrame::signalScroll(GtkEventControllerScroll* pController, double delta_x, double delta_y, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
+ GdkModifierType eState = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
+
+ auto nTime = gdk_event_get_time(pEvent);
+
+ UpdateLastInputEventTime(nTime);
+
+ double nEventX(0.0), nEventY(0.0);
+ gdk_event_get_position(pEvent, &nEventX, &nEventY);
+
+ pThis->DrawingAreaScroll(delta_x, delta_y, nEventX, nEventY, nTime, eState);
+
+ return true;
+}
+
+#endif
+
+void GtkSalFrame::gestureSwipe(GtkGestureSwipe* gesture, gdouble velocity_x, gdouble velocity_y, gpointer frame)
+{
+ gdouble x, y;
+ GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
+ //I feel I want the first point of the sequence, not the last point which
+ //the docs say this gives, but for the moment assume we start and end
+ //within the same vcl window
+ if (gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y))
+ {
+ SalSwipeEvent aEvent;
+ aEvent.mnVelocityX = velocity_x;
+ aEvent.mnVelocityY = velocity_y;
+ aEvent.mnX = x;
+ aEvent.mnY = y;
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->CallCallbackExc(SalEvent::Swipe, &aEvent);
+ }
+}
+
+void GtkSalFrame::gestureLongPress(GtkGestureLongPress* gesture, gdouble x, gdouble y, gpointer frame)
+{
+ GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
+ if (gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y))
+ {
+ SalLongPressEvent aEvent;
+ aEvent.mnX = x;
+ aEvent.mnY = y;
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->CallCallbackExc(SalEvent::LongPress, &aEvent);
+ }
+}
+
+void GtkSalFrame::DrawingAreaMotion(int nEventX, int nEventY, guint32 nTime, guint nState)
+{
+ UpdateLastInputEventTime(nTime);
+
+ SalMouseEvent aEvent;
+ aEvent.mnTime = nTime;
+ aEvent.mnX = nEventX;
+ aEvent.mnY = nEventY;
+ aEvent.mnCode = GetMouseModCode(nState);
+ aEvent.mnButton = 0;
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = maGeometry.nWidth-1-aEvent.mnX;
+
+ CallCallbackExc(SalEvent::MouseMove, &aEvent);
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalMotion(GtkEventControllerMotion *pController, double x, double y, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
+ GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
+ pThis->DrawingAreaMotion(x, y, gdk_event_get_time(pEvent), eType);
+}
+#else
+gboolean GtkSalFrame::signalMotion( GtkWidget*, GdkEventMotion* pEvent, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ GtkWidget* pEventWidget = pThis->getMouseEventWidget();
+ bool bDifferentEventWindow = pEvent->window != widget_get_surface(pEventWidget);
+
+ //If a menu, e.g. font name dropdown, is open, then under wayland moving the
+ //mouse in the top left corner of the toplevel window in a
+ //0,0,float-width,float-height area generates motion events which are
+ //delivered to the dropdown
+ if (pThis->isFloatGrabWindow() && bDifferentEventWindow)
+ return true;
+
+ vcl::DeletionListener aDel( pThis );
+
+ int nEventX = pEvent->x;
+ int nEventY = pEvent->y;
+
+ if (bDifferentEventWindow)
+ translate_coords(pEvent->window, pEventWidget, nEventX, nEventY);
+
+ int frame_x = static_cast<int>(pEvent->x_root - nEventX);
+ int frame_y = static_cast<int>(pEvent->y_root - nEventY);
+
+ if (!aDel.isDeleted() && !(pThis->m_nStyle & SalFrameStyleFlags::SYSTEMCHILD))
+ {
+ if (pThis->m_bGeometryIsProvisional || frame_x != pThis->maGeometry.nX || frame_y != pThis->maGeometry.nY)
+ {
+ pThis->m_bGeometryIsProvisional = false;
+ pThis->maGeometry.nX = frame_x;
+ pThis->maGeometry.nY = frame_y;
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->maNWFData.mbCanDetermineWindowPosition)
+ pThis->CallCallbackExc(SalEvent::Move, nullptr);
+ }
+ }
+
+ if (!aDel.isDeleted())
+ {
+ pThis->DrawingAreaMotion(nEventX, nEventY, pEvent->time, pEvent->state);
+ }
+
+ if (!aDel.isDeleted())
+ {
+ // ask for the next hint
+ gint x, y;
+ GdkModifierType mask;
+ gdk_window_get_pointer( widget_get_surface(GTK_WIDGET(pThis->m_pWindow)), &x, &y, &mask );
+ }
+
+ return true;
+}
+#endif
+
+void GtkSalFrame::DrawingAreaCrossing(SalEvent nEventType, int nEventX, int nEventY, guint32 nTime, guint nState)
+{
+ UpdateLastInputEventTime(nTime);
+
+ SalMouseEvent aEvent;
+ aEvent.mnTime = nTime;
+ aEvent.mnX = nEventX;
+ aEvent.mnY = nEventY;
+ aEvent.mnCode = GetMouseModCode(nState);
+ aEvent.mnButton = 0;
+
+ if (AllSettings::GetLayoutRTL())
+ aEvent.mnX = maGeometry.nWidth-1-aEvent.mnX;
+
+ CallCallbackExc(nEventType, &aEvent);
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalEnter(GtkEventControllerMotion *pController, double x, double y, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
+ GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
+ pThis->DrawingAreaCrossing(SalEvent::MouseMove, x, y, pEvent ? gdk_event_get_time(pEvent) : GDK_CURRENT_TIME, eType);
+}
+
+void GtkSalFrame::signalLeave(GtkEventControllerMotion *pController, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
+ GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
+ pThis->DrawingAreaCrossing(SalEvent::MouseLeave, -1, -1, pEvent ? gdk_event_get_time(pEvent) : GDK_CURRENT_TIME, eType);
+}
+#else
+gboolean GtkSalFrame::signalCrossing( GtkWidget*, GdkEventCrossing* pEvent, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->DrawingAreaCrossing((pEvent->type == GDK_ENTER_NOTIFY) ? SalEvent::MouseMove : SalEvent::MouseLeave,
+ pEvent->x,
+ pEvent->y,
+ pEvent->time,
+ pEvent->state);
+ return true;
+}
+#endif
+
+cairo_t* GtkSalFrame::getCairoContext() const
+{
+ cairo_t* cr = cairo_create(m_pSurface);
+ assert(cr);
+ return cr;
+}
+
+void GtkSalFrame::damaged(sal_Int32 nExtentsX, sal_Int32 nExtentsY,
+ sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight) const
+{
+#if OSL_DEBUG_LEVEL > 0
+ if (dumpframes)
+ {
+ static int frame;
+ OString tmp("/tmp/frame" + OString::number(frame++) + ".png");
+ cairo_t* cr = getCairoContext();
+ cairo_surface_write_to_png(cairo_get_target(cr), tmp.getStr());
+ cairo_destroy(cr);
+ }
+#endif
+
+ // quite a bit of noise in RTL mode with negative widths
+ if (nExtentsWidth <= 0 || nExtentsHeight <= 0)
+ return;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_queue_draw_area(GTK_WIDGET(m_pDrawingArea),
+ nExtentsX, nExtentsY,
+ nExtentsWidth, nExtentsHeight);
+#else
+ gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
+ (void)nExtentsX;
+ (void)nExtentsY;
+#endif
+}
+
+// blit our backing cairo surface to the target cairo context
+void GtkSalFrame::DrawingAreaDraw(cairo_t *cr)
+{
+ cairo_set_source_surface(cr, m_pSurface, 0, 0);
+ cairo_paint(cr);
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalFrame::signalDraw(GtkWidget*, cairo_t *cr, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->DrawingAreaDraw(cr);
+ return false;
+}
+#else
+void GtkSalFrame::signalDraw(GtkDrawingArea*, cairo_t *cr, int /*width*/, int /*height*/, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->DrawingAreaDraw(cr);
+}
+#endif
+
+void GtkSalFrame::DrawingAreaResized(GtkWidget* pWidget, int nWidth, int nHeight)
+{
+ // ignore size-allocations that occur during configuring an embedded SalObject
+ if (m_bSalObjectSetPosSize)
+ return;
+ maGeometry.nWidth = nWidth;
+ maGeometry.nHeight = nHeight;
+ bool bRealized = gtk_widget_get_realized(pWidget);
+ if (bRealized)
+ AllocateFrame();
+ CallCallbackExc( SalEvent::Resize, nullptr );
+ if (bRealized)
+ TriggerPaintEvent();
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, GdkRectangle *pAllocation, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->DrawingAreaResized(pWidget, pAllocation->width, pAllocation->height);
+}
+#else
+void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, int nWidth, int nHeight, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->DrawingAreaResized(pWidget, nWidth, nHeight);
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+namespace {
+
+void swapDirection(GdkGravity& gravity)
+{
+ if (gravity == GDK_GRAVITY_NORTH_WEST)
+ gravity = GDK_GRAVITY_NORTH_EAST;
+ else if (gravity == GDK_GRAVITY_NORTH_EAST)
+ gravity = GDK_GRAVITY_NORTH_WEST;
+ else if (gravity == GDK_GRAVITY_SOUTH_WEST)
+ gravity = GDK_GRAVITY_SOUTH_EAST;
+ else if (gravity == GDK_GRAVITY_SOUTH_EAST)
+ gravity = GDK_GRAVITY_SOUTH_WEST;
+}
+
+}
+#endif
+
+void GtkSalFrame::signalRealize(GtkWidget*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->AllocateFrame();
+ if (pThis->m_bSalObjectSetPosSize)
+ return;
+ pThis->TriggerPaintEvent();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!pThis->m_bFloatPositioned)
+ return;
+
+ static auto window_move_to_rect = reinterpret_cast<void (*) (GdkWindow*, const GdkRectangle*, GdkGravity,
+ GdkGravity, GdkAnchorHints, gint, gint)>(
+ dlsym(nullptr, "gdk_window_move_to_rect"));
+ if (!window_move_to_rect)
+ return;
+
+ GdkGravity rect_anchor = GDK_GRAVITY_SOUTH_WEST, menu_anchor = GDK_GRAVITY_NORTH_WEST;
+
+ if (pThis->m_nFloatFlags & FloatWinPopupFlags::Left)
+ {
+ rect_anchor = GDK_GRAVITY_NORTH_WEST;
+ menu_anchor = GDK_GRAVITY_NORTH_EAST;
+ }
+ else if (pThis->m_nFloatFlags & FloatWinPopupFlags::Up)
+ {
+ rect_anchor = GDK_GRAVITY_NORTH_WEST;
+ menu_anchor = GDK_GRAVITY_SOUTH_WEST;
+ }
+ else if (pThis->m_nFloatFlags & FloatWinPopupFlags::Right)
+ {
+ rect_anchor = GDK_GRAVITY_NORTH_EAST;
+ }
+
+ VclPtr<vcl::Window> pVclParent = pThis->GetWindow()->GetParent();
+ if (pVclParent->GetOutDev()->HasMirroredGraphics() && pVclParent->IsRTLEnabled())
+ {
+ swapDirection(rect_anchor);
+ swapDirection(menu_anchor);
+ }
+
+ tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pVclParent, pThis->m_aFloatRect);
+ if (gdk_window_get_window_type(widget_get_surface(pThis->m_pParent->m_pWindow)) != GDK_WINDOW_TOPLEVEL)
+ {
+ // See tdf#152155 for an example
+ gtk_coord nX(0), nY(0.0);
+ gtk_widget_translate_coordinates(pThis->m_pParent->m_pWindow, widget_get_toplevel(pThis->m_pParent->m_pWindow), 0, 0, &nX, &nY);
+ aFloatRect.Move(nX, nY);
+ }
+
+ GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
+ static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
+
+ GdkSurface* gdkWindow = widget_get_surface(pThis->m_pWindow);
+ window_move_to_rect(gdkWindow, &rect, rect_anchor, menu_anchor, static_cast<GdkAnchorHints>(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE), 0, 0);
+#endif
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalFrame::signalConfigure(GtkWidget*, GdkEventConfigure* pEvent, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ bool bMoved = false;
+ int x = pEvent->x, y = pEvent->y;
+
+ /* #i31785# claims we cannot trust the x,y members of the event;
+ * they are e.g. not set correctly on maximize/demaximize;
+ * yet the gdkdisplay-x11.c code handling configure_events has
+ * done this XTranslateCoordinates work since the day ~zero.
+ */
+ if (pThis->m_bGeometryIsProvisional || x != pThis->maGeometry.nX || y != pThis->maGeometry.nY )
+ {
+ bMoved = true;
+ pThis->m_bGeometryIsProvisional = false;
+ pThis->maGeometry.nX = x;
+ pThis->maGeometry.nY = y;
+ }
+
+ // update decoration hints
+ GdkRectangle aRect;
+ gdk_window_get_frame_extents( widget_get_surface(GTK_WIDGET(pThis->m_pWindow)), &aRect );
+ pThis->maGeometry.nTopDecoration = y - aRect.y;
+ pThis->maGeometry.nBottomDecoration = aRect.y + aRect.height - y - pEvent->height;
+ pThis->maGeometry.nLeftDecoration = x - aRect.x;
+ pThis->maGeometry.nRightDecoration = aRect.x + aRect.width - x - pEvent->width;
+ pThis->updateScreenNumber();
+
+ if (bMoved)
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->maNWFData.mbCanDetermineWindowPosition)
+ pThis->CallCallbackExc(SalEvent::Move, nullptr);
+ }
+
+ return false;
+}
+#endif
+
+void GtkSalFrame::queue_draw()
+{
+ gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
+}
+
+void GtkSalFrame::TriggerPaintEvent()
+{
+ //Under gtk2 we can basically paint directly into the XWindow and on
+ //additional "expose-event" events we can re-render the missing pieces
+ //
+ //Under gtk3 we have to keep our own buffer up to date and flush it into
+ //the given cairo context on "draw". So we emit a paint event on
+ //opportune resize trigger events to initially fill our backbuffer and then
+ //keep it up to date with our direct paints and tell gtk those regions
+ //have changed and then blit them into the provided cairo context when
+ //we get the "draw"
+ //
+ //The other alternative was to always paint everything on "draw", but
+ //that duplicates the amount of drawing and is hideously slow
+ SAL_INFO("vcl.gtk3", "force painting" << 0 << "," << 0 << " " << maGeometry.nWidth << "x" << maGeometry.nHeight);
+ SalPaintEvent aPaintEvt(0, 0, maGeometry.nWidth, maGeometry.nHeight, true);
+ CallCallbackExc(SalEvent::Paint, &aPaintEvt);
+ queue_draw();
+}
+
+void GtkSalFrame::DrawingAreaFocusInOut(SalEvent nEventType)
+{
+ SalGenericInstance* pSalInstance = GetGenericInstance();
+
+ // check if printers have changed (analogous to salframe focus handler)
+ pSalInstance->updatePrinterUpdate();
+
+ if (nEventType == SalEvent::LoseFocus)
+ m_nKeyModifiers = ModKeyFlags::NONE;
+
+ if (m_pIMHandler)
+ {
+ bool bFocusInAnotherGtkWidget = false;
+ if (GTK_IS_WINDOW(m_pWindow))
+ {
+ GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(m_pWindow));
+ bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(m_pFixedContainer);
+ }
+ if (!bFocusInAnotherGtkWidget)
+ m_pIMHandler->focusChanged(nEventType == SalEvent::GetFocus);
+ }
+
+ // ask for changed printers like generic implementation
+ if (nEventType == SalEvent::GetFocus && pSalInstance->isPrinterInit())
+ pSalInstance->updatePrinterUpdate();
+
+ CallCallbackExc(nEventType, nullptr);
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalFrame::signalFocus( GtkWidget*, GdkEventFocus* pEvent, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ SalGenericInstance *pSalInstance = GetGenericInstance();
+
+ // check if printers have changed (analogous to salframe focus handler)
+ pSalInstance->updatePrinterUpdate();
+
+ if( !pEvent->in )
+ pThis->m_nKeyModifiers = ModKeyFlags::NONE;
+
+ if( pThis->m_pIMHandler )
+ {
+ bool bFocusInAnotherGtkWidget = false;
+ if (GTK_IS_WINDOW(pThis->m_pWindow))
+ {
+ GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
+ bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(pThis->m_pFixedContainer);
+ }
+ if (!bFocusInAnotherGtkWidget)
+ pThis->m_pIMHandler->focusChanged( pEvent->in != 0 );
+ }
+
+ // ask for changed printers like generic implementation
+ if( pEvent->in && pSalInstance->isPrinterInit() )
+ pSalInstance->updatePrinterUpdate();
+
+ // FIXME: find out who the hell steals the focus from our frame
+ // while we have the pointer grabbed, this should not come from
+ // the window manager. Is this an event that was still queued ?
+ // The focus does not seem to get set inside our process
+ // in the meantime do not propagate focus get/lose if floats are open
+ if( m_nFloats == 0 )
+ {
+ GtkWidget* pGrabWidget;
+ if (GTK_IS_EVENT_BOX(pThis->m_pWindow))
+ pGrabWidget = GTK_WIDGET(pThis->m_pWindow);
+ else
+ pGrabWidget = GTK_WIDGET(pThis->m_pFixedContainer);
+ bool bHasFocus = gtk_widget_has_focus(pGrabWidget);
+ pThis->CallCallbackExc(bHasFocus ? SalEvent::GetFocus : SalEvent::LoseFocus, nullptr);
+ }
+
+ return false;
+}
+#else
+void GtkSalFrame::signalFocusEnter(GtkEventControllerFocus*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->DrawingAreaFocusInOut(SalEvent::GetFocus);
+}
+
+void GtkSalFrame::signalFocusLeave(GtkEventControllerFocus*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->DrawingAreaFocusInOut(SalEvent::LoseFocus);
+}
+#endif
+
+// change of focus between native widgets within the toplevel
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalSetFocus(GtkWindow*, GtkWidget* pWidget, gpointer frame)
+#else
+void GtkSalFrame::signalSetFocus(GtkWindow*, GParamSpec*, gpointer frame)
+#endif
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ GtkWidget* pGrabWidget = GTK_WIDGET(pThis->m_pFixedContainer);
+
+ GtkWidget* pTopLevel = widget_get_toplevel(pGrabWidget);
+ // see commentary in GtkSalObjectWidgetClip::Show
+ if (pTopLevel && g_object_get_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange"))
+ return;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pWidget = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
+#endif
+
+ // tdf#129634 interpret losing focus as focus passing explicitly to another widget
+ bool bLoseFocus = pWidget && pWidget != pGrabWidget;
+
+ // do not propagate focus get/lose if floats are open
+ pThis->CallCallbackExc(bLoseFocus ? SalEvent::LoseFocus : SalEvent::GetFocus, nullptr);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_can_focus(GTK_WIDGET(pThis->m_pFixedContainer), !bLoseFocus);
+#endif
+}
+
+void GtkSalFrame::WindowMap()
+{
+ if (m_bIconSetWhileUnmapped)
+ SetIcon(gtk_window_get_icon_name(GTK_WINDOW(m_pWindow)));
+
+ CallCallbackExc( SalEvent::Resize, nullptr );
+ TriggerPaintEvent();
+}
+
+void GtkSalFrame::WindowUnmap()
+{
+ CallCallbackExc( SalEvent::Resize, nullptr );
+
+ if (m_bFloatPositioned)
+ {
+ // Unrealize is needed for cases where we reuse the same popup
+ // (e.g. the font name control), making the realize signal fire
+ // again on next show.
+ gtk_widget_unrealize(m_pWindow);
+ m_bFloatPositioned = false;
+ }
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalMap(GtkWidget*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->WindowMap();
+}
+
+void GtkSalFrame::signalUnmap(GtkWidget*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->WindowUnmap();
+}
+#else
+gboolean GtkSalFrame::signalMap(GtkWidget*, GdkEvent*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->WindowMap();
+ return false;
+}
+
+gboolean GtkSalFrame::signalUnmap(GtkWidget*, GdkEvent*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->WindowUnmap();
+ return false;
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+
+static bool key_forward(GdkEventKey* pEvent, GtkWindow* pDest)
+{
+ gpointer pClass = g_type_class_ref(GTK_TYPE_WINDOW);
+ GtkWidgetClass* pWindowClass = GTK_WIDGET_CLASS(pClass);
+ bool bHandled = pEvent->type == GDK_KEY_PRESS
+ ? pWindowClass->key_press_event(GTK_WIDGET(pDest), pEvent)
+ : pWindowClass->key_release_event(GTK_WIDGET(pDest), pEvent);
+ g_type_class_unref(pClass);
+ return bHandled;
+}
+
+static bool activate_menubar_mnemonic(GtkWidget* pWidget, guint nKeyval)
+{
+ const char* pLabel = gtk_menu_item_get_label(GTK_MENU_ITEM(pWidget));
+ gunichar cAccelChar = 0;
+ if (!pango_parse_markup(pLabel, -1, '_', nullptr, nullptr, &cAccelChar, nullptr))
+ return false;
+ if (!cAccelChar)
+ return false;
+ auto nMnemonicKeyval = gdk_keyval_to_lower(gdk_unicode_to_keyval(cAccelChar));
+ if (nKeyval == nMnemonicKeyval)
+ return gtk_widget_mnemonic_activate(pWidget, false);
+ return false;
+}
+
+bool GtkSalFrame::HandleMenubarMnemonic(guint eState, guint nKeyval)
+{
+ bool bUsedInMenuBar = false;
+ if (eState & GDK_ALT_MASK)
+ {
+ if (GtkWidget* pMenuBar = m_pSalMenu ? m_pSalMenu->GetMenuBarWidget() : nullptr)
+ {
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pMenuBar));
+ for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
+ {
+ bUsedInMenuBar = activate_menubar_mnemonic(static_cast<GtkWidget*>(pChild->data), nKeyval);
+ if (bUsedInMenuBar)
+ break;
+ }
+ g_list_free(pChildren);
+ }
+ }
+ return bUsedInMenuBar;
+}
+
+gboolean GtkSalFrame::signalKey(GtkWidget* pWidget, GdkEventKey* pEvent, gpointer frame)
+{
+ UpdateLastInputEventTime(pEvent->time);
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ bool bFocusInAnotherGtkWidget = false;
+
+ VclPtr<vcl::Window> xTopLevelInterimWindow;
+
+ if (GTK_IS_WINDOW(pThis->m_pWindow))
+ {
+ GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
+ bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(pThis->m_pFixedContainer);
+ if (bFocusInAnotherGtkWidget)
+ {
+ if (!gtk_widget_get_realized(pFocusWindow))
+ return true;
+
+ // if the focus is not in our main widget, see if there is a handler
+ // for this key stroke in GtkWindow first
+ if (key_forward(pEvent, GTK_WINDOW(pThis->m_pWindow)))
+ return true;
+
+ // Is focus inside an InterimItemWindow? In which case find that
+ // InterimItemWindow and send unconsumed keystrokes to it to
+ // support ctrl-q etc shortcuts. Only bother to search for the
+ // InterimItemWindow if it is a toplevel that fills its frame, or
+ // the keystroke is sufficiently special its worth passing on,
+ // e.g. F6 to switch between task-panels or F5 to close a navigator
+ if (pThis->IsCycleFocusOutDisallowed() || IsFunctionKeyVal(pEvent->keyval))
+ {
+ GtkWidget* pSearch = pFocusWindow;
+ while (pSearch)
+ {
+ void* pData = g_object_get_data(G_OBJECT(pSearch), "InterimWindowGlue");
+ if (pData)
+ {
+ xTopLevelInterimWindow = static_cast<vcl::Window*>(pData);
+ break;
+ }
+ pSearch = gtk_widget_get_parent(pSearch);
+ }
+ }
+ }
+ }
+
+ if (pThis->isFloatGrabWindow())
+ return signalKey(pWidget, pEvent, pThis->m_pParent);
+
+ vcl::DeletionListener aDel( pThis );
+
+ if (!bFocusInAnotherGtkWidget && pThis->m_pIMHandler && pThis->m_pIMHandler->handleKeyEvent(pEvent))
+ return true;
+
+ bool bStopProcessingKey = false;
+
+ // handle modifiers
+ if( pEvent->keyval == GDK_KEY_Shift_L || pEvent->keyval == GDK_KEY_Shift_R ||
+ pEvent->keyval == GDK_KEY_Control_L || pEvent->keyval == GDK_KEY_Control_R ||
+ pEvent->keyval == GDK_KEY_Alt_L || pEvent->keyval == GDK_KEY_Alt_R ||
+ pEvent->keyval == GDK_KEY_Meta_L || pEvent->keyval == GDK_KEY_Meta_R ||
+ pEvent->keyval == GDK_KEY_Super_L || pEvent->keyval == GDK_KEY_Super_R )
+ {
+ sal_uInt16 nModCode = GetKeyModCode( pEvent->state );
+ ModKeyFlags nExtModMask = ModKeyFlags::NONE;
+ sal_uInt16 nModMask = 0;
+ // pressing just the ctrl key leads to a keysym of XK_Control but
+ // the event state does not contain ControlMask. In the release
+ // event it's the other way round: it does contain the Control mask.
+ // The modifier mode therefore has to be adapted manually.
+ switch( pEvent->keyval )
+ {
+ case GDK_KEY_Control_L:
+ nExtModMask = ModKeyFlags::LeftMod1;
+ nModMask = KEY_MOD1;
+ break;
+ case GDK_KEY_Control_R:
+ nExtModMask = ModKeyFlags::RightMod1;
+ nModMask = KEY_MOD1;
+ break;
+ case GDK_KEY_Alt_L:
+ nExtModMask = ModKeyFlags::LeftMod2;
+ nModMask = KEY_MOD2;
+ break;
+ case GDK_KEY_Alt_R:
+ nExtModMask = ModKeyFlags::RightMod2;
+ nModMask = KEY_MOD2;
+ break;
+ case GDK_KEY_Shift_L:
+ nExtModMask = ModKeyFlags::LeftShift;
+ nModMask = KEY_SHIFT;
+ break;
+ case GDK_KEY_Shift_R:
+ nExtModMask = ModKeyFlags::RightShift;
+ nModMask = KEY_SHIFT;
+ break;
+ // Map Meta/Super to MOD3 modifier on all Unix systems
+ // except macOS
+ case GDK_KEY_Meta_L:
+ case GDK_KEY_Super_L:
+ nExtModMask = ModKeyFlags::LeftMod3;
+ nModMask = KEY_MOD3;
+ break;
+ case GDK_KEY_Meta_R:
+ case GDK_KEY_Super_R:
+ nExtModMask = ModKeyFlags::RightMod3;
+ nModMask = KEY_MOD3;
+ break;
+ }
+
+ SalKeyModEvent aModEvt;
+ aModEvt.mbDown = pEvent->type == GDK_KEY_PRESS;
+
+ if( pEvent->type == GDK_KEY_RELEASE )
+ {
+ aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
+ aModEvt.mnCode = nModCode & ~nModMask;
+ pThis->m_nKeyModifiers &= ~nExtModMask;
+ }
+ else
+ {
+ aModEvt.mnCode = nModCode | nModMask;
+ pThis->m_nKeyModifiers |= nExtModMask;
+ aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
+ }
+
+ pThis->CallCallbackExc( SalEvent::KeyModChange, &aModEvt );
+ }
+ else
+ {
+ bool bRestoreDisallowCycleFocusOut = false;
+
+ VclPtr<vcl::Window> xOrigFrameFocusWin;
+ VclPtr<vcl::Window> xOrigFocusWin;
+ if (xTopLevelInterimWindow)
+ {
+ // Focus is inside an InterimItemWindow so send unconsumed
+ // keystrokes to by setting it as the mpFocusWin
+ VclPtr<vcl::Window> xVclWindow = pThis->GetWindow();
+ ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
+ xOrigFrameFocusWin = pFrameData->mpFocusWin;
+ pFrameData->mpFocusWin = xTopLevelInterimWindow;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ xOrigFocusWin = pSVData->mpWinData->mpFocusWin;
+ pSVData->mpWinData->mpFocusWin = xTopLevelInterimWindow;
+
+ if (pEvent->keyval == GDK_KEY_F6 && pThis->IsCycleFocusOutDisallowed())
+ {
+ // For F6, allow the focus to leave the InterimItemWindow
+ pThis->AllowCycleFocusOut();
+ bRestoreDisallowCycleFocusOut = true;
+ }
+ }
+
+ bStopProcessingKey = pThis->doKeyCallback(pEvent->state,
+ pEvent->keyval,
+ pEvent->hardware_keycode,
+ pEvent->group,
+ sal_Unicode(gdk_keyval_to_unicode( pEvent->keyval )),
+ (pEvent->type == GDK_KEY_PRESS),
+ false);
+
+ // tdf#144846 If this is registered as a menubar mnemonic then ensure
+ // that any other widget won't be considered as a candidate by taking
+ // over the task of launch the menubar menu outself
+ // The code was moved here from its original position at beginning
+ // of this function in order to resolve tdf#146174.
+ if (!bStopProcessingKey && // module key handler did not process key
+ pEvent->type == GDK_KEY_PRESS && // module key handler handles only GDK_KEY_PRESS
+ GTK_IS_WINDOW(pThis->m_pWindow) &&
+ pThis->HandleMenubarMnemonic(pEvent->state, pEvent->keyval))
+ {
+ return true;
+ }
+
+ if (!aDel.isDeleted())
+ {
+ pThis->m_nKeyModifiers = ModKeyFlags::NONE;
+
+ if (xTopLevelInterimWindow)
+ {
+ // Focus was inside an InterimItemWindow, restore the original
+ // focus win, unless the focus was changed away from the
+ // InterimItemWindow which should only be possible with F6
+ VclPtr<vcl::Window> xVclWindow = pThis->GetWindow();
+ ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
+ if (pFrameData->mpFocusWin == xTopLevelInterimWindow)
+ pFrameData->mpFocusWin = xOrigFrameFocusWin;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->mpWinData->mpFocusWin == xTopLevelInterimWindow)
+ pSVData->mpWinData->mpFocusWin = xOrigFocusWin;
+
+ if (bRestoreDisallowCycleFocusOut)
+ {
+ // undo the above AllowCycleFocusOut for F6
+ pThis->DisallowCycleFocusOut();
+ }
+ }
+ }
+
+ }
+
+ if (!bFocusInAnotherGtkWidget && !aDel.isDeleted() && pThis->m_pIMHandler)
+ pThis->m_pIMHandler->updateIMSpotLocation();
+
+ return bStopProcessingKey;
+}
+#else
+
+bool GtkSalFrame::DrawingAreaKey(GtkEventControllerKey* pController, SalEvent nEventType, guint keyval, guint keycode, guint state)
+{
+ guint32 nTime = gdk_event_get_time(gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController)));
+ UpdateLastInputEventTime(nTime);
+
+ bool bFocusInAnotherGtkWidget = false;
+
+ VclPtr<vcl::Window> xTopLevelInterimWindow;
+
+ if (GTK_IS_WINDOW(m_pWindow))
+ {
+ GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(m_pWindow));
+ bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(m_pFixedContainer);
+ if (bFocusInAnotherGtkWidget)
+ {
+ if (!gtk_widget_get_realized(pFocusWindow))
+ return true;
+ // if the focus is not in our main widget, see if there is a handler
+ // for this key stroke in GtkWindow first
+ bool bHandled = gtk_event_controller_key_forward(pController, m_pWindow);
+ if (bHandled)
+ return true;
+
+ // Is focus inside an InterimItemWindow? In which case find that
+ // InterimItemWindow and send unconsumed keystrokes to it to
+ // support ctrl-q etc shortcuts. Only bother to search for the
+ // InterimItemWindow if it is a toplevel that fills its frame, or
+ // the keystroke is sufficiently special its worth passing on,
+ // e.g. F6 to switch between task-panels or F5 to close a navigator
+ if (IsCycleFocusOutDisallowed() || IsFunctionKeyVal(keyval))
+ {
+ GtkWidget* pSearch = pFocusWindow;
+ while (pSearch)
+ {
+ void* pData = g_object_get_data(G_OBJECT(pSearch), "InterimWindowGlue");
+ if (pData)
+ {
+ xTopLevelInterimWindow = static_cast<vcl::Window*>(pData);
+ break;
+ }
+ pSearch = gtk_widget_get_parent(pSearch);
+ }
+ }
+ }
+ }
+
+ vcl::DeletionListener aDel(this);
+
+ bool bStopProcessingKey = false;
+
+ // handle modifiers
+ if( keyval == GDK_KEY_Shift_L || keyval == GDK_KEY_Shift_R ||
+ keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R ||
+ keyval == GDK_KEY_Alt_L || keyval == GDK_KEY_Alt_R ||
+ keyval == GDK_KEY_Meta_L || keyval == GDK_KEY_Meta_R ||
+ keyval == GDK_KEY_Super_L || keyval == GDK_KEY_Super_R )
+ {
+ sal_uInt16 nModCode = GetKeyModCode(state);
+ ModKeyFlags nExtModMask = ModKeyFlags::NONE;
+ sal_uInt16 nModMask = 0;
+ // pressing just the ctrl key leads to a keysym of XK_Control but
+ // the event state does not contain ControlMask. In the release
+ // event it's the other way round: it does contain the Control mask.
+ // The modifier mode therefore has to be adapted manually.
+ switch (keyval)
+ {
+ case GDK_KEY_Control_L:
+ nExtModMask = ModKeyFlags::LeftMod1;
+ nModMask = KEY_MOD1;
+ break;
+ case GDK_KEY_Control_R:
+ nExtModMask = ModKeyFlags::RightMod1;
+ nModMask = KEY_MOD1;
+ break;
+ case GDK_KEY_Alt_L:
+ nExtModMask = ModKeyFlags::LeftMod2;
+ nModMask = KEY_MOD2;
+ break;
+ case GDK_KEY_Alt_R:
+ nExtModMask = ModKeyFlags::RightMod2;
+ nModMask = KEY_MOD2;
+ break;
+ case GDK_KEY_Shift_L:
+ nExtModMask = ModKeyFlags::LeftShift;
+ nModMask = KEY_SHIFT;
+ break;
+ case GDK_KEY_Shift_R:
+ nExtModMask = ModKeyFlags::RightShift;
+ nModMask = KEY_SHIFT;
+ break;
+ // Map Meta/Super to MOD3 modifier on all Unix systems
+ // except macOS
+ case GDK_KEY_Meta_L:
+ case GDK_KEY_Super_L:
+ nExtModMask = ModKeyFlags::LeftMod3;
+ nModMask = KEY_MOD3;
+ break;
+ case GDK_KEY_Meta_R:
+ case GDK_KEY_Super_R:
+ nExtModMask = ModKeyFlags::RightMod3;
+ nModMask = KEY_MOD3;
+ break;
+ }
+
+ SalKeyModEvent aModEvt;
+ aModEvt.mbDown = nEventType == SalEvent::KeyInput;
+
+ if (!aModEvt.mbDown)
+ {
+ aModEvt.mnModKeyCode = m_nKeyModifiers;
+ aModEvt.mnCode = nModCode & ~nModMask;
+ m_nKeyModifiers &= ~nExtModMask;
+ }
+ else
+ {
+ aModEvt.mnCode = nModCode | nModMask;
+ m_nKeyModifiers |= nExtModMask;
+ aModEvt.mnModKeyCode = m_nKeyModifiers;
+ }
+
+ CallCallbackExc(SalEvent::KeyModChange, &aModEvt);
+ }
+ else
+ {
+ bool bRestoreDisallowCycleFocusOut = false;
+
+ VclPtr<vcl::Window> xOrigFrameFocusWin;
+ VclPtr<vcl::Window> xOrigFocusWin;
+ if (xTopLevelInterimWindow)
+ {
+ // Focus is inside an InterimItemWindow so send unconsumed
+ // keystrokes to by setting it as the mpFocusWin
+ VclPtr<vcl::Window> xVclWindow = GetWindow();
+ ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
+ xOrigFrameFocusWin = pFrameData->mpFocusWin;
+ pFrameData->mpFocusWin = xTopLevelInterimWindow;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ xOrigFocusWin = pSVData->mpWinData->mpFocusWin;
+ pSVData->mpWinData->mpFocusWin = xTopLevelInterimWindow;
+
+ if (keyval == GDK_KEY_F6 && IsCycleFocusOutDisallowed())
+ {
+ // For F6, allow the focus to leave the InterimItemWindow
+ AllowCycleFocusOut();
+ bRestoreDisallowCycleFocusOut = true;
+ }
+ }
+
+
+ bStopProcessingKey = doKeyCallback(state,
+ keyval,
+ keycode,
+ 0, // group
+ sal_Unicode(gdk_keyval_to_unicode(keyval)),
+ nEventType == SalEvent::KeyInput,
+ false);
+
+ if (!aDel.isDeleted())
+ {
+ m_nKeyModifiers = ModKeyFlags::NONE;
+
+ if (xTopLevelInterimWindow)
+ {
+ // Focus was inside an InterimItemWindow, restore the original
+ // focus win, unless the focus was changed away from the
+ // InterimItemWindow which should only be possible with F6
+ VclPtr<vcl::Window> xVclWindow = GetWindow();
+ ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
+ if (pFrameData->mpFocusWin == xTopLevelInterimWindow)
+ pFrameData->mpFocusWin = xOrigFrameFocusWin;
+
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->mpWinData->mpFocusWin == xTopLevelInterimWindow)
+ pSVData->mpWinData->mpFocusWin = xOrigFocusWin;
+
+ if (bRestoreDisallowCycleFocusOut)
+ {
+ // undo the above AllowCycleFocusOut for F6
+ DisallowCycleFocusOut();
+ }
+ }
+ }
+ }
+
+ if (m_pIMHandler)
+ m_pIMHandler->updateIMSpotLocation();
+
+ return bStopProcessingKey;
+}
+
+gboolean GtkSalFrame::signalKeyPressed(GtkEventControllerKey* pController, guint keyval, guint keycode, GdkModifierType state, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ return pThis->DrawingAreaKey(pController, SalEvent::KeyInput, keyval, keycode, state);
+}
+
+gboolean GtkSalFrame::signalKeyReleased(GtkEventControllerKey* pController, guint keyval, guint keycode, GdkModifierType state, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ return pThis->DrawingAreaKey(pController, SalEvent::KeyUp, keyval, keycode, state);
+}
+#endif
+
+bool GtkSalFrame::WindowCloseRequest()
+{
+ CallCallbackExc(SalEvent::Close, nullptr);
+ return true;
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalFrame::signalDelete(GtkWidget*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ return pThis->WindowCloseRequest();
+}
+#else
+gboolean GtkSalFrame::signalDelete(GtkWidget*, GdkEvent*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ return pThis->WindowCloseRequest();
+}
+#endif
+
+const cairo_font_options_t* GtkSalFrame::get_font_options()
+{
+ GtkWidget* pWidget = getMouseEventWidget();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ PangoContext* pContext = gtk_widget_get_pango_context(pWidget);
+ assert(pContext);
+ return pango_cairo_context_get_font_options(pContext);
+#else
+ return gdk_screen_get_font_options(gtk_widget_get_screen(pWidget));
+#endif
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalStyleUpdated(GtkWidget*, const gchar* /*pSetting*/, gpointer frame)
+#else
+void GtkSalFrame::signalStyleUpdated(GtkWidget*, gpointer frame)
+#endif
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ // note: settings changed for multiple frames is avoided in winproc.cxx ImplHandleSettings
+ GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::SettingsChanged );
+
+ // a plausible alternative might be to send SalEvent::FontChanged if pSetting starts with "gtk-xft"
+
+ // fire off font-changed when the system cairo font hints change
+ GtkInstance *pInstance = GetGtkInstance();
+ const cairo_font_options_t* pLastCairoFontOptions = pInstance->GetLastSeenCairoFontOptions();
+ const cairo_font_options_t* pCurrentCairoFontOptions = pThis->get_font_options();
+ bool bFontSettingsChanged = true;
+ if (pLastCairoFontOptions && pCurrentCairoFontOptions)
+ bFontSettingsChanged = !cairo_font_options_equal(pLastCairoFontOptions, pCurrentCairoFontOptions);
+ else if (!pLastCairoFontOptions && !pCurrentCairoFontOptions)
+ bFontSettingsChanged = false;
+ if (bFontSettingsChanged)
+ {
+ pInstance->ResetLastSeenCairoFontOptions(pCurrentCairoFontOptions);
+ GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::FontChanged );
+ }
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalFrame::signalWindowState( GtkWidget*, GdkEvent* pEvent, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if( (pThis->m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) != (pEvent->window_state.new_window_state & GDK_TOPLEVEL_STATE_MINIMIZED) )
+ {
+ GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize );
+ pThis->TriggerPaintEvent();
+ }
+
+ if ((pEvent->window_state.new_window_state & GDK_TOPLEVEL_STATE_MAXIMIZED) &&
+ !(pThis->m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED))
+ {
+ pThis->m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(pThis->m_pWindow));
+ }
+
+ if ((pEvent->window_state.new_window_state & GDK_WINDOW_STATE_WITHDRAWN) &&
+ !(pThis->m_nState & GDK_WINDOW_STATE_WITHDRAWN))
+ {
+ if (pThis->isFloatGrabWindow())
+ pThis->closePopup();
+ }
+
+ pThis->m_nState = pEvent->window_state.new_window_state;
+
+ return false;
+}
+#else
+void GtkSalFrame::signalWindowState(GdkToplevel* pSurface, GParamSpec*, gpointer frame)
+{
+ GdkToplevelState eNewWindowState = gdk_toplevel_get_state(pSurface);
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if( (pThis->m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) != (eNewWindowState & GDK_TOPLEVEL_STATE_MINIMIZED) )
+ {
+ GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize );
+ pThis->TriggerPaintEvent();
+ }
+
+ if ((eNewWindowState & GDK_TOPLEVEL_STATE_MAXIMIZED) &&
+ !(pThis->m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED))
+ {
+ pThis->m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(pThis->m_pWindow));
+ }
+
+ pThis->m_nState = eNewWindowState;
+}
+#endif
+
+namespace
+{
+ GdkDragAction VclToGdk(sal_Int8 dragOperation)
+ {
+ GdkDragAction eRet(static_cast<GdkDragAction>(0));
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
+ eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_COPY);
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
+ eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_MOVE);
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
+ eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_LINK);
+ return eRet;
+ }
+
+ sal_Int8 GdkToVcl(GdkDragAction dragOperation)
+ {
+ sal_Int8 nRet(0);
+ if (dragOperation & GDK_ACTION_COPY)
+ nRet |= css::datatransfer::dnd::DNDConstants::ACTION_COPY;
+ if (dragOperation & GDK_ACTION_MOVE)
+ nRet |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ if (dragOperation & GDK_ACTION_LINK)
+ nRet |= css::datatransfer::dnd::DNDConstants::ACTION_LINK;
+ return nRet;
+ }
+}
+
+namespace
+{
+ GdkDragAction getPreferredDragAction(sal_Int8 dragOperation)
+ {
+ GdkDragAction eAct(static_cast<GdkDragAction>(0));
+
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
+ eAct = GDK_ACTION_MOVE;
+ else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
+ eAct = GDK_ACTION_COPY;
+ else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
+ eAct = GDK_ACTION_LINK;
+
+ return eAct;
+ }
+}
+
+static bool g_DropSuccessSet = false;
+static bool g_DropSuccess = false;
+
+namespace {
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+
+void read_drop_async_completed(GObject* source, GAsyncResult* res, gpointer user_data)
+{
+ GdkDrop* drop = GDK_DROP(source);
+ read_transfer_result* pRes = static_cast<read_transfer_result*>(user_data);
+
+ GInputStream* pResult = gdk_drop_read_finish(drop, res, nullptr, nullptr);
+
+ if (!pResult)
+ {
+ pRes->bDone = true;
+ g_main_context_wakeup(nullptr);
+ return;
+ }
+
+ pRes->aVector.resize(read_transfer_result::BlockSize);
+
+ g_input_stream_read_async(pResult,
+ pRes->aVector.data(),
+ pRes->aVector.size(),
+ G_PRIORITY_DEFAULT,
+ nullptr,
+ read_transfer_result::read_block_async_completed,
+ user_data);
+}
+#endif
+
+class GtkDropTargetDropContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDropContext>
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkDragContext *m_pContext;
+ guint m_nTime;
+#else
+ GdkDrop* m_pDrop;
+#endif
+public:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkDropTargetDropContext(GdkDragContext* pContext, guint nTime)
+ : m_pContext(pContext)
+ , m_nTime(nTime)
+#else
+ GtkDropTargetDropContext(GdkDrop* pDrop)
+ : m_pDrop(pDrop)
+#endif
+ {
+ }
+
+ // XDropTargetDropContext
+ virtual void SAL_CALL acceptDrop(sal_Int8 dragOperation) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
+#else
+ GdkDragAction eDragAction = getPreferredDragAction(dragOperation);
+ gdk_drop_status(m_pDrop,
+ static_cast<GdkDragAction>(eDragAction | gdk_drop_get_actions(m_pDrop)),
+ eDragAction);
+#endif
+ }
+
+ virtual void SAL_CALL rejectDrop() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
+#else
+ gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), static_cast<GdkDragAction>(0));
+#endif
+ }
+
+ virtual void SAL_CALL dropComplete(sal_Bool bSuccess) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_drag_finish(m_pContext, bSuccess, false, m_nTime);
+#else
+ // should we do something better here
+ gdk_drop_finish(m_pDrop, bSuccess
+ ? gdk_drop_get_actions(m_pDrop)
+ : static_cast<GdkDragAction>(0));
+#endif
+ if (GtkInstDragSource::g_ActiveDragSource)
+ {
+ g_DropSuccessSet = true;
+ g_DropSuccess = bSuccess;
+ }
+ }
+};
+
+}
+
+class GtkDnDTransferable : public GtkTransferable
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkDragContext *m_pContext;
+ guint m_nTime;
+ GtkWidget *m_pWidget;
+ GtkInstDropTarget* m_pDropTarget;
+#else
+ GdkDrop* m_pDrop;
+#endif
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GMainLoop *m_pLoop;
+ GtkSelectionData *m_pData;
+#endif
+public:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkDnDTransferable(GdkDragContext *pContext, guint nTime, GtkWidget *pWidget, GtkInstDropTarget *pDropTarget)
+ : m_pContext(pContext)
+ , m_nTime(nTime)
+ , m_pWidget(pWidget)
+ , m_pDropTarget(pDropTarget)
+#else
+ GtkDnDTransferable(GdkDrop *pDrop)
+ : m_pDrop(pDrop)
+#endif
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_pLoop(nullptr)
+ , m_pData(nullptr)
+#endif
+ {
+ }
+
+ virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override
+ {
+ css::datatransfer::DataFlavor aFlavor(rFlavor);
+ if (aFlavor.MimeType == "text/plain;charset=utf-16")
+ aFlavor.MimeType = "text/plain;charset=utf-8";
+
+ auto it = m_aMimeTypeToGtkType.find(aFlavor.MimeType);
+ if (it == m_aMimeTypeToGtkType.end())
+ return css::uno::Any();
+
+ css::uno::Any aRet;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ /* like gtk_clipboard_wait_for_contents run a sub loop
+ * waiting for drag-data-received triggered from
+ * gtk_drag_get_data
+ */
+ {
+ m_pLoop = g_main_loop_new(nullptr, true);
+ m_pDropTarget->SetFormatConversionRequest(this);
+
+ gtk_drag_get_data(m_pWidget, m_pContext, it->second, m_nTime);
+
+ if (g_main_loop_is_running(m_pLoop))
+ main_loop_run(m_pLoop);
+
+ g_main_loop_unref(m_pLoop);
+ m_pLoop = nullptr;
+ m_pDropTarget->SetFormatConversionRequest(nullptr);
+ }
+
+ if (aFlavor.MimeType == "text/plain;charset=utf-8")
+ {
+ OUString aStr;
+ gchar *pText = reinterpret_cast<gchar*>(gtk_selection_data_get_text(m_pData));
+ if (pText)
+ aStr = OUString(pText, rtl_str_getLength(pText), RTL_TEXTENCODING_UTF8);
+ g_free(pText);
+ aRet <<= aStr.replaceAll("\r\n", "\n");
+ }
+ else
+ {
+ gint length(0);
+ const guchar *rawdata = gtk_selection_data_get_data_with_length(m_pData,
+ &length);
+ // seen here was rawhide == nullptr and length set to -1
+ if (rawdata)
+ {
+ css::uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
+ aRet <<= aSeq;
+ }
+ }
+
+ gtk_selection_data_free(m_pData);
+#else
+ SalInstance* pInstance = GetSalInstance();
+ read_transfer_result aRes;
+ const char *mime_types[] = { it->second.getStr(), nullptr };
+
+ gdk_drop_read_async(m_pDrop,
+ mime_types,
+ G_PRIORITY_DEFAULT,
+ nullptr,
+ read_drop_async_completed,
+ &aRes);
+
+ while (!aRes.bDone)
+ pInstance->DoYield(true, false);
+
+ if (aFlavor.MimeType == "text/plain;charset=utf-8")
+ aRet <<= aRes.get_as_string();
+ else
+ aRet <<= aRes.get_as_sequence();
+#endif
+ return aRet;
+ }
+
+ virtual std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ std::vector<GdkAtom> targets;
+ for (GList* l = gdk_drag_context_list_targets(m_pContext); l; l = l->next)
+ targets.push_back(static_cast<GdkAtom>(l->data));
+ return GtkTransferable::getTransferDataFlavorsAsVector(targets.data(), targets.size());
+#else
+ GdkContentFormats* pFormats = gdk_drop_get_formats(m_pDrop);
+ gsize n_targets;
+ const char * const *targets = gdk_content_formats_get_mime_types(pFormats, &n_targets);
+ return GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
+#endif
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ void LoopEnd(GtkSelectionData *pData)
+ {
+ m_pData = pData;
+ g_main_loop_quit(m_pLoop);
+ }
+#endif
+};
+
+// For LibreOffice internal D&D we provide the Transferable without Gtk
+// intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
+GtkInstDragSource* GtkInstDragSource::g_ActiveDragSource;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+
+gboolean GtkSalFrame::signalDragDrop(GtkDropTargetAsync* context, GdkDrop* drop, double x, double y, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return false;
+ return pThis->m_pDropTarget->signalDragDrop(context, drop, x, y);
+}
+#else
+gboolean GtkSalFrame::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return false;
+ return pThis->m_pDropTarget->signalDragDrop(pWidget, context, x, y, time);
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkInstDropTarget::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time)
+#else
+gboolean GtkInstDropTarget::signalDragDrop(GtkDropTargetAsync* context, GdkDrop* drop, double x, double y)
+#endif
+{
+ // remove the deferred dragExit, as we'll do a drop
+#ifndef NDEBUG
+ bool res =
+#endif
+ g_idle_remove_by_data(this);
+#ifndef NDEBUG
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ assert(res);
+#else
+ (void)res;
+#endif
+#endif
+
+ css::datatransfer::dnd::DropTargetDropEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(this);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ aEvent.Context = new GtkDropTargetDropContext(context, time);
+#else
+ aEvent.Context = new GtkDropTargetDropContext(drop);
+#endif
+ aEvent.LocationX = x;
+ aEvent.LocationY = y;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ aEvent.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
+#else
+ aEvent.DropAction = GdkToVcl(getPreferredDragAction(GdkToVcl(gdk_drop_get_actions(drop))));
+#endif
+ // ACTION_DEFAULT is documented as...
+ // 'This means the user did not press any key during the Drag and Drop operation
+ // and the action that was combined with ACTION_DEFAULT is the system default action'
+ // in tdf#107031 writer won't insert a link when a heading is dragged from the
+ // navigator unless this is set. Its unclear really what ACTION_DEFAULT means,
+ // there is a deprecated 'GDK_ACTION_DEFAULT Means nothing, and should not be used'
+ // possible equivalent in gtk.
+ // So (tdf#109227) set ACTION_DEFAULT if no modifier key is held down
+#if !GTK_CHECK_VERSION(4,0,0)
+ aEvent.SourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
+ GdkModifierType mask;
+ gdk_window_get_pointer(widget_get_surface(pWidget), nullptr, nullptr, &mask);
+#else
+ aEvent.SourceActions = GdkToVcl(gdk_drop_get_actions(drop));
+ GdkModifierType mask = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(context));
+#endif
+ if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
+ aEvent.DropAction |= css::datatransfer::dnd::DNDConstants::ACTION_DEFAULT;
+
+ css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
+ // For LibreOffice internal D&D we provide the Transferable without Gtk
+ // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
+ if (GtkInstDragSource::g_ActiveDragSource)
+ xTransferable = GtkInstDragSource::g_ActiveDragSource->GetTransferrable();
+ else
+ {
+#if GTK_CHECK_VERSION(4,0,0)
+ xTransferable = new GtkDnDTransferable(drop);
+#else
+ xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
+#endif
+ }
+ aEvent.Transferable = xTransferable;
+
+ fire_drop(aEvent);
+
+ return true;
+}
+
+namespace {
+
+class GtkDropTargetDragContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDragContext>
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkDragContext *m_pContext;
+ guint m_nTime;
+#else
+ GdkDrop* m_pDrop;
+#endif
+public:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkDropTargetDragContext(GdkDragContext *pContext, guint nTime)
+ : m_pContext(pContext)
+ , m_nTime(nTime)
+#else
+ GtkDropTargetDragContext(GdkDrop* pDrop)
+ : m_pDrop(pDrop)
+#endif
+ {
+ }
+
+ virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
+#else
+ gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), getPreferredDragAction(dragOperation));
+#endif
+ }
+
+ virtual void SAL_CALL rejectDrag() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
+#else
+ gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), static_cast<GdkDragAction>(0));
+#endif
+ }
+};
+
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return;
+ pThis->m_pDropTarget->signalDragDropReceived(pWidget, context, x, y, data, ttype, time);
+}
+
+void GtkInstDropTarget::signalDragDropReceived(GtkWidget* /*pWidget*/, GdkDragContext * /*context*/, gint /*x*/, gint /*y*/, GtkSelectionData* data, guint /*ttype*/, guint /*time*/)
+{
+ /*
+ * If we get a drop, then we will call like gtk_clipboard_wait_for_contents
+ * with a loop inside a loop to get the right format, so if this is the
+ * case return to the outer loop here with a copy of the desired data
+ *
+ * don't look at me like that.
+ */
+ if (!m_pFormatConversionRequest)
+ return;
+
+ m_pFormatConversionRequest->LoopEnd(gtk_selection_data_copy(data));
+}
+#endif
+
+#if GTK_CHECK_VERSION(4,0,0)
+GdkDragAction GtkSalFrame::signalDragMotion(GtkDropTargetAsync *dest, GdkDrop *drop, double x, double y, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return GdkDragAction(0);
+ return pThis->m_pDropTarget->signalDragMotion(dest, drop, x, y);
+}
+#else
+gboolean GtkSalFrame::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return false;
+ return pThis->m_pDropTarget->signalDragMotion(pWidget, context, x, y, time);
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4,0,0)
+gboolean GtkInstDropTarget::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time)
+#else
+GdkDragAction GtkInstDropTarget::signalDragMotion(GtkDropTargetAsync *context, GdkDrop *pDrop, double x, double y)
+#endif
+{
+ if (!m_bInDrag)
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) : pWidget;
+ gtk_drag_highlight(pHighlightWidget);
+#else
+ GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) :
+ gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(context));
+ gtk_widget_set_state_flags(pHighlightWidget, GTK_STATE_FLAG_DROP_ACTIVE, false);
+#endif
+ }
+
+ css::datatransfer::dnd::DropTargetDragEnterEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(this);
+#if !GTK_CHECK_VERSION(4,0,0)
+ rtl::Reference<GtkDropTargetDragContext> pContext = new GtkDropTargetDragContext(context, time);
+#else
+ rtl::Reference<GtkDropTargetDragContext> pContext = new GtkDropTargetDragContext(pDrop);
+#endif
+ //preliminary accept the Drag and select the preferred action, the fire_* will
+ //inform the original caller of our choice and the callsite can decide
+ //to overrule this choice. i.e. typically here we default to ACTION_MOVE
+#if !GTK_CHECK_VERSION(4,0,0)
+ sal_Int8 nSourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
+ GdkModifierType mask;
+ gdk_window_get_pointer(widget_get_surface(pWidget), nullptr, nullptr, &mask);
+#else
+ sal_Int8 nSourceActions = GdkToVcl(gdk_drop_get_actions(pDrop));
+ GdkModifierType mask = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(context));
+#endif
+
+ // tdf#124411 default to move if drag originates within LO itself, default
+ // to copy if it comes from outside, this is similar to srcAndDestEqual
+ // in macosx DropTarget::determineDropAction equivalent
+ sal_Int8 nNewDropAction = GtkInstDragSource::g_ActiveDragSource ?
+ css::datatransfer::dnd::DNDConstants::ACTION_MOVE :
+ css::datatransfer::dnd::DNDConstants::ACTION_COPY;
+
+ // tdf#109227 if a modifier is held down, default to the matching
+ // action for that modifier combo, otherwise pick the preferred
+ // default from the possible source actions
+ if ((mask & GDK_SHIFT_MASK) && !(mask & GDK_CONTROL_MASK))
+ nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ else if ((mask & GDK_CONTROL_MASK) && !(mask & GDK_SHIFT_MASK))
+ nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
+ else if ((mask & GDK_SHIFT_MASK) && (mask & GDK_CONTROL_MASK) )
+ nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_LINK;
+ nNewDropAction &= nSourceActions;
+
+ GdkDragAction eAction;
+ if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) && !nNewDropAction)
+ eAction = getPreferredDragAction(nSourceActions);
+ else
+ eAction = getPreferredDragAction(nNewDropAction);
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ gdk_drag_status(context, eAction, time);
+#else
+ gdk_drop_status(pDrop,
+ static_cast<GdkDragAction>(eAction | gdk_drop_get_actions(pDrop)),
+ eAction);
+#endif
+ aEvent.Context = pContext;
+ aEvent.LocationX = x;
+ aEvent.LocationY = y;
+ //under wayland at least, the action selected by gdk_drag_status on the
+ //context is not immediately available via gdk_drag_context_get_selected_action
+ //so here we set the DropAction from what we selected on the context, not
+ //what the context says is selected
+ aEvent.DropAction = GdkToVcl(eAction);
+ aEvent.SourceActions = nSourceActions;
+
+ if (!m_bInDrag)
+ {
+ css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
+ // For LibreOffice internal D&D we provide the Transferable without Gtk
+ // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
+ if (GtkInstDragSource::g_ActiveDragSource)
+ xTransferable = GtkInstDragSource::g_ActiveDragSource->GetTransferrable();
+ else
+ {
+#if !GTK_CHECK_VERSION(4,0,0)
+ xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
+#else
+ xTransferable = new GtkDnDTransferable(pDrop);
+#endif
+ }
+ css::uno::Sequence<css::datatransfer::DataFlavor> aFormats = xTransferable->getTransferDataFlavors();
+ aEvent.SupportedDataFlavors = aFormats;
+ fire_dragEnter(aEvent);
+ m_bInDrag = true;
+ }
+ else
+ {
+ fire_dragOver(aEvent);
+ }
+
+#if !GTK_CHECK_VERSION(4,0,0)
+ return true;
+#else
+ return eAction;
+#endif
+}
+
+#if GTK_CHECK_VERSION(4,0,0)
+void GtkSalFrame::signalDragLeave(GtkDropTargetAsync* pDest, GdkDrop* /*drop*/, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return;
+ pThis->m_pDropTarget->signalDragLeave(gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(pDest)));
+}
+#else
+void GtkSalFrame::signalDragLeave(GtkWidget* pWidget, GdkDragContext* /*context*/, guint /*time*/, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return;
+ pThis->m_pDropTarget->signalDragLeave(pWidget);
+}
+#endif
+
+static gboolean lcl_deferred_dragExit(gpointer user_data)
+{
+ GtkInstDropTarget* pThis = static_cast<GtkInstDropTarget*>(user_data);
+ css::datatransfer::dnd::DropTargetEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(pThis);
+ pThis->fire_dragExit(aEvent);
+ return false;
+}
+
+void GtkInstDropTarget::signalDragLeave(GtkWidget *pWidget)
+{
+ m_bInDrag = false;
+
+ GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) : pWidget;
+#if !GTK_CHECK_VERSION(4,0,0)
+ gtk_drag_unhighlight(pHighlightWidget);
+#else
+ gtk_widget_unset_state_flags(pHighlightWidget, GTK_STATE_FLAG_DROP_ACTIVE);
+#endif
+
+ // defer fire_dragExit, since gtk also sends a drag-leave before the drop, while
+ // LO expect to either handle the drop or the exit... at least in Writer.
+ // but since we don't know there will be a drop following the leave, defer the
+ // exit handling to an idle.
+ g_idle_add(lcl_deferred_dragExit, this);
+}
+
+void GtkSalFrame::signalDestroy( GtkWidget* pObj, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if( pObj != pThis->m_pWindow )
+ return;
+
+ pThis->m_aDamageHandler.damaged = nullptr;
+ pThis->m_aDamageHandler.handle = nullptr;
+ if (pThis->m_pSurface)
+ cairo_surface_set_user_data(pThis->m_pSurface, SvpSalGraphics::getDamageKey(), nullptr, nullptr);
+ pThis->m_pFixedContainer = nullptr;
+ pThis->m_pDrawingArea = nullptr;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ pThis->m_pEventBox = nullptr;
+#endif
+ pThis->m_pTopLevelGrid = nullptr;
+ pThis->m_pWindow = nullptr;
+ pThis->m_xFrameWeld.reset();
+ pThis->InvalidateGraphics();
+}
+
+// GtkSalFrame::IMHandler
+
+GtkSalFrame::IMHandler::IMHandler( GtkSalFrame* pFrame )
+: m_pFrame(pFrame),
+ m_nPrevKeyPresses( 0 ),
+ m_pIMContext( nullptr ),
+ m_bFocused( true ),
+ m_bPreeditJustChanged( false )
+{
+ m_aInputEvent.mpTextAttr = nullptr;
+ createIMContext();
+}
+
+GtkSalFrame::IMHandler::~IMHandler()
+{
+ // cancel an eventual event posted to begin preedit again
+ GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
+ deleteIMContext();
+}
+
+void GtkSalFrame::IMHandler::createIMContext()
+{
+ if( m_pIMContext )
+ return;
+
+ m_pIMContext = gtk_im_multicontext_new ();
+ g_signal_connect( m_pIMContext, "commit",
+ G_CALLBACK (signalIMCommit), this );
+ g_signal_connect( m_pIMContext, "preedit_changed",
+ G_CALLBACK (signalIMPreeditChanged), this );
+ g_signal_connect( m_pIMContext, "retrieve_surrounding",
+ G_CALLBACK (signalIMRetrieveSurrounding), this );
+ g_signal_connect( m_pIMContext, "delete_surrounding",
+ G_CALLBACK (signalIMDeleteSurrounding), this );
+ g_signal_connect( m_pIMContext, "preedit_start",
+ G_CALLBACK (signalIMPreeditStart), this );
+ g_signal_connect( m_pIMContext, "preedit_end",
+ G_CALLBACK (signalIMPreeditEnd), this );
+
+ GetGenericUnixSalData()->ErrorTrapPush();
+ im_context_set_client_widget(m_pIMContext, m_pFrame->getMouseEventWidget());
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_event_controller_key_set_im_context(m_pFrame->m_pKeyController, m_pIMContext);
+#endif
+ gtk_im_context_focus_in( m_pIMContext );
+ GetGenericUnixSalData()->ErrorTrapPop();
+ m_bFocused = true;
+
+}
+
+void GtkSalFrame::IMHandler::deleteIMContext()
+{
+ if( !m_pIMContext )
+ return;
+
+ // first give IC a chance to deinitialize
+ GetGenericUnixSalData()->ErrorTrapPush();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_event_controller_key_set_im_context(m_pFrame->m_pKeyController, nullptr);
+#endif
+ im_context_set_client_widget(m_pIMContext, nullptr);
+ GetGenericUnixSalData()->ErrorTrapPop();
+ // destroy old IC
+ g_object_unref( m_pIMContext );
+ m_pIMContext = nullptr;
+}
+
+void GtkSalFrame::IMHandler::doCallEndExtTextInput()
+{
+ m_aInputEvent.mpTextAttr = nullptr;
+ m_pFrame->CallCallbackExc( SalEvent::EndExtTextInput, nullptr );
+}
+
+void GtkSalFrame::IMHandler::updateIMSpotLocation()
+{
+ SalExtTextInputPosEvent aPosEvent;
+ m_pFrame->CallCallbackExc( SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent) );
+ GdkRectangle aArea;
+ aArea.x = aPosEvent.mnX;
+ aArea.y = aPosEvent.mnY;
+ aArea.width = aPosEvent.mnWidth;
+ aArea.height = aPosEvent.mnHeight;
+ GetGenericUnixSalData()->ErrorTrapPush();
+ gtk_im_context_set_cursor_location( m_pIMContext, &aArea );
+ GetGenericUnixSalData()->ErrorTrapPop();
+}
+
+void GtkSalFrame::IMHandler::sendEmptyCommit()
+{
+ vcl::DeletionListener aDel( m_pFrame );
+
+ SalExtTextInputEvent aEmptyEv;
+ aEmptyEv.mpTextAttr = nullptr;
+ aEmptyEv.maText.clear();
+ aEmptyEv.mnCursorPos = 0;
+ aEmptyEv.mnCursorFlags = 0;
+ m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&aEmptyEv) );
+ if( ! aDel.isDeleted() )
+ m_pFrame->CallCallbackExc( SalEvent::EndExtTextInput, nullptr );
+}
+
+void GtkSalFrame::IMHandler::endExtTextInput( EndExtTextInputFlags /*nFlags*/ )
+{
+ gtk_im_context_reset ( m_pIMContext );
+
+ if( !m_aInputEvent.mpTextAttr )
+ return;
+
+ vcl::DeletionListener aDel( m_pFrame );
+ // delete preedit in sal (commit an empty string)
+ sendEmptyCommit();
+ if( ! aDel.isDeleted() )
+ {
+ // mark previous preedit state again (will e.g. be sent at focus gain)
+ m_aInputEvent.mpTextAttr = m_aInputFlags.data();
+ if( m_bFocused )
+ {
+ // begin preedit again
+ GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
+ }
+ }
+}
+
+void GtkSalFrame::IMHandler::focusChanged( bool bFocusIn )
+{
+ m_bFocused = bFocusIn;
+ if( bFocusIn )
+ {
+ GetGenericUnixSalData()->ErrorTrapPush();
+ gtk_im_context_focus_in( m_pIMContext );
+ GetGenericUnixSalData()->ErrorTrapPop();
+ if( m_aInputEvent.mpTextAttr )
+ {
+ sendEmptyCommit();
+ // begin preedit again
+ GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
+ }
+ }
+ else
+ {
+ GetGenericUnixSalData()->ErrorTrapPush();
+ gtk_im_context_focus_out( m_pIMContext );
+ GetGenericUnixSalData()->ErrorTrapPop();
+ // cancel an eventual event posted to begin preedit again
+ GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
+ }
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+bool GtkSalFrame::IMHandler::handleKeyEvent( GdkEventKey* pEvent )
+{
+ vcl::DeletionListener aDel( m_pFrame );
+
+ if( pEvent->type == GDK_KEY_PRESS )
+ {
+ // Add this key press event to the list of previous key presses
+ // to which we compare key release events. If a later key release
+ // event has a matching key press event in this list, we swallow
+ // the key release because some GTK Input Methods don't swallow it
+ // for us.
+ m_aPrevKeyPresses.emplace_back(pEvent );
+ m_nPrevKeyPresses++;
+
+ // Also pop off the earliest key press event if there are more than 10
+ // already.
+ while (m_nPrevKeyPresses > 10)
+ {
+ m_aPrevKeyPresses.pop_front();
+ m_nPrevKeyPresses--;
+ }
+
+ GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) );
+
+ // #i51353# update spot location on every key input since we cannot
+ // know which key may activate a preedit choice window
+ updateIMSpotLocation();
+ if( aDel.isDeleted() )
+ return true;
+
+ bool bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
+ g_object_unref( pRef );
+
+ if( aDel.isDeleted() )
+ return true;
+
+ m_bPreeditJustChanged = false;
+
+ if( bResult )
+ return true;
+ else
+ {
+ SAL_WARN_IF( m_nPrevKeyPresses <= 0, "vcl.gtk3", "key press has vanished !" );
+ if( ! m_aPrevKeyPresses.empty() ) // sanity check
+ {
+ // event was not swallowed, do not filter a following
+ // key release event
+ // note: this relies on gtk_im_context_filter_keypress
+ // returning without calling a handler (in the "not swallowed"
+ // case ) which might change the previous key press list so
+ // we would pop the wrong event here
+ m_aPrevKeyPresses.pop_back();
+ m_nPrevKeyPresses--;
+ }
+ }
+ }
+
+ // Determine if we got an earlier key press event corresponding to this key release
+ if (pEvent->type == GDK_KEY_RELEASE)
+ {
+ GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) );
+ bool bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
+ g_object_unref( pRef );
+
+ if( aDel.isDeleted() )
+ return true;
+
+ m_bPreeditJustChanged = false;
+
+ auto iter = std::find(m_aPrevKeyPresses.begin(), m_aPrevKeyPresses.end(), pEvent);
+ // If we found a corresponding previous key press event, swallow the release
+ // and remove the earlier key press from our list
+ if (iter != m_aPrevKeyPresses.end())
+ {
+ m_aPrevKeyPresses.erase(iter);
+ m_nPrevKeyPresses--;
+ return true;
+ }
+
+ if( bResult )
+ return true;
+ }
+
+ return false;
+}
+
+/* FIXME:
+* #122282# still more hacking: some IMEs never start a preedit but simply commit
+* in this case we cannot commit a single character. Workaround: do not do the
+* single key hack for enter or space if the unicode committed does not match
+*/
+
+static bool checkSingleKeyCommitHack( guint keyval, sal_Unicode cCode )
+{
+ bool bRet = true;
+ switch( keyval )
+ {
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_Return:
+ if( cCode != '\n' && cCode != '\r' )
+ bRet = false;
+ break;
+ case GDK_KEY_space:
+ case GDK_KEY_KP_Space:
+ if( cCode != ' ' )
+ bRet = false;
+ break;
+ default:
+ break;
+ }
+ return bRet;
+}
+
+void GtkSalFrame::IMHandler::signalIMCommit( GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler )
+{
+ GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
+
+ SolarMutexGuard aGuard;
+ vcl::DeletionListener aDel( pThis->m_pFrame );
+ {
+ const bool bWasPreedit =
+ (pThis->m_aInputEvent.mpTextAttr != nullptr) ||
+ pThis->m_bPreeditJustChanged;
+
+ pThis->m_aInputEvent.mpTextAttr = nullptr;
+ pThis->m_aInputEvent.maText = OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 );
+ pThis->m_aInputEvent.mnCursorPos = pThis->m_aInputEvent.maText.getLength();
+ pThis->m_aInputEvent.mnCursorFlags = 0;
+
+ pThis->m_aInputFlags.clear();
+
+ /* necessary HACK: all keyboard input comes in here as soon as an IMContext is set
+ * which is logical and consequent. But since even simple input like
+ * <space> comes through the commit signal instead of signalKey
+ * and all kinds of windows only implement KeyInput (e.g. PushButtons,
+ * RadioButtons and a lot of other Controls), will send a single
+ * KeyInput/KeyUp sequence instead of an ExtText event if there
+ * never was a preedit and the text is only one character.
+ *
+ * In this case there the last ExtText event must have been
+ * SalEvent::EndExtTextInput, either because of a regular commit
+ * or because there never was a preedit.
+ */
+ bool bSingleCommit = false;
+ if( ! bWasPreedit
+ && pThis->m_aInputEvent.maText.getLength() == 1
+ && ! pThis->m_aPrevKeyPresses.empty()
+ )
+ {
+ const PreviousKeyPress& rKP = pThis->m_aPrevKeyPresses.back();
+ sal_Unicode aOrigCode = pThis->m_aInputEvent.maText[0];
+
+ if( checkSingleKeyCommitHack( rKP.keyval, aOrigCode ) )
+ {
+ pThis->m_pFrame->doKeyCallback( rKP.state, rKP.keyval, rKP.hardware_keycode, rKP.group, aOrigCode, true, true );
+ bSingleCommit = true;
+ }
+ }
+ if( ! bSingleCommit )
+ {
+ pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
+ if( ! aDel.isDeleted() )
+ pThis->doCallEndExtTextInput();
+ }
+ if( ! aDel.isDeleted() )
+ {
+ // reset input event
+ pThis->m_aInputEvent.maText.clear();
+ pThis->m_aInputEvent.mnCursorPos = 0;
+ pThis->updateIMSpotLocation();
+ }
+ }
+}
+#else
+void GtkSalFrame::IMHandler::signalIMCommit( GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler )
+{
+ GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
+
+ SolarMutexGuard aGuard;
+ vcl::DeletionListener aDel( pThis->m_pFrame );
+ {
+#if 0
+ const bool bWasPreedit =
+ (pThis->m_aInputEvent.mpTextAttr != nullptr) ||
+ pThis->m_bPreeditJustChanged;
+#endif
+
+ pThis->m_aInputEvent.mpTextAttr = nullptr;
+ pThis->m_aInputEvent.maText = OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 );
+ pThis->m_aInputEvent.mnCursorPos = pThis->m_aInputEvent.maText.getLength();
+ pThis->m_aInputEvent.mnCursorFlags = 0;
+
+ pThis->m_aInputFlags.clear();
+
+ /* necessary HACK: all keyboard input comes in here as soon as an IMContext is set
+ * which is logical and consequent. But since even simple input like
+ * <space> comes through the commit signal instead of signalKey
+ * and all kinds of windows only implement KeyInput (e.g. PushButtons,
+ * RadioButtons and a lot of other Controls), will send a single
+ * KeyInput/KeyUp sequence instead of an ExtText event if there
+ * never was a preedit and the text is only one character.
+ *
+ * In this case there the last ExtText event must have been
+ * SalEvent::EndExtTextInput, either because of a regular commit
+ * or because there never was a preedit.
+ */
+ bool bSingleCommit = false;
+#if 0
+ // TODO this needs a rethink to work again if necessary
+ if( ! bWasPreedit
+ && pThis->m_aInputEvent.maText.getLength() == 1
+ && ! pThis->m_aPrevKeyPresses.empty()
+ )
+ {
+ const PreviousKeyPress& rKP = pThis->m_aPrevKeyPresses.back();
+ sal_Unicode aOrigCode = pThis->m_aInputEvent.maText[0];
+
+ if( checkSingleKeyCommitHack( rKP.keyval, aOrigCode ) )
+ {
+ pThis->m_pFrame->doKeyCallback( rKP.state, rKP.keyval, rKP.hardware_keycode, rKP.group, aOrigCode, true, true );
+ bSingleCommit = true;
+ }
+ }
+#endif
+ if( ! bSingleCommit )
+ {
+ pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
+ if( ! aDel.isDeleted() )
+ pThis->doCallEndExtTextInput();
+ }
+ if( ! aDel.isDeleted() )
+ {
+ // reset input event
+ pThis->m_aInputEvent.maText.clear();
+ pThis->m_aInputEvent.mnCursorPos = 0;
+ pThis->updateIMSpotLocation();
+ }
+ }
+}
+#endif
+
+OUString GtkSalFrame::GetPreeditDetails(GtkIMContext* pIMContext, std::vector<ExtTextInputAttr>& rInputFlags, sal_Int32& rCursorPos, sal_uInt8& rCursorFlags)
+{
+ char* pText = nullptr;
+ PangoAttrList* pAttrs = nullptr;
+ gint nCursorPos = 0;
+
+ gtk_im_context_get_preedit_string( pIMContext,
+ &pText,
+ &pAttrs,
+ &nCursorPos );
+
+ gint nUtf8Len = pText ? strlen(pText) : 0;
+ OUString sText = pText ? OUString(pText, nUtf8Len, RTL_TEXTENCODING_UTF8) : OUString();
+
+ std::vector<sal_Int32> aUtf16Offsets;
+ for (sal_Int32 nUtf16Offset = 0; nUtf16Offset < sText.getLength(); sText.iterateCodePoints(&nUtf16Offset))
+ aUtf16Offsets.push_back(nUtf16Offset);
+
+ sal_Int32 nUtf32Len = aUtf16Offsets.size();
+ // from the above loop filling aUtf16Offsets, we know that its size() fits into sal_Int32
+ aUtf16Offsets.push_back(sText.getLength());
+
+ // sanitize the CurPos which is in utf-32
+ if (nCursorPos < 0)
+ nCursorPos = 0;
+ else if (nCursorPos > nUtf32Len)
+ nCursorPos = nUtf32Len;
+
+ rCursorPos = aUtf16Offsets[nCursorPos];
+ rCursorFlags = 0;
+
+ rInputFlags.resize(std::max(1, static_cast<int>(sText.getLength())), ExtTextInputAttr::NONE);
+
+ PangoAttrIterator *iter = pango_attr_list_get_iterator(pAttrs);
+ do
+ {
+ GSList *attr_list = nullptr;
+ GSList *tmp_list = nullptr;
+ gint nUtf8Start, nUtf8End;
+ ExtTextInputAttr sal_attr = ExtTextInputAttr::NONE;
+
+ // docs say... "Get the range of the current segment ... the stored
+ // return values are signed, not unsigned like the values in
+ // PangoAttribute", which implies that the units are otherwise the same
+ // as that of PangoAttribute whose docs state these units are "in
+ // bytes"
+ // so this is the utf8 range
+ pango_attr_iterator_range(iter, &nUtf8Start, &nUtf8End);
+
+ // sanitize the utf8 range
+ nUtf8Start = std::min(nUtf8Start, nUtf8Len);
+ nUtf8End = std::min(nUtf8End, nUtf8Len);
+ if (nUtf8Start >= nUtf8End)
+ continue;
+
+ // get the utf32 range
+ sal_Int32 nUtf32Start = g_utf8_pointer_to_offset(pText, pText + nUtf8Start);
+ sal_Int32 nUtf32End = g_utf8_pointer_to_offset(pText, pText + nUtf8End);
+
+ // sanitize the utf32 range
+ nUtf32Start = std::min(nUtf32Start, nUtf32Len);
+ nUtf32End = std::min(nUtf32End, nUtf32Len);
+ if (nUtf32Start >= nUtf32End)
+ continue;
+
+ tmp_list = attr_list = pango_attr_iterator_get_attrs (iter);
+ while (tmp_list)
+ {
+ PangoAttribute *pango_attr = static_cast<PangoAttribute *>(tmp_list->data);
+
+ switch (pango_attr->klass->type)
+ {
+ case PANGO_ATTR_BACKGROUND:
+ sal_attr |= ExtTextInputAttr::Highlight;
+ rCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
+ break;
+ case PANGO_ATTR_UNDERLINE:
+ {
+ PangoAttrInt* pango_underline = reinterpret_cast<PangoAttrInt*>(pango_attr);
+ switch (pango_underline->value)
+ {
+ case PANGO_UNDERLINE_NONE:
+ break;
+ case PANGO_UNDERLINE_DOUBLE:
+ sal_attr |= ExtTextInputAttr::DoubleUnderline;
+ break;
+ default:
+ sal_attr |= ExtTextInputAttr::Underline;
+ break;
+ }
+ break;
+ }
+ case PANGO_ATTR_STRIKETHROUGH:
+ sal_attr |= ExtTextInputAttr::RedText;
+ break;
+ default:
+ break;
+ }
+ pango_attribute_destroy (pango_attr);
+ tmp_list = tmp_list->next;
+ }
+ if (!attr_list)
+ sal_attr |= ExtTextInputAttr::Underline;
+ g_slist_free (attr_list);
+
+ // Set the sal attributes on our text
+ // rhbz#1648281 apply over our utf-16 range derived from the input utf-32 range
+ for (sal_Int32 i = aUtf16Offsets[nUtf32Start]; i < aUtf16Offsets[nUtf32End]; ++i)
+ {
+ SAL_WARN_IF(i >= static_cast<int>(rInputFlags.size()),
+ "vcl.gtk3", "pango attrib out of range. Broken range: "
+ << aUtf16Offsets[nUtf32Start] << "," << aUtf16Offsets[nUtf32End] << " Legal range: 0,"
+ << rInputFlags.size());
+ if (i >= static_cast<int>(rInputFlags.size()))
+ continue;
+ rInputFlags[i] |= sal_attr;
+ }
+ } while (pango_attr_iterator_next (iter));
+ pango_attr_iterator_destroy(iter);
+
+ g_free( pText );
+ pango_attr_list_unref( pAttrs );
+
+ return sText;
+}
+
+void GtkSalFrame::IMHandler::signalIMPreeditChanged( GtkIMContext* pIMContext, gpointer im_handler )
+{
+ GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
+
+ sal_Int32 nCursorPos(0);
+ sal_uInt8 nCursorFlags(0);
+ std::vector<ExtTextInputAttr> aInputFlags;
+ OUString sText = GtkSalFrame::GetPreeditDetails(pIMContext, aInputFlags, nCursorPos, nCursorFlags);
+ if (sText.isEmpty() && pThis->m_aInputEvent.maText.isEmpty())
+ {
+ // change from nothing to nothing -> do not start preedit
+ // e.g. this will activate input into a calc cell without
+ // user input
+ return;
+ }
+
+ pThis->m_bPreeditJustChanged = true;
+
+ bool bEndPreedit = sText.isEmpty() && pThis->m_aInputEvent.mpTextAttr != nullptr;
+ pThis->m_aInputEvent.maText = sText;
+ pThis->m_aInputEvent.mnCursorPos = nCursorPos;
+ pThis->m_aInputEvent.mnCursorFlags = nCursorFlags;
+ pThis->m_aInputFlags = aInputFlags;
+ pThis->m_aInputEvent.mpTextAttr = pThis->m_aInputFlags.data();
+
+ SolarMutexGuard aGuard;
+ vcl::DeletionListener aDel( pThis->m_pFrame );
+
+ pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
+ if( bEndPreedit && ! aDel.isDeleted() )
+ pThis->doCallEndExtTextInput();
+ if( ! aDel.isDeleted() )
+ pThis->updateIMSpotLocation();
+}
+
+void GtkSalFrame::IMHandler::signalIMPreeditStart( GtkIMContext*, gpointer /*im_handler*/ )
+{
+}
+
+void GtkSalFrame::IMHandler::signalIMPreeditEnd( GtkIMContext*, gpointer im_handler )
+{
+ GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
+
+ pThis->m_bPreeditJustChanged = true;
+
+ SolarMutexGuard aGuard;
+ vcl::DeletionListener aDel( pThis->m_pFrame );
+ pThis->doCallEndExtTextInput();
+ if( ! aDel.isDeleted() )
+ pThis->updateIMSpotLocation();
+}
+
+gboolean GtkSalFrame::IMHandler::signalIMRetrieveSurrounding( GtkIMContext* pContext, gpointer im_handler )
+{
+ GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
+
+ SalSurroundingTextRequestEvent aEvt;
+ aEvt.maText.clear();
+ aEvt.mnStart = aEvt.mnEnd = 0;
+
+ SolarMutexGuard aGuard;
+ pThis->m_pFrame->CallCallback(SalEvent::SurroundingTextRequest, &aEvt);
+
+ OString sUTF = OUStringToOString(aEvt.maText, RTL_TEXTENCODING_UTF8);
+ std::u16string_view sCursorText(aEvt.maText.subView(0, aEvt.mnStart));
+ gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
+ OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
+ return true;
+}
+
+gboolean GtkSalFrame::IMHandler::signalIMDeleteSurrounding( GtkIMContext*, gint offset, gint nchars,
+ gpointer im_handler )
+{
+ GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
+
+ // First get the surrounding text
+ SalSurroundingTextRequestEvent aSurroundingTextEvt;
+ aSurroundingTextEvt.maText.clear();
+ aSurroundingTextEvt.mnStart = aSurroundingTextEvt.mnEnd = 0;
+
+ SolarMutexGuard aGuard;
+ pThis->m_pFrame->CallCallback(SalEvent::SurroundingTextRequest, &aSurroundingTextEvt);
+
+ // Turn offset, nchars into a utf-16 selection
+ Selection aSelection = SalFrame::CalcDeleteSurroundingSelection(aSurroundingTextEvt.maText,
+ aSurroundingTextEvt.mnStart,
+ offset, nchars);
+ Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32);
+ if (aSelection == aInvalid)
+ return false;
+
+ SalSurroundingTextSelectionChangeEvent aEvt;
+ aEvt.mnStart = aSelection.Min();
+ aEvt.mnEnd = aSelection.Max();
+
+ pThis->m_pFrame->CallCallback(SalEvent::DeleteSurroundingTextRequest, &aEvt);
+
+ aSelection = Selection(aEvt.mnStart, aEvt.mnEnd);
+ if (aSelection == aInvalid)
+ return false;
+
+ return true;
+}
+
+Size GtkSalDisplay::GetScreenSize( int nDisplayScreen )
+{
+ tools::Rectangle aRect = m_pSys->GetDisplayScreenPosSizePixel( nDisplayScreen );
+ return Size( aRect.GetWidth(), aRect.GetHeight() );
+}
+
+sal_uIntPtr GtkSalFrame::GetNativeWindowHandle(GtkWidget *pWidget)
+{
+ GdkSurface* pSurface = widget_get_surface(pWidget);
+ GdkDisplay *pDisplay = getGdkDisplay();
+
+#if defined(GDK_WINDOWING_X11)
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ return gdk_x11_surface_get_xid(pSurface);
+ }
+#endif
+
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
+ {
+ return reinterpret_cast<sal_uIntPtr>(gdk_wayland_surface_get_wl_surface(pSurface));
+ }
+#endif
+
+ return 0;
+}
+
+void GtkInstDragSource::set_datatransfer(const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
+ const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener)
+{
+ m_xListener = rListener;
+ m_xTrans = rTrans;
+}
+
+void GtkInstDragSource::setActiveDragSource()
+{
+ // For LibreOffice internal D&D we provide the Transferable without Gtk
+ // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
+ g_ActiveDragSource = this;
+ g_DropSuccessSet = false;
+ g_DropSuccess = false;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+std::vector<GtkTargetEntry> GtkInstDragSource::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
+{
+ return m_aConversionHelper.FormatsToGtk(rFormats);
+}
+#endif
+
+void GtkInstDragSource::startDrag(const datatransfer::dnd::DragGestureEvent& rEvent,
+ sal_Int8 sourceActions, sal_Int32 /*cursor*/, sal_Int32 /*image*/,
+ const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
+ const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener)
+{
+ set_datatransfer(rTrans, rListener);
+
+ if (m_pFrame)
+ {
+ setActiveDragSource();
+
+ m_pFrame->startDrag(rEvent, rTrans, m_aConversionHelper ,VclToGdk(sourceActions));
+ }
+ else
+ dragFailed();
+}
+
+void GtkSalFrame::startDrag(const css::datatransfer::dnd::DragGestureEvent& rEvent,
+ const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
+ VclToGtkHelper& rConversionHelper,
+ GdkDragAction sourceActions)
+{
+ SolarMutexGuard aGuard;
+
+ assert(m_pDragSource);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+
+ GdkSeat *pSeat = gdk_display_get_default_seat(getGdkDisplay());
+ GdkDrag* pDrag = gdk_drag_begin(widget_get_surface(getMouseEventWidget()),
+ gdk_seat_get_pointer(pSeat),
+ transerable_content_new(&rConversionHelper, rTrans.get()),
+ sourceActions,
+ rEvent.DragOriginX, rEvent.DragOriginY);
+
+ g_signal_connect(G_OBJECT(pDrag), "drop-performed", G_CALLBACK(signalDragEnd), this);
+ g_signal_connect(G_OBJECT(pDrag), "cancel", G_CALLBACK(signalDragFailed), this);
+ g_signal_connect(G_OBJECT(pDrag), "dnd-finished", G_CALLBACK(signalDragDelete), this);
+
+#else
+
+ auto aFormats = rTrans->getTransferDataFlavors();
+ auto aGtkTargets = rConversionHelper.FormatsToGtk(aFormats);
+
+ GtkTargetList *pTargetList = gtk_target_list_new(aGtkTargets.data(), aGtkTargets.size());
+
+ gint nDragButton = 1; // default to left button
+ css::awt::MouseEvent aEvent;
+ if (rEvent.Event >>= aEvent)
+ {
+ if (aEvent.Buttons & css::awt::MouseButton::LEFT )
+ nDragButton = 1;
+ else if (aEvent.Buttons & css::awt::MouseButton::RIGHT)
+ nDragButton = 3;
+ else if (aEvent.Buttons & css::awt::MouseButton::MIDDLE)
+ nDragButton = 2;
+ }
+
+ GdkEvent aFakeEvent;
+ memset(&aFakeEvent, 0, sizeof(GdkEvent));
+ aFakeEvent.type = GDK_BUTTON_PRESS;
+ aFakeEvent.button.window = widget_get_surface(getMouseEventWidget());
+ aFakeEvent.button.time = GDK_CURRENT_TIME;
+
+ aFakeEvent.button.device = gtk_get_current_event_device();
+ // if no current event to determine device, or (tdf#140272) the device will be unsuitable then find an
+ // appropiate device to use.
+ if (!aFakeEvent.button.device || !gdk_device_get_window_at_position(aFakeEvent.button.device, nullptr, nullptr))
+ {
+ GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(getGdkDisplay());
+ GList* pDevices = gdk_device_manager_list_devices(pDeviceManager, GDK_DEVICE_TYPE_MASTER);
+ for (GList* pEntry = pDevices; pEntry; pEntry = pEntry->next)
+ {
+ GdkDevice* pDevice = static_cast<GdkDevice*>(pEntry->data);
+ if (gdk_device_get_source(pDevice) == GDK_SOURCE_KEYBOARD)
+ continue;
+ if (gdk_device_get_window_at_position(pDevice, nullptr, nullptr))
+ {
+ aFakeEvent.button.device = pDevice;
+ break;
+ }
+ }
+ g_list_free(pDevices);
+ }
+
+ GdkDragContext *pDrag;
+ if (!aFakeEvent.button.device || !gdk_device_get_window_at_position(aFakeEvent.button.device, nullptr, nullptr))
+ pDrag = nullptr;
+ else
+ pDrag = gtk_drag_begin_with_coordinates(getMouseEventWidget(),
+ pTargetList,
+ sourceActions,
+ nDragButton,
+ &aFakeEvent,
+ rEvent.DragOriginX,
+ rEvent.DragOriginY);
+
+ gtk_target_list_unref(pTargetList);
+
+ for (auto &a : aGtkTargets)
+ g_free(a.target);
+#endif
+
+ if (!pDrag)
+ m_pDragSource->dragFailed();
+}
+
+void GtkInstDragSource::dragFailed()
+{
+ if (m_xListener.is())
+ {
+ datatransfer::dnd::DragSourceDropEvent aEv;
+ aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_NONE;
+ aEv.DropSuccess = false;
+ auto xListener = m_xListener;
+ m_xListener.clear();
+ xListener->dragDropEnd(aEv);
+ }
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalDragFailed(GdkDrag* /*drag*/, GdkDragCancelReason /*reason*/, gpointer frame)
+#else
+gboolean GtkSalFrame::signalDragFailed(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkDragResult /*result*/, gpointer frame)
+#endif
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (pThis->m_pDragSource)
+ pThis->m_pDragSource->dragFailed();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return false;
+#endif
+}
+
+void GtkInstDragSource::dragDelete()
+{
+ if (m_xListener.is())
+ {
+ datatransfer::dnd::DragSourceDropEvent aEv;
+ aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ aEv.DropSuccess = true;
+ auto xListener = m_xListener;
+ m_xListener.clear();
+ xListener->dragDropEnd(aEv);
+ }
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalDragDelete(GdkDrag* /*context*/, gpointer frame)
+#else
+void GtkSalFrame::signalDragDelete(GtkWidget* /*widget*/, GdkDragContext* /*context*/, gpointer frame)
+#endif
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDragSource)
+ return;
+ pThis->m_pDragSource->dragDelete();
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void GtkInstDragSource::dragEnd(GdkDrag* context)
+#else
+void GtkInstDragSource::dragEnd(GdkDragContext* context)
+#endif
+{
+ if (m_xListener.is())
+ {
+ datatransfer::dnd::DragSourceDropEvent aEv;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ aEv.DropAction = GdkToVcl(gdk_drag_get_selected_action(context));
+#else
+ aEv.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
+#endif
+ // an internal drop can accept the drop but fail with dropComplete( false )
+ // this is different than the GTK API
+ if (g_DropSuccessSet)
+ aEv.DropSuccess = g_DropSuccess;
+ else
+ aEv.DropSuccess = true;
+ auto xListener = m_xListener;
+ m_xListener.clear();
+ xListener->dragDropEnd(aEv);
+ }
+ g_ActiveDragSource = nullptr;
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::signalDragEnd(GdkDrag* context, gpointer frame)
+#else
+void GtkSalFrame::signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer frame)
+#endif
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDragSource)
+ return;
+ pThis->m_pDragSource->dragEnd(context);
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void GtkInstDragSource::dragDataGet(GtkSelectionData *data, guint info)
+{
+ m_aConversionHelper.setSelectionData(m_xTrans, data, info);
+}
+
+void GtkSalFrame::signalDragDataGet(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkSelectionData *data, guint info,
+ guint /*time*/, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDragSource)
+ return;
+ pThis->m_pDragSource->dragDataGet(data, info);
+}
+#endif
+
+bool GtkSalFrame::CallCallbackExc(SalEvent nEvent, const void* pEvent) const
+{
+ SolarMutexGuard aGuard;
+ bool nRet = false;
+ try
+ {
+ nRet = CallCallback(nEvent, pEvent);
+ }
+ catch (...)
+ {
+ GetGtkSalData()->setException(std::current_exception());
+ }
+ return nRet;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalFrame::nopaint_container_resize_children(GtkContainer *pContainer)
+{
+ bool bOrigSalObjectSetPosSize = m_bSalObjectSetPosSize;
+ m_bSalObjectSetPosSize = true;
+ gtk_container_resize_children(pContainer);
+ m_bSalObjectSetPosSize = bOrigSalObjectSetPosSize;
+}
+#endif
+
+GdkEvent* GtkSalFrame::makeFakeKeyPress(GtkWidget* pWidget)
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkEvent *event = gdk_event_new(GDK_KEY_PRESS);
+ event->key.window = GDK_WINDOW(g_object_ref(widget_get_surface(pWidget)));
+
+ GdkSeat *seat = gdk_display_get_default_seat(gtk_widget_get_display(pWidget));
+ gdk_event_set_device(event, gdk_seat_get_keyboard(seat));
+
+ event->key.send_event = 1 /* TRUE */;
+ event->key.time = gtk_get_current_event_time();
+ event->key.state = 0;
+ event->key.keyval = 0;
+ event->key.length = 0;
+ event->key.string = nullptr;
+ event->key.hardware_keycode = 0;
+ event->key.group = 0;
+ event->key.is_modifier = false;
+ return event;
+#else
+ (void)pWidget;
+ return nullptr;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtkinst.cxx b/vcl/unx/gtk3/gtkinst.cxx
new file mode 100644
index 000000000..592641504
--- /dev/null
+++ b/vcl/unx/gtk3/gtkinst.cxx
@@ -0,0 +1,24113 @@
+/* -*- 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 <stack>
+#include <string.h>
+#include <string_view>
+
+#include <dndhelper.hxx>
+#include <osl/process.h>
+#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/atkbridge.hxx>
+#include <unx/gtk/gtksalmenu.hxx>
+#include <headless/svpvd.hxx>
+#include <headless/svpbmp.hxx>
+#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 <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/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 <com/sun/star/io/XInputStream.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/pngwrite.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)
+ // init gdk thread protection
+ bool const sup = g_thread_supported();
+ // extracted from the 'if' to avoid Clang -Wunreachable-code
+ if ( !sup )
+ g_thread_init( nullptr );
+
+ 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();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ InitAtkBridge();
+#endif
+
+ ImplSVData* pSVData = ImplGetSVData();
+#ifdef GTK_TOOLKIT_NAME
+ pSVData->maAppData.mxToolkitName = OUString(GTK_TOOLKIT_NAME);
+#else
+ pSVData->maAppData.mxToolkitName = OUString("gtk3");
+#endif
+
+ bNeedsInit = false;
+}
+
+GtkInstance::~GtkInstance()
+{
+ assert( nullptr == m_pTimer );
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ DeInitAtkBridge();
+#endif
+ 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*/,
+ const SystemGraphicsData* pGd )
+{
+ 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 = pGd ? static_cast<cairo_surface_t*>(pGd->pSurface) : nullptr;
+ std::unique_ptr<SalVirtualDevice> pNew(new SvpSalVirtualDevice(pSvpSalGraphics->getSurface(), pPreExistingTarget));
+ pNew->SetSize( nDX, nDY );
+ return pNew;
+}
+
+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 "com.sun.star.datatransfer.VclGtkClipboard";
+}
+
+Sequence< OUString > VclGtkClipboard::getSupportedServiceNames()
+{
+ Sequence<OUString> aRet { "com.sun.star.datatransfer.clipboard.SystemClipboard" };
+ 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::ClearableMutexGuard aGuard( m_aMutex );
+ m_pSetClipboardEvent = nullptr;
+ SetGtkClipboard();
+}
+
+void VclGtkClipboard::SyncGtkClipboard()
+{
+ osl::ClearableMutexGuard 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 = 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) ? OUString("CLIPBOARD") : OUString("PRIMARY");
+}
+
+sal_Int8 VclGtkClipboard::getRenderingCapabilities()
+{
+ return 0;
+}
+
+void VclGtkClipboard::addClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
+{
+ osl::ClearableMutexGuard aGuard( m_aMutex );
+
+ m_aListeners.push_back( listener );
+}
+
+void VclGtkClipboard::removeClipboardListener( const Reference< datatransfer::clipboard::XClipboardListener >& listener )
+{
+ osl::ClearableMutexGuard aGuard( m_aMutex );
+
+ m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), listener), m_aListeners.end());
+}
+
+Reference< XInterface > GtkInstance::CreateClipboard(const Sequence< Any >& arguments)
+{
+ if ( IsRunningUnitTest() )
+ return SalInstance::CreateClipboard( arguments );
+
+ OUString sel;
+ if (!arguments.hasElements()) {
+ sel = "CLIPBOARD";
+ } else if (arguments.getLength() != 1 || !(arguments[0] >>= sel)) {
+ throw css::lang::IllegalArgumentException(
+ "bad GtkInstance::CreateClipboard arguments",
+ 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<XInterface> xClipboard(static_cast<cppu::OWeakObject *>(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)
+{
+}
+
+OUString SAL_CALL GtkInstDropTarget::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.VclGtkDropTarget";
+}
+
+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 { "com.sun.star.datatransfer.dnd.GtkDropTarget" };
+ return aRet;
+}
+
+GtkInstDropTarget::~GtkInstDropTarget()
+{
+ if (m_pFrame)
+ m_pFrame->deregisterDropTarget(this);
+}
+
+void GtkInstDropTarget::deinitialize()
+{
+ m_pFrame = nullptr;
+ m_bActive = false;
+}
+
+void GtkInstDropTarget::initialize(const Sequence<Any>& rArguments)
+{
+ if (rArguments.getLength() < 2)
+ {
+ throw RuntimeException("DropTarget::initialize: Cannot install window event handler",
+ static_cast<OWeakObject*>(this));
+ }
+
+ sal_IntPtr nFrame = 0;
+ rArguments.getConstArray()[1] >>= nFrame;
+
+ if (!nFrame)
+ {
+ throw RuntimeException("DropTarget::initialize: missing SalFrame",
+ static_cast<OWeakObject*>(this));
+ }
+
+ m_pFrame = reinterpret_cast<GtkSalFrame*>(nFrame);
+ m_pFrame->registerDropTarget(this);
+ m_bActive = true;
+}
+
+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 );
+
+ m_aListeners.erase(std::remove(m_aListeners.begin(), m_aListeners.end(), xListener), m_aListeners.end());
+}
+
+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;
+}
+
+Reference<XInterface> GtkInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv)
+{
+ return vcl::X11DnDHelper(new GtkInstDropTarget(), pSysEnv->aShellWindow);
+}
+
+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;
+}
+
+void GtkInstDragSource::initialize(const css::uno::Sequence<css::uno::Any >& rArguments)
+{
+ if (rArguments.getLength() < 2)
+ {
+ throw RuntimeException("DragSource::initialize: Cannot install window event handler",
+ static_cast<OWeakObject*>(this));
+ }
+
+ sal_IntPtr nFrame = 0;
+ rArguments.getConstArray()[1] >>= nFrame;
+
+ if (!nFrame)
+ {
+ throw RuntimeException("DragSource::initialize: missing SalFrame",
+ static_cast<OWeakObject*>(this));
+ }
+
+ m_pFrame = reinterpret_cast<GtkSalFrame*>(nFrame);
+ m_pFrame->registerDragSource(this);
+}
+
+OUString SAL_CALL GtkInstDragSource::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.VclGtkDragSource";
+}
+
+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 { "com.sun.star.datatransfer.dnd.GtkDragSource" };
+ return aRet;
+}
+
+Reference<XInterface> GtkInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv)
+{
+ return vcl::X11DnDHelper(new GtkInstDragSource(), pSysEnv->aShellWindow);
+}
+
+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_show(pParent);
+ gtk_widget_show(m_pGLArea);
+#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, const OString& rHelpId)
+ {
+ gchar *helpid = g_strdup(rHelpId.getStr());
+ g_object_set_data_full(G_OBJECT(pWidget), "g-lo-helpid", helpid, g_free);
+ }
+
+ OString 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 OString(pStr, pStr ? strlen(pStr) : 0);
+ }
+
+ 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
+ tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pFrame->GetWindow(), rInRect);
+ aFloatRect.Move(-pFrame->maGeometry.nX, -pFrame->maGeometry.nY);
+
+ 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 (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_CHECK_VERSION(4, 0, 0)
+ if (!gtk_widget_get_realized(pWidget))
+ gtk_widget_realize(pWidget);
+ GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
+ GdkCursor *pCursor = pName ? gdk_cursor_new_from_name(pDisplay, pName) : nullptr;
+ widget_set_cursor(pWidget, pCursor);
+ gdk_display_flush(pDisplay);
+ if (pCursor)
+ g_object_unref(pCursor);
+#else
+ (void)pWidget;
+ (void)pName;
+#endif
+}
+
+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());
+}
+
+}
+
+OString 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 OString(pStr, pStr ? strlen(pStr) : 0);
+}
+
+void set_buildable_id(GtkBuildable* pWidget, const OString& rId)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkBuildableIface *iface = GTK_BUILDABLE_GET_IFACE(pWidget);
+ (*iface->set_id)(pWidget, rId.getStr());
+#else
+ gtk_buildable_set_name(pWidget, rId.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;
+
+ m_aFocusInHdl.Call(*this);
+ }
+
+ static gboolean signalMnemonicActivate(GtkWidget*, gboolean, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_mnemonic_activate();
+ }
+
+ bool signal_mnemonic_activate()
+ {
+ return m_aMnemonicActivateHdl.Call(*this);
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalFocusOut(GtkEventControllerFocus*, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_focus_in();
+ }
+#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;
+
+ m_aFocusOutHdl.Call(*this);
+ }
+
+ 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
+
+ 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)
+ int m_nPressStartX;
+ int m_nPressStartY;
+#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;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ int m_nGrabCount;
+ 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 && m_aMousePressHdl.Call(aMEvt))
+ gtk_gesture_set_state(GTK_GESTURE(pGesture), GTK_EVENT_SEQUENCE_CLAIMED);
+
+ if (nEventType == SalEvent::MouseButtonUp && m_aMouseReleaseHdl.Call(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;
+ }
+
+ 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;
+ }
+
+ /* 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;
+ }
+
+ 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 m_aMousePressHdl.Call(aMEvt);
+ }
+
+ if (!m_aMouseReleaseHdl.IsSet())
+ return false;
+ return m_aMouseReleaseHdl.Call(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 m_aMouseMotionHdl.Call(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);
+ }
+
+ 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);
+
+ m_aMouseMotionHdl.Call(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)
+ 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);
+#else
+ (void)context;
+#endif
+ return;
+ }
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (bUnsetDragIcon)
+ {
+ cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
+ gtk_drag_set_icon_surface(context, surface);
+ }
+#endif
+ 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);
+ }
+
+#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)
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_nGrabCount(0)
+ , m_pFocusController(nullptr)
+ , m_pClickController(nullptr)
+ , m_pMotionController(nullptr)
+ , m_pDragController(nullptr)
+ , m_pKeyController(nullptr)
+#endif
+ {
+ if (!bTakeOwnership)
+ g_object_ref(m_pWidget);
+
+ 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_show(m_pWidget);
+ }
+
+ virtual void hide() override
+ {
+ gtk_widget_hide(m_pWidget);
+ }
+
+ 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_grid_left_attach(int nAttach) override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ int row, width, height;
+ gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, nullptr, &row, &width, &height);
+ g_object_ref(m_pWidget);
+ gtk_grid_remove(GTK_GRID(pParent), m_pWidget);
+ gtk_grid_attach(GTK_GRID(pParent), m_pWidget, nAttach, row, width, height);
+ g_object_unref(m_pWidget);
+#else
+ gtk_container_child_set(GTK_CONTAINER(pParent), m_pWidget, "left-attach", nAttach, nullptr);
+#endif
+ }
+
+ virtual int get_grid_left_attach() const override
+ {
+ gint nAttach(0);
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, &nAttach, nullptr, nullptr, nullptr);
+#else
+ gtk_container_child_get(GTK_CONTAINER(pParent), m_pWidget, "left-attach", &nAttach, nullptr);
+#endif
+ return nAttach;
+ }
+
+ virtual void set_grid_width(int nCols) override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ int col, row, height;
+ gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, &col, &row, nullptr, &height);
+ g_object_ref(m_pWidget);
+ gtk_grid_remove(GTK_GRID(pParent), m_pWidget);
+ gtk_grid_attach(GTK_GRID(pParent), m_pWidget, col, row, nCols, height);
+ g_object_unref(m_pWidget);
+#else
+ gtk_container_child_set(GTK_CONTAINER(pParent), m_pWidget, "width", nCols, nullptr);
+#endif
+ }
+
+ virtual void set_grid_top_attach(int nAttach) override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ int col, width, height;
+ gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, &col, nullptr, &width, &height);
+ g_object_ref(m_pWidget);
+ gtk_grid_remove(GTK_GRID(pParent), m_pWidget);
+ gtk_grid_attach(GTK_GRID(pParent), m_pWidget, col, nAttach, width, height);
+ g_object_unref(m_pWidget);
+#else
+ gtk_container_child_set(GTK_CONTAINER(pParent), m_pWidget, "top-attach", nAttach, nullptr);
+#endif
+ }
+
+ virtual int get_grid_top_attach() const override
+ {
+ gint nAttach(0);
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_grid_query_child(GTK_GRID(pParent), m_pWidget, nullptr, &nAttach, nullptr, nullptr);
+#else
+ gtk_container_child_get(GTK_CONTAINER(pParent), m_pWidget, "top-attach", &nAttach, nullptr);
+#endif
+ return nAttach;
+ }
+
+ 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 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 std::unique_ptr<weld::Container> weld_parent() const override;
+
+ virtual OString get_buildable_name() const override
+ {
+ return ::get_buildable_id(GTK_BUILDABLE(m_pWidget));
+ }
+
+ virtual void set_buildable_name(const OString& rId) override
+ {
+ ::set_buildable_id(GTK_BUILDABLE(m_pWidget), rId);
+ }
+
+ virtual void set_help_id(const OString& rHelpId) override
+ {
+ ::set_help_id(m_pWidget, rHelpId);
+ }
+
+ virtual OString get_help_id() const override
+ {
+ OString sRet = ::get_help_id(m_pWidget);
+ if (sRet.isEmpty())
+ sRet = OString("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)
+ {
+ m_aSizeAllocateHdl.Call(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 m_aKeyPressHdl.Call(CreateKeyEvent(keyval, keycode, state, 0));
+ }
+ return false;
+ }
+
+ bool signal_key_release(guint keyval, guint keycode, GdkModifierType state)
+ {
+ if (m_aKeyReleaseHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ return m_aKeyReleaseHdl.Call(CreateKeyEvent(keyval, keycode, state, 0));
+ }
+ return false;
+ }
+#else
+
+ virtual bool do_signal_key_press(const GdkEventKey* pEvent)
+ {
+ if (m_aKeyPressHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ return m_aKeyPressHdl.Call(GtkToVcl(*pEvent));
+ }
+ return false;
+ }
+
+ virtual bool do_signal_key_release(const GdkEventKey* pEvent)
+ {
+ if (m_aKeyReleaseHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ return m_aKeyReleaseHdl.Call(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_add() override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ ++m_nGrabCount;
+#else
+ gtk_grab_add(m_pWidget);
+#endif
+ }
+
+ virtual bool has_grab() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return m_nGrabCount != 0;
+#else
+ return gtk_widget_has_grab(m_pWidget);
+#endif
+ }
+
+ virtual void grab_remove() override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ --m_nGrabCount;
+#else
+ gtk_grab_remove(m_pWidget);
+#endif
+ }
+
+ 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_toolbar_background() override
+ {
+ // no-op
+ }
+
+ virtual ~GtkInstanceWidget() override
+ {
+ 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 OString&)>& func) override;
+
+ virtual OUString strip_mnemonic(const OUString &rLabel) const override
+ {
+ return rLabel.replaceFirst("_", "");
+ }
+
+ 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_show(m_pWidget);
+ 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::DEFAULT));
+ 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_hide(m_pWidget);
+ if (!bAlreadyRealized)
+ gtk_widget_unrealize(m_pWidget);
+ }
+};
+
+}
+
+#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)
+ {
+ gchar* 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)
+ {
+ gchar* 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::TempFile> 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::TempFile> xRet(new utl::TempFile);
+ 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::TempFile> 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
+{
+ GdkPixbuf* getPixbuf(const css::uno::Reference<css::graphic::XGraphic>& rImage)
+ {
+ Image aImage(rImage);
+
+ 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(
+ "Compression", sal_Int32(1)) };
+
+ vcl::PNGWriter aWriter(aImage.GetBitmapEx(), &aFilterData);
+ aWriter.Write(aMemStm);
+
+ 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
+ // scaleable 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::TempFile> getImageFile(const css::uno::Reference<css::graphic::XGraphic>& rImage)
+ {
+ Image aImage(rImage);
+
+ OUString sStock(aImage.GetStock());
+ if (!sStock.isEmpty())
+ return get_icon_stream_as_file(sStock);
+
+ std::unique_ptr<utl::TempFile> xRet(new utl::TempFile);
+ 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(
+ "Compression", sal_Int32(1)) };
+ auto aBitmapEx = aImage.GetBitmapEx();
+ vcl::PNGWriter aWriter(aBitmapEx, &aFilterData);
+ aWriter.Write(*pStream);
+
+ xRet->CloseStream();
+ return xRet;
+ }
+
+ GdkPixbuf* getPixbuf(const VirtualDevice& rDevice)
+ {
+ Size aSize(rDevice.GetOutputSizePixel());
+ cairo_surface_t* orig_surface = get_underlying_cairo_surface(rDevice);
+ double m_fXScale, m_fYScale;
+ dl_cairo_surface_get_device_scale(orig_surface, &m_fXScale, &m_fYScale);
+
+ cairo_surface_t* surface;
+ if (m_fXScale != 1.0 || m_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;
+ }
+
+#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)
+ {
+ GtkWidget* pImage = nullptr;
+ if (auto xTempFile = getImageFile(rIcon))
+ 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))
+ 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))
+ 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);
+#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<OString, GtkMenuItem*> m_aMap;
+#else
+ GtkPopoverMenu* m_pMenu;
+
+ o3tl::sorted_vector<OString> m_aInsertedActions; // must outlive m_aActionEntries
+ std::map<OString, OString> m_aIdToAction;
+ std::set<OString> 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 OString& 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)
+ {
+ 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 OString& rId)
+ {
+ for (int i = 0, nCount = g_menu_model_get_n_items(pMenuModel); i < nCount; ++i)
+ {
+ OString sTarget;
+ char *id;
+ if (g_menu_model_get_item_attribute(pMenuModel, i, "target", "s", &id))
+ {
+ sTarget = OString(id);
+ 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);
+ OString aStr(pStr, nLength);
+ 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)
+ {
+ OString 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)
+ {
+ OString 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, 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 = OString(id);
+ 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());
+ pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new() : gtk_menu_item_new();
+ gtk_box_pack_start(pBox, pImage, true, 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), OUStringToOString(rId, RTL_TEXTENCODING_UTF8));
+ gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
+ gtk_widget_show(pItem);
+ 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), OUStringToOString(rId, RTL_TEXTENCODING_UTF8));
+ gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
+ gtk_widget_show(pItem);
+ 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 OString& 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 OString& rIdent, bool bSensitive)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup;
+ GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(pActionGroup), m_aIdToAction[rIdent].getStr());
+ g_simple_action_set_enabled(G_SIMPLE_ACTION(pAction), bSensitive);
+#else
+ gtk_widget_set_sensitive(GTK_WIDGET(m_aMap[rIdent]), bSensitive);
+#endif
+ }
+
+ bool get_item_sensitive(const OString& rIdent) const
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup;
+ GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(pActionGroup), m_aIdToAction.find(rIdent)->second.getStr());
+ return g_action_get_enabled(pAction);
+#else
+ return gtk_widget_get_sensitive(GTK_WIDGET(m_aMap.find(rIdent)->second));
+#endif
+ }
+
+ void set_item_active(const OString& rIdent, bool bActive)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ disable_item_notify_events();
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(m_aMap[rIdent]), bActive);
+ enable_item_notify_events();
+#else
+ GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup;
+ g_action_group_change_action_state(pActionGroup, m_aIdToAction[rIdent].getStr(),
+ g_variant_new_string(bActive ? rIdent.getStr() : "'none'"));
+#endif
+ }
+
+ bool get_item_active(const OString& rIdent) const
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(m_aMap.find(rIdent)->second));
+#else
+ GActionGroup* pActionGroup = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end() ? m_pActionGroup : m_pHiddenActionGroup;
+ GVariant* pState = g_action_group_get_action_state(pActionGroup, m_aIdToAction.find(rIdent)->second.getStr());
+ if (!pState)
+ return false;
+ const char *pStateString = g_variant_get_string(pState, nullptr);
+ bool bInactive = g_strcmp0(pStateString, "'none'") == 0;
+ g_variant_unref(pState);
+ return bInactive;
+#endif
+ }
+
+ void set_item_label(const OString& rIdent, const OUString& rText)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_menu_item_set_label(m_aMap[rIdent], MapToGtkAccelerator(rText).getStr());
+#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;
+ // clone the original item, remove the original, insert the replacement at
+ // the original location
+ GMenuItem* pMenuItem = g_menu_item_new_from_model(aRes.first, aRes.second);
+ g_menu_remove(G_MENU(aRes.first), aRes.second);
+ g_menu_item_set_label(pMenuItem, MapToGtkAccelerator(rText).getStr());
+ g_menu_insert_item(G_MENU(aRes.first), aRes.second, pMenuItem);
+ g_object_unref(pMenuItem);
+ }
+#endif
+ }
+
+ OUString get_item_label(const OString& rIdent) const
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ const gchar* pText = gtk_menu_item_get_label(m_aMap.find(rIdent)->second);
+ return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+#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 OUString();
+
+ // clone the original item to query its label
+ GMenuItem* pMenuItem = g_menu_item_new_from_model(aRes.first, aRes.second);
+ char *pLabel = nullptr;
+ g_menu_item_get_attribute(pMenuItem, G_MENU_ATTRIBUTE_LABEL, "&s", &pLabel);
+ OUString aRet(pLabel, pLabel ? strlen(pLabel) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pLabel);
+ g_object_unref(pMenuItem);
+ return aRet;
+ }
+ return OUString();
+#endif
+ }
+
+ void set_item_visible(const OString& rIdent, bool bShow)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pWidget = GTK_WIDGET(m_aMap[rIdent]);
+ if (bShow)
+ gtk_widget_show(pWidget);
+ else
+ gtk_widget_hide(pWidget);
+#else
+ bool bOldVisible = m_aHiddenIds.find(rIdent) == m_aHiddenIds.end();
+ if (bShow == bOldVisible)
+ return;
+
+ if (!bShow)
+ {
+ GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(m_pActionGroup), m_aIdToAction[rIdent].getStr());
+ g_action_map_add_action(G_ACTION_MAP(m_pHiddenActionGroup), pAction);
+ g_action_map_remove_action(G_ACTION_MAP(m_pActionGroup), m_aIdToAction[rIdent].getStr());
+ m_aHiddenIds.insert(rIdent);
+ }
+ else
+ {
+ GAction* pAction = g_action_map_lookup_action(G_ACTION_MAP(m_pHiddenActionGroup), m_aIdToAction[rIdent].getStr());
+ g_action_map_add_action(G_ACTION_MAP(m_pActionGroup), pAction);
+ g_action_map_remove_action(G_ACTION_MAP(m_pHiddenActionGroup), m_aIdToAction[rIdent].getStr());
+ m_aHiddenIds.erase(rIdent);
+ }
+#endif
+ }
+
+ OString get_item_id(int pos) const
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
+ gpointer pMenuItem = g_list_nth_data(pChildren, pos);
+ OString id = ::get_buildable_id(GTK_BUILDABLE(pMenuItem));
+ g_list_free(pChildren);
+ return id;
+#else
+ OString sTarget;
+ if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
+ {
+ auto aSectionAndPos = get_section_and_pos_for(pMenuModel, pos);
+ char *id;
+ if (g_menu_model_get_item_attribute(aSectionAndPos.first, aSectionAndPos.second, "target", "s", &id))
+ {
+ sTarget = OString(id);
+ g_free(id);
+ }
+ }
+ return sTarget;
+#endif
+ }
+
+ int get_n_children() const
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
+ int nLen = g_list_length(pChildren);
+ g_list_free(pChildren);
+ return nLen;
+#else
+ if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
+ return count_immediate_children(pMenuModel);
+ return 0;
+#endif
+ }
+
+ void clear_items()
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ for (const auto& a : m_aMap)
+ {
+ GtkMenuItem* pMenuItem = a.second;
+ g_signal_handlers_disconnect_by_data(pMenuItem, this);
+ gtk_widget_destroy(GTK_WIDGET(pMenuItem));
+ }
+ m_aMap.clear();
+#else
+ if (GMenuModel* pMenuModel = m_pMenu ? gtk_popover_menu_get_menu_model(m_pMenu) : nullptr)
+ {
+ GMenu* pMenu = G_MENU(pMenuModel);
+ g_menu_remove_all(pMenu);
+ g_menu_insert_section(pMenu, 0, nullptr, G_MENU_MODEL(g_menu_new()));
+ m_aHiddenIds.clear();
+ update_action_group_from_popover_model();
+ }
+#endif
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkMenu* getMenu() const
+#else
+ GtkPopoverMenu* getMenu() const
+#endif
+ {
+ return m_pMenu;
+ }
+
+ virtual ~MenuHelper()
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ for (auto& a : m_aMap)
+ g_signal_handlers_disconnect_by_data(a.second, this);
+ if (m_bTakeOwnership)
+ gtk_widget_destroy(GTK_WIDGET(m_pMenu));
+#else
+ g_object_unref(m_pActionGroup);
+ g_object_unref(m_pHiddenActionGroup);
+#endif
+ }
+};
+
+class GtkInstanceSizeGroup : public weld::SizeGroup
+{
+private:
+ GtkSizeGroup* m_pGroup;
+public:
+ GtkInstanceSizeGroup()
+ : m_pGroup(gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL))
+ {
+ }
+ virtual void add_widget(weld::Widget* pWidget) override
+ {
+ GtkInstanceWidget* pVclWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
+ assert(pVclWidget);
+ gtk_size_group_add_widget(m_pGroup, pVclWidget->getWidget());
+ }
+ virtual void set_mode(VclSizeGroupMode eVclMode) override
+ {
+ GtkSizeGroupMode eGtkMode(GTK_SIZE_GROUP_NONE);
+ switch (eVclMode)
+ {
+ case VclSizeGroupMode::NONE:
+ eGtkMode = GTK_SIZE_GROUP_NONE;
+ break;
+ case VclSizeGroupMode::Horizontal:
+ eGtkMode = GTK_SIZE_GROUP_HORIZONTAL;
+ break;
+ case VclSizeGroupMode::Vertical:
+ eGtkMode = GTK_SIZE_GROUP_VERTICAL;
+ break;
+ case VclSizeGroupMode::Both:
+ eGtkMode = GTK_SIZE_GROUP_BOTH;
+ break;
+ }
+ gtk_size_group_set_mode(m_pGroup, eGtkMode);
+ }
+ virtual ~GtkInstanceSizeGroup() override
+ {
+ g_object_unref(m_pGroup);
+ }
+};
+
+class ChildFrame : public WorkWindow
+{
+private:
+ Idle maLayoutIdle;
+
+ DECL_LINK(ImplHandleLayoutTimerHdl, Timer*, void);
+public:
+ ChildFrame(vcl::Window* pParent, WinBits nStyle)
+ : WorkWindow(pParent, nStyle)
+ , maLayoutIdle( "ChildFrame maLayoutIdle" )
+ {
+ maLayoutIdle.SetPriority(TaskPriority::RESIZE);
+ maLayoutIdle.SetInvokeHandler( LINK( this, ChildFrame, ImplHandleLayoutTimerHdl ) );
+ }
+
+ virtual void dispose() override
+ {
+ maLayoutIdle.Stop();
+ WorkWindow::dispose();
+ }
+
+ virtual void queue_resize(StateChangedType eReason = StateChangedType::Layout) override
+ {
+ WorkWindow::queue_resize(eReason);
+ if (maLayoutIdle.IsActive())
+ return;
+ maLayoutIdle.Start();
+ }
+
+ void Layout()
+ {
+ if (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild))
+ pChild->SetPosSizePixel(Point(0, 0), GetSizePixel());
+ }
+
+ virtual void Resize() override
+ {
+ maLayoutIdle.Stop();
+ Layout();
+ WorkWindow::Resize();
+ }
+};
+
+IMPL_LINK_NOARG(ChildFrame, ImplHandleLayoutTimerHdl, Timer*, void)
+{
+ Layout();
+}
+
+class GtkInstanceContainer : public GtkInstanceWidget, public virtual weld::Container
+{
+private:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkContainer* m_pContainer;
+#else
+ GtkWidget* m_pContainer;
+#endif
+ gulong m_nSetFocusChildSignalId;
+ bool m_bChildHasFocus;
+
+ void signal_set_focus_child(bool bChildHasFocus)
+ {
+ if (m_bChildHasFocus != bChildHasFocus)
+ {
+ m_bChildHasFocus = bChildHasFocus;
+ signal_container_focus_changed();
+ }
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void signalSetFocusChild(GtkContainer*, GtkWidget* pChild, gpointer widget)
+ {
+ GtkInstanceContainer* pThis = static_cast<GtkInstanceContainer*>(widget);
+ pThis->signal_set_focus_child(pChild != nullptr);
+ }
+#endif
+
+public:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkInstanceContainer(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pContainer), pBuilder, bTakeOwnership)
+#else
+ GtkInstanceContainer(GtkWidget* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(pContainer, pBuilder, bTakeOwnership)
+#endif
+ , m_pContainer(pContainer)
+ , m_nSetFocusChildSignalId(0)
+ , m_bChildHasFocus(false)
+ {
+ }
+
+ virtual void connect_container_focus_changed(const Link<Container&, void>& rLink) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!m_nSetFocusChildSignalId)
+ m_nSetFocusChildSignalId = g_signal_connect(G_OBJECT(m_pContainer), "set-focus-child", G_CALLBACK(signalSetFocusChild), this);
+#endif
+ weld::Container::connect_container_focus_changed(rLink);
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* getContainer() { return m_pContainer; }
+#else
+ GtkContainer* getContainer() { return m_pContainer; }
+#endif
+
+ virtual void child_grab_focus() override
+ {
+ gtk_widget_grab_focus(m_pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ bool bHasFocusChild = gtk_widget_get_focus_child(GTK_WIDGET(m_pContainer));
+#else
+ bool bHasFocusChild = gtk_container_get_focus_child(m_pContainer);
+#endif
+ if (!bHasFocusChild)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (GtkWidget* pChild = gtk_widget_get_first_child(m_pContainer))
+ {
+ gtk_widget_set_focus_child(m_pContainer, pChild);
+ bHasFocusChild = true;
+ }
+#else
+ GList* pChildren = gtk_container_get_children(m_pContainer);
+ if (GList* pChild = g_list_first(pChildren))
+ {
+ gtk_container_set_focus_child(m_pContainer, static_cast<GtkWidget*>(pChild->data));
+ bHasFocusChild = true;
+ }
+ g_list_free(pChildren);
+#endif
+ }
+
+ if (bHasFocusChild)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_child_focus(gtk_widget_get_focus_child(m_pWidget), GTK_DIR_TAB_FORWARD);
+#else
+ gtk_widget_child_focus(gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget)), GTK_DIR_TAB_FORWARD);
+#endif
+ }
+
+ }
+
+ virtual void move(weld::Widget* pWidget, weld::Container* pNewParent) override
+ {
+ GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
+ assert(pGtkWidget);
+ GtkWidget* pChild = pGtkWidget->getWidget();
+ g_object_ref(pChild);
+ auto pOldContainer = getContainer();
+ container_remove(GTK_WIDGET(pOldContainer), pChild);
+
+ GtkInstanceContainer* pNewGtkParent = dynamic_cast<GtkInstanceContainer*>(pNewParent);
+ assert(!pNewParent || pNewGtkParent);
+ if (pNewGtkParent)
+ {
+ auto pNewContainer = pNewGtkParent->getContainer();
+ container_add(GTK_WIDGET(pNewContainer), pChild);
+ }
+ g_object_unref(pChild);
+ }
+
+ virtual css::uno::Reference<css::awt::XWindow> CreateChildFrame() override
+ {
+ // This will cause a GtkSalFrame to be created. With WB_SYSTEMCHILDWINDOW set it
+ // will create a toplevel GtkEventBox window
+ auto xEmbedWindow = VclPtr<ChildFrame>::Create(ImplGetDefaultWindow(), WB_SYSTEMCHILDWINDOW | WB_DIALOGCONTROL | WB_CHILDDLGCTRL);
+ SalFrame* pFrame = xEmbedWindow->ImplGetFrame();
+ GtkSalFrame* pGtkFrame = dynamic_cast<GtkSalFrame*>(pFrame);
+ assert(pGtkFrame);
+
+ // relocate that toplevel GtkEventBox into this widget
+ GtkWidget* pWindow = pGtkFrame->getWindow();
+
+ GtkWidget* pParent = gtk_widget_get_parent(pWindow);
+
+ g_object_ref(pWindow);
+ container_remove(pParent, pWindow);
+ container_add(GTK_WIDGET(m_pContainer), pWindow);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_child_set(m_pContainer, pWindow, "expand", true, "fill", true, nullptr);
+#endif
+ gtk_widget_set_hexpand(pWindow, true);
+ gtk_widget_set_vexpand(pWindow, true);
+ gtk_widget_realize(pWindow);
+ gtk_widget_set_can_focus(pWindow, true);
+ g_object_unref(pWindow);
+
+ // NoActivate otherwise Show grab focus to this widget
+ xEmbedWindow->Show(true, ShowFlags::NoActivate);
+ css::uno::Reference<css::awt::XWindow> xWindow(xEmbedWindow->GetComponentInterface(), css::uno::UNO_QUERY);
+ return xWindow;
+ }
+
+ virtual ~GtkInstanceContainer() override
+ {
+ if (m_nSetFocusChildSignalId)
+ g_signal_handler_disconnect(m_pContainer, m_nSetFocusChildSignalId);
+ }
+};
+
+}
+
+std::unique_ptr<weld::Container> GtkInstanceWidget::weld_parent() const
+{
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (!pParent)
+ return nullptr;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return std::make_unique<GtkInstanceContainer>(GTK_CONTAINER(pParent), m_pBuilder, false);
+#else
+ return std::make_unique<GtkInstanceContainer>(pParent, m_pBuilder, false);
+#endif
+}
+
+namespace {
+
+bool sortButtons(const GtkWidget* pA, const GtkWidget* pB)
+{
+ //order within groups according to platform rules
+ return getButtonPriority(get_buildable_id(GTK_BUILDABLE(pA))) <
+ getButtonPriority(get_buildable_id(GTK_BUILDABLE(pB)));
+}
+
+void sort_native_button_order(GtkBox* pContainer)
+{
+ std::vector<GtkWidget*> aChildren;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(pContainer));
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ aChildren.push_back(pChild);
+ }
+#else
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pContainer));
+ for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
+ aChildren.push_back(static_cast<GtkWidget*>(pChild->data));
+ g_list_free(pChildren);
+#endif
+
+ //sort child order within parent so that we match the platform button order
+ std::stable_sort(aChildren.begin(), aChildren.end(), sortButtons);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (size_t pos = 0; pos < aChildren.size(); ++pos)
+ gtk_box_reorder_child_after(pContainer, aChildren[pos], pos ? aChildren[pos - 1] : nullptr);
+#else
+ for (size_t pos = 0; pos < aChildren.size(); ++pos)
+ gtk_box_reorder_child(pContainer, aChildren[pos], pos);
+#endif
+}
+
+class GtkInstanceBox : public GtkInstanceContainer, public virtual weld::Box
+{
+private:
+ GtkBox* m_pBox;
+
+public:
+ GtkInstanceBox(GtkBox* pBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ : GtkInstanceContainer(GTK_CONTAINER(pBox), pBuilder, bTakeOwnership)
+#else
+ : GtkInstanceContainer(GTK_WIDGET(pBox), pBuilder, bTakeOwnership)
+#endif
+ , m_pBox(pBox)
+ {
+ }
+
+ virtual void reorder_child(weld::Widget* pWidget, int nNewPosition) override
+ {
+ GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pWidget);
+ assert(pGtkWidget);
+ GtkWidget* pChild = pGtkWidget->getWidget();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_reorder_child(m_pBox, pChild, nNewPosition);
+#else
+ if (nNewPosition == 0)
+ gtk_box_reorder_child_after(m_pBox, pChild, nullptr);
+ else
+ {
+ int nNewSiblingPos = nNewPosition - 1;
+ int nChildPosition = 0;
+ for (GtkWidget* pNewSibling = gtk_widget_get_first_child(GTK_WIDGET(m_pBox));
+ pNewSibling; pNewSibling = gtk_widget_get_next_sibling(pNewSibling))
+ {
+ if (nChildPosition == nNewSiblingPos)
+ {
+ gtk_box_reorder_child_after(m_pBox, pChild, pNewSibling);
+ break;
+ }
+ ++nChildPosition;
+ }
+ }
+#endif
+ }
+
+ virtual void sort_native_button_order() override
+ {
+ ::sort_native_button_order(m_pBox);
+ }
+};
+
+}
+
+namespace
+{
+ Point get_csd_offset(GtkWidget* pTopLevel)
+ {
+ // try and omit drawing CSD under wayland
+ GtkWidget* pChild = widget_get_first_child(pTopLevel);
+
+ gtk_coord x, y;
+ gtk_widget_translate_coordinates(pChild, pTopLevel, 0, 0, &x, &y);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ int innerborder = gtk_container_get_border_width(GTK_CONTAINER(pChild));
+ int outerborder = gtk_container_get_border_width(GTK_CONTAINER(pTopLevel));
+ int totalborder = outerborder + innerborder;
+ x -= totalborder;
+ y -= totalborder;
+#endif
+
+ return Point(x, y);
+ }
+
+ void do_collect_screenshot_data(GtkWidget* pItem, gpointer data)
+ {
+ GtkWidget* pTopLevel = widget_get_toplevel(pItem);
+
+ gtk_coord x, y;
+ gtk_widget_translate_coordinates(pItem, pTopLevel, 0, 0, &x, &y);
+
+ Point aOffset = get_csd_offset(pTopLevel);
+
+ GtkAllocation alloc;
+ gtk_widget_get_allocation(pItem, &alloc);
+
+ const basegfx::B2IPoint aCurrentTopLeft(x - aOffset.X(), y - aOffset.Y());
+ const basegfx::B2IRange aCurrentRange(aCurrentTopLeft, aCurrentTopLeft + basegfx::B2IPoint(alloc.width, alloc.height));
+
+ if (!aCurrentRange.isEmpty())
+ {
+ weld::ScreenShotCollection* pCollection = static_cast<weld::ScreenShotCollection*>(data);
+ pCollection->emplace_back(::get_help_id(pItem), aCurrentRange);
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (GtkWidget* pChild = gtk_widget_get_first_child(pItem);
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ do_collect_screenshot_data(pChild, data);
+ }
+#else
+ if (GTK_IS_CONTAINER(pItem))
+ gtk_container_forall(GTK_CONTAINER(pItem), do_collect_screenshot_data, data);
+#endif
+ }
+
+ tools::Rectangle get_monitor_workarea(GtkWidget* pWindow)
+ {
+ GdkRectangle aRect;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkScreen* pScreen = gtk_widget_get_screen(pWindow);
+ gint nMonitor = gdk_screen_get_monitor_at_window(pScreen, widget_get_surface(pWindow));
+ gdk_screen_get_monitor_workarea(pScreen, nMonitor, &aRect);
+#else
+ GdkDisplay* pDisplay = gtk_widget_get_display(pWindow);
+ GdkSurface* gdkWindow = widget_get_surface(pWindow);
+ GdkMonitor* pMonitor = gdk_display_get_monitor_at_surface(pDisplay, gdkWindow);
+ gdk_monitor_get_geometry(pMonitor, &aRect);
+#endif
+ return tools::Rectangle(aRect.x, aRect.y, aRect.x + aRect.width, aRect.y + aRect.height);
+ }
+
+
+class GtkInstanceWindow : public GtkInstanceContainer, public virtual weld::Window
+{
+private:
+ GtkWindow* m_pWindow;
+ rtl::Reference<SalGtkXWindow> m_xWindow; //uno api
+ std::optional<Point> m_aPosWhileInvis; //tdf#146648 store last known position when visible to return as pos if hidden
+ gulong m_nToplevelFocusChangedSignalId;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void implResetDefault(GtkWidget *pWidget, gpointer user_data)
+ {
+ if (GTK_IS_BUTTON(pWidget))
+ g_object_set(G_OBJECT(pWidget), "has-default", false, nullptr);
+ if (GTK_IS_CONTAINER(pWidget))
+ gtk_container_forall(GTK_CONTAINER(pWidget), implResetDefault, user_data);
+ }
+
+ void recursively_unset_default_buttons()
+ {
+ implResetDefault(GTK_WIDGET(m_pWindow), nullptr);
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean help_pressed(GtkAccelGroup*, GObject*, guint, GdkModifierType, gpointer widget)
+ {
+ GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
+ pThis->help();
+ return true;
+ }
+#endif
+
+ static void signalToplevelFocusChanged(GtkWindow*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
+ pThis->signal_container_focus_changed();
+ }
+
+ bool isPositioningAllowed() const
+ {
+ // no X/Y positioning under Wayland
+ GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
+ return !DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay);
+ }
+
+protected:
+ void help();
+public:
+ GtkInstanceWindow(GtkWindow* pWindow, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ : GtkInstanceContainer(GTK_CONTAINER(pWindow), pBuilder, bTakeOwnership)
+#else
+ : GtkInstanceContainer(GTK_WIDGET(pWindow), pBuilder, bTakeOwnership)
+#endif
+ , m_pWindow(pWindow)
+ , m_nToplevelFocusChangedSignalId(0)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ const bool bIsFrameWeld = pBuilder == nullptr;
+ if (!bIsFrameWeld)
+ {
+ //hook up F1 to show help
+ GtkAccelGroup *pGroup = gtk_accel_group_new();
+ GClosure* closure = g_cclosure_new(G_CALLBACK(help_pressed), this, nullptr);
+ gtk_accel_group_connect(pGroup, GDK_KEY_F1, static_cast<GdkModifierType>(0), GTK_ACCEL_LOCKED, closure);
+ gtk_window_add_accel_group(pWindow, pGroup);
+ }
+#endif
+ }
+
+ virtual void set_title(const OUString& rTitle) override
+ {
+ ::set_title(m_pWindow, rTitle);
+ }
+
+ virtual OUString get_title() const override
+ {
+ return ::get_title(m_pWindow);
+ }
+
+ virtual css::uno::Reference<css::awt::XWindow> GetXWindow() override
+ {
+ if (!m_xWindow.is())
+ m_xWindow.set(new SalGtkXWindow(this, m_pWidget));
+ return m_xWindow;
+ }
+
+ virtual void set_modal(bool bModal) override
+ {
+ gtk_window_set_modal(m_pWindow, bModal);
+ }
+
+ virtual bool get_modal() const override
+ {
+ return gtk_window_get_modal(m_pWindow);
+ }
+
+ virtual void resize_to_request() override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_window_set_default_size(m_pWindow, 1, 1);
+#else
+ gtk_window_resize(m_pWindow, 1, 1);
+#endif
+ }
+
+ virtual void window_move(int x, int y) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_window_move(m_pWindow, x, y);
+#else
+ (void)x;
+ (void)y;
+#endif
+ }
+
+ virtual SystemEnvData get_system_data() const override
+ {
+ GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(GTK_WIDGET(m_pWindow));
+ assert(pFrame && "nothing should call this impl, yet anyway, if ever, except on result of GetFrameWeld()");
+ const SystemEnvData* pEnvData = pFrame->GetSystemData();
+ assert(pEnvData);
+ return *pEnvData;
+ }
+
+ virtual Size get_size() const override
+ {
+ int current_width, current_height;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_window_get_size(m_pWindow, &current_width, &current_height);
+#else
+ gtk_window_get_default_size(m_pWindow, &current_width, &current_height);
+#endif
+ return Size(current_width, current_height);
+ }
+
+ virtual Point get_position() const override
+ {
+ if (m_aPosWhileInvis)
+ {
+ assert(!get_visible());
+ return *m_aPosWhileInvis;
+ }
+
+ int current_x(0), current_y(0);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_window_get_position(m_pWindow, &current_x, &current_y);
+#endif
+ return Point(current_x, current_y);
+ }
+
+ virtual void show() override
+ {
+ m_aPosWhileInvis.reset();
+ GtkInstanceContainer::show();
+ }
+
+ virtual void hide() override
+ {
+ if (is_visible())
+ m_aPosWhileInvis = get_position();
+ GtkInstanceContainer::hide();
+ }
+
+ virtual tools::Rectangle get_monitor_workarea() const override
+ {
+ return ::get_monitor_workarea(GTK_WIDGET(m_pWindow));
+ }
+
+ virtual void set_centered_on_parent(bool bTrackGeometryRequests) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (bTrackGeometryRequests)
+ gtk_window_set_position(m_pWindow, GTK_WIN_POS_CENTER_ALWAYS);
+ else
+ gtk_window_set_position(m_pWindow, GTK_WIN_POS_CENTER_ON_PARENT);
+#else
+ (void)bTrackGeometryRequests;
+#endif
+ }
+
+ virtual bool get_resizable() const override
+ {
+ return gtk_window_get_resizable(m_pWindow);
+ }
+
+ virtual bool has_toplevel_focus() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_window_is_active(m_pWindow);
+#else
+ return gtk_window_has_toplevel_focus(m_pWindow);
+#endif
+ }
+
+ virtual void present() override
+ {
+ gtk_window_present(m_pWindow);
+ }
+
+ virtual void change_default_widget(weld::Widget* pOld, weld::Widget* pNew) override
+ {
+ GtkInstanceWidget* pGtkNew = dynamic_cast<GtkInstanceWidget*>(pNew);
+ GtkWidget* pWidgetNew = pGtkNew ? pGtkNew->getWidget() : nullptr;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_window_set_default_widget(m_pWindow, pWidgetNew);
+ (void)pOld;
+#else
+ GtkInstanceWidget* pGtkOld = dynamic_cast<GtkInstanceWidget*>(pOld);
+ GtkWidget* pWidgetOld = pGtkOld ? pGtkOld->getWidget() : nullptr;
+ if (pWidgetOld)
+ g_object_set(G_OBJECT(pWidgetOld), "has-default", false, nullptr);
+ else
+ recursively_unset_default_buttons();
+ if (pWidgetNew)
+ g_object_set(G_OBJECT(pWidgetNew), "has-default", true, nullptr);
+#endif
+ }
+
+ virtual bool is_default_widget(const weld::Widget* pCandidate) const override
+ {
+ const GtkInstanceWidget* pGtkCandidate = dynamic_cast<const GtkInstanceWidget*>(pCandidate);
+ GtkWidget* pWidget = pGtkCandidate ? pGtkCandidate->getWidget() : nullptr;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return pWidget && gtk_window_get_default_widget(m_pWindow) == pWidget;
+#else
+ gboolean has_default(false);
+ if (pWidget)
+ g_object_get(G_OBJECT(pWidget), "has-default", &has_default, nullptr);
+ return has_default;
+#endif
+ }
+
+ virtual void set_window_state(const OString& rStr) override
+ {
+ WindowStateData aData;
+ ImplWindowStateFromStr( aData, rStr );
+
+ auto nMask = aData.GetMask();
+ auto nState = aData.GetState() & WindowStateState::SystemMask;
+
+ if (nMask & WindowStateMask::Width && nMask & WindowStateMask::Height)
+ {
+ gtk_window_set_default_size(m_pWindow, aData.GetWidth(), aData.GetHeight());
+ }
+ if (nMask & WindowStateMask::State)
+ {
+ if (nState & WindowStateState::Maximized)
+ gtk_window_maximize(m_pWindow);
+ else
+ gtk_window_unmaximize(m_pWindow);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (isPositioningAllowed() && (nMask & WindowStateMask::X && nMask & WindowStateMask::Y))
+ {
+ gtk_window_move(m_pWindow, aData.GetX(), aData.GetY());
+ }
+#endif
+ }
+
+ virtual OString get_window_state(WindowStateMask nMask) const override
+ {
+ bool bPositioningAllowed = isPositioningAllowed();
+
+ WindowStateData aData;
+ WindowStateMask nAvailable = WindowStateMask::State |
+ WindowStateMask::Width | WindowStateMask::Height;
+ if (bPositioningAllowed)
+ nAvailable |= WindowStateMask::X | WindowStateMask::Y;
+ aData.SetMask(nMask & nAvailable);
+
+ if (nMask & WindowStateMask::State)
+ {
+ WindowStateState nState = WindowStateState::Normal;
+ if (gtk_window_is_maximized(m_pWindow))
+ nState |= WindowStateState::Maximized;
+ aData.SetState(nState);
+ }
+
+ if (bPositioningAllowed && (nMask & (WindowStateMask::X | WindowStateMask::Y)))
+ {
+ auto aPos = get_position();
+ aData.SetX(aPos.X());
+ aData.SetY(aPos.Y());
+ }
+
+ if (nMask & (WindowStateMask::Width | WindowStateMask::Height))
+ {
+ auto aSize = get_size();
+ aData.SetWidth(aSize.Width());
+ aData.SetHeight(aSize.Height());
+ }
+
+ return aData.ToStr();
+ }
+
+ virtual void connect_container_focus_changed(const Link<Container&, void>& rLink) override
+ {
+ if (!m_nToplevelFocusChangedSignalId)
+ m_nToplevelFocusChangedSignalId = g_signal_connect(m_pWindow, "notify::has-toplevel-focus", G_CALLBACK(signalToplevelFocusChanged), this);
+ GtkInstanceContainer::connect_container_focus_changed(rLink);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ if (m_nToplevelFocusChangedSignalId)
+ g_signal_handler_block(m_pWidget, m_nToplevelFocusChangedSignalId);
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::enable_notify_events();
+ if (m_nToplevelFocusChangedSignalId)
+ g_signal_handler_unblock(m_pWidget, m_nToplevelFocusChangedSignalId);
+ }
+
+ virtual VclPtr<VirtualDevice> screenshot() override
+ {
+ // detect if we have to manually setup its size
+ bool bAlreadyRealized = gtk_widget_get_realized(GTK_WIDGET(m_pWindow));
+ // has to be visible for draw to work
+ bool bAlreadyVisible = gtk_widget_get_visible(GTK_WIDGET(m_pWindow));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!bAlreadyVisible)
+ {
+ if (GTK_IS_DIALOG(m_pWindow))
+ sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pWindow))));
+ gtk_widget_show(GTK_WIDGET(m_pWindow));
+ }
+#endif
+
+ if (!bAlreadyRealized)
+ {
+ GtkAllocation allocation;
+ gtk_widget_realize(GTK_WIDGET(m_pWindow));
+ gtk_widget_get_allocation(GTK_WIDGET(m_pWindow), &allocation);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_size_allocate(GTK_WIDGET(m_pWindow), &allocation);
+#else
+ gtk_widget_size_allocate(GTK_WIDGET(m_pWindow), &allocation, 0);
+#endif
+ }
+
+ VclPtr<VirtualDevice> xOutput(VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT));
+ xOutput->SetOutputSizePixel(get_size());
+ cairo_surface_t* pSurface = get_underlying_cairo_surface(*xOutput);
+ cairo_t* cr = cairo_create(pSurface);
+
+ Point aOffset = get_csd_offset(GTK_WIDGET(m_pWindow));
+
+ cairo_translate(cr, -aOffset.X(), -aOffset.Y());
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_draw(GTK_WIDGET(m_pWindow), cr);
+#else
+ GtkSnapshot* pSnapshot = gtk_snapshot_new();
+ GtkWidgetClass* pWidgetClass = GTK_WIDGET_GET_CLASS(GTK_WIDGET(m_pWindow));
+ pWidgetClass->snapshot(GTK_WIDGET(m_pWindow), 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 (!bAlreadyVisible)
+ gtk_widget_hide(GTK_WIDGET(m_pWindow));
+ if (!bAlreadyRealized)
+ gtk_widget_unrealize(GTK_WIDGET(m_pWindow));
+
+ return xOutput;
+ }
+
+ virtual weld::ScreenShotCollection collect_screenshot_data() override
+ {
+ weld::ScreenShotCollection aRet;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pWindow));
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ do_collect_screenshot_data(pChild, &aRet);
+ }
+#else
+ gtk_container_foreach(GTK_CONTAINER(m_pWindow), do_collect_screenshot_data, &aRet);
+#endif
+
+ return aRet;
+ }
+
+ virtual ~GtkInstanceWindow() override
+ {
+ if (m_nToplevelFocusChangedSignalId)
+ g_signal_handler_disconnect(m_pWindow, m_nToplevelFocusChangedSignalId);
+ if (m_xWindow.is())
+ m_xWindow->clear();
+ }
+};
+
+class GtkInstanceDialog;
+
+struct DialogRunner
+{
+ GtkWindow* m_pDialog;
+ GtkInstanceDialog *m_pInstance;
+ gint m_nResponseId;
+ GMainLoop *m_pLoop;
+ VclPtr<vcl::Window> m_xFrameWindow;
+ int m_nModalDepth;
+
+ DialogRunner(GtkWindow* pDialog, GtkInstanceDialog* pInstance)
+ : m_pDialog(pDialog)
+ , m_pInstance(pInstance)
+ , m_nResponseId(GTK_RESPONSE_NONE)
+ , m_pLoop(nullptr)
+ , m_nModalDepth(0)
+ {
+ GtkWindow* pParent = gtk_window_get_transient_for(m_pDialog);
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(GTK_WIDGET(pParent)) : nullptr;
+ m_xFrameWindow = pFrame ? pFrame->GetWindow() : nullptr;
+ }
+
+ bool loop_is_running() const
+ {
+ return m_pLoop && g_main_loop_is_running(m_pLoop);
+ }
+
+ void loop_quit()
+ {
+ if (g_main_loop_is_running(m_pLoop))
+ g_main_loop_quit(m_pLoop);
+ }
+
+ static void signal_response(GtkDialog*, gint nResponseId, gpointer data);
+ static void signal_cancel(GtkAssistant*, gpointer data);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signal_delete(GtkDialog* pDialog, GdkEventAny*, gpointer data)
+ {
+ DialogRunner* pThis = static_cast<DialogRunner*>(data);
+ if (GTK_IS_ASSISTANT(pThis->m_pDialog))
+ {
+ // An assistant isn't a dialog, but we want to treat it like one
+ signal_response(pDialog, GTK_RESPONSE_DELETE_EVENT, data);
+ }
+ else
+ pThis->loop_quit();
+ return true; /* Do not destroy */
+ }
+#endif
+
+ static void signal_destroy(GtkDialog*, gpointer data)
+ {
+ DialogRunner* pThis = static_cast<DialogRunner*>(data);
+ pThis->loop_quit();
+ }
+
+ void inc_modal_count()
+ {
+ if (m_xFrameWindow)
+ {
+ m_xFrameWindow->IncModalCount();
+ if (m_nModalDepth == 0)
+ m_xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(true);
+ ++m_nModalDepth;
+ }
+ }
+
+ void dec_modal_count()
+ {
+ if (m_xFrameWindow)
+ {
+ m_xFrameWindow->DecModalCount();
+ --m_nModalDepth;
+ if (m_nModalDepth == 0)
+ m_xFrameWindow->ImplGetFrame()->NotifyModalHierarchy(false);
+ }
+ }
+
+ // same as gtk_dialog_run except that unmap doesn't auto-respond
+ // so we can hide the dialog and restore it without a response getting
+ // triggered
+ gint run()
+ {
+ g_object_ref(m_pDialog);
+
+ inc_modal_count();
+
+ bool bWasModal = gtk_window_get_modal(m_pDialog);
+ if (!bWasModal)
+ gtk_window_set_modal(m_pDialog, true);
+
+ if (!gtk_widget_get_visible(GTK_WIDGET(m_pDialog)))
+ gtk_widget_show(GTK_WIDGET(m_pDialog));
+
+ gulong nSignalResponseId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signal_response), this) : 0;
+ gulong nSignalCancelId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signal_cancel), this) : 0;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signal_delete), this);
+#endif
+ gulong nSignalDestroyId = g_signal_connect(m_pDialog, "destroy", G_CALLBACK(signal_destroy), this);
+
+ m_pLoop = g_main_loop_new(nullptr, false);
+ m_nResponseId = GTK_RESPONSE_NONE;
+
+ main_loop_run(m_pLoop);
+
+ g_main_loop_unref(m_pLoop);
+
+ m_pLoop = nullptr;
+
+ if (!bWasModal)
+ gtk_window_set_modal(m_pDialog, false);
+
+ if (nSignalResponseId)
+ g_signal_handler_disconnect(m_pDialog, nSignalResponseId);
+ if (nSignalCancelId)
+ g_signal_handler_disconnect(m_pDialog, nSignalCancelId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pDialog, nSignalDeleteId);
+#endif
+ g_signal_handler_disconnect(m_pDialog, nSignalDestroyId);
+
+ dec_modal_count();
+
+ g_object_unref(m_pDialog);
+
+ return m_nResponseId;
+ }
+
+ ~DialogRunner()
+ {
+ if (m_xFrameWindow && m_nModalDepth)
+ {
+ // if, like the calc validation dialog does, the modality was
+ // toggled off during execution ensure that on cleanup the parent
+ // is left in the state it was found
+ while (m_nModalDepth++ < 0)
+ m_xFrameWindow->IncModalCount();
+ }
+ }
+};
+
+}
+
+typedef std::set<GtkWidget*> winset;
+
+namespace
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ void collectVisibleChildren(GtkWidget* pTop, winset& rVisibleWidgets)
+ {
+ for (GtkWidget* pChild = gtk_widget_get_first_child(pTop);
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ if (!gtk_widget_get_visible(pChild))
+ continue;
+ rVisibleWidgets.insert(pChild);
+ collectVisibleChildren(pChild, rVisibleWidgets);
+ }
+ }
+#endif
+
+ void hideUnless(GtkWidget* pTop, const winset& rVisibleWidgets,
+ std::vector<GtkWidget*> &rWasVisibleWidgets)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (GtkWidget* pChild = gtk_widget_get_first_child(pTop);
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ if (!gtk_widget_get_visible(pChild))
+ continue;
+ if (rVisibleWidgets.find(pChild) == rVisibleWidgets.end())
+ {
+ g_object_ref(pChild);
+ rWasVisibleWidgets.emplace_back(pChild);
+ gtk_widget_hide(pChild);
+ }
+ else
+ {
+ hideUnless(pChild, rVisibleWidgets, rWasVisibleWidgets);
+ }
+ }
+#else
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pTop));
+ for (GList* pEntry = g_list_first(pChildren); pEntry; pEntry = g_list_next(pEntry))
+ {
+ GtkWidget* pChild = static_cast<GtkWidget*>(pEntry->data);
+ if (!gtk_widget_get_visible(pChild))
+ continue;
+ if (rVisibleWidgets.find(pChild) == rVisibleWidgets.end())
+ {
+ g_object_ref(pChild);
+ rWasVisibleWidgets.emplace_back(pChild);
+ gtk_widget_hide(pChild);
+ }
+ else if (GTK_IS_CONTAINER(pChild))
+ {
+ hideUnless(pChild, rVisibleWidgets, rWasVisibleWidgets);
+ }
+ }
+ g_list_free(pChildren);
+#endif
+ }
+
+class GtkInstanceButton;
+
+class GtkInstanceDialog : public GtkInstanceWindow, public virtual weld::Dialog
+{
+private:
+ GtkWindow* m_pDialog;
+ DialogRunner m_aDialogRun;
+ std::shared_ptr<weld::DialogController> m_xDialogController;
+ // Used to keep ourself alive during a runAsync(when doing runAsync without a DialogController)
+ std::shared_ptr<weld::Dialog> m_xRunAsyncSelf;
+ std::function<void(sal_Int32)> m_aFunc;
+ gulong m_nCloseSignalId;
+ gulong m_nResponseSignalId;
+ gulong m_nCancelSignalId;
+ gulong m_nSignalDeleteId;
+
+ // for calc ref dialog that shrink to range selection widgets and resize back
+ GtkWidget* m_pRefEdit;
+ std::vector<GtkWidget*> m_aHiddenWidgets; // vector of hidden Controls
+ int m_nOldEditWidth; // Original width of the input field
+ int m_nOldEditWidthReq; // Original width request of the input field
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ int m_nOldBorderWidth; // border width for expanded dialog
+#endif
+
+ void signal_close()
+ {
+ close(true);
+ }
+
+ static void signalClose(GtkWidget*, gpointer widget)
+ {
+ GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
+ pThis->signal_close();
+ }
+
+ static void signalAsyncResponse(GtkWidget*, gint ret, gpointer widget)
+ {
+ GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
+ pThis->asyncresponse(ret);
+ }
+
+ static void signalAsyncCancel(GtkAssistant*, gpointer widget)
+ {
+ GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
+ // make esc in an assistant act as if cancel button was pressed
+ pThis->close(false);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalAsyncDelete(GtkWidget* pDialog, GdkEventAny*, gpointer widget)
+ {
+ GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
+ if (GTK_IS_ASSISTANT(pThis->m_pDialog))
+ {
+ // An assistant isn't a dialog, but we want to treat it like one
+ signalAsyncResponse(pDialog, GTK_RESPONSE_DELETE_EVENT, widget);
+ }
+ return true; /* Do not destroy */
+ }
+#endif
+
+ static int GtkToVcl(int ret)
+ {
+ if (ret == GTK_RESPONSE_OK)
+ ret = RET_OK;
+ else if (ret == GTK_RESPONSE_CANCEL)
+ ret = RET_CANCEL;
+ else if (ret == GTK_RESPONSE_DELETE_EVENT)
+ ret = RET_CANCEL;
+ else if (ret == GTK_RESPONSE_CLOSE)
+ ret = RET_CLOSE;
+ else if (ret == GTK_RESPONSE_YES)
+ ret = RET_YES;
+ else if (ret == GTK_RESPONSE_NO)
+ ret = RET_NO;
+ else if (ret == GTK_RESPONSE_HELP)
+ ret = RET_HELP;
+ return ret;
+ }
+
+ static int VclToGtk(int nResponse)
+ {
+ if (nResponse == RET_OK)
+ return GTK_RESPONSE_OK;
+ else if (nResponse == RET_CANCEL)
+ return GTK_RESPONSE_CANCEL;
+ else if (nResponse == RET_CLOSE)
+ return GTK_RESPONSE_CLOSE;
+ else if (nResponse == RET_YES)
+ return GTK_RESPONSE_YES;
+ else if (nResponse == RET_NO)
+ return GTK_RESPONSE_NO;
+ else if (nResponse == RET_HELP)
+ return GTK_RESPONSE_HELP;
+ return nResponse;
+ }
+
+ void asyncresponse(gint ret);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void signalActivate(GtkMenuItem*, gpointer data)
+ {
+ bool* pActivate = static_cast<bool*>(data);
+ *pActivate = true;
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool signal_screenshot_popup_menu(const GdkEventButton* pEvent)
+ {
+ GtkWidget *pMenu = gtk_menu_new();
+
+ GtkWidget* pMenuItem = gtk_menu_item_new_with_mnemonic(MapToGtkAccelerator(VclResId(SV_BUTTONTEXT_SCREENSHOT)).getStr());
+ gtk_menu_shell_append(GTK_MENU_SHELL(pMenu), pMenuItem);
+ bool bActivate(false);
+ g_signal_connect(pMenuItem, "activate", G_CALLBACK(signalActivate), &bActivate);
+ gtk_widget_show(pMenuItem);
+
+ int button, event_time;
+ if (pEvent)
+ {
+ button = pEvent->button;
+ event_time = pEvent->time;
+ }
+ else
+ {
+ button = 0;
+ event_time = gtk_get_current_event_time();
+ }
+
+ gtk_menu_attach_to_widget(GTK_MENU(pMenu), GTK_WIDGET(m_pDialog), nullptr);
+
+ GMainLoop* pLoop = g_main_loop_new(nullptr, true);
+ gulong nSignalId = g_signal_connect_swapped(G_OBJECT(pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
+
+ gtk_menu_popup(GTK_MENU(pMenu), nullptr, nullptr, nullptr, nullptr, button, event_time);
+
+ if (g_main_loop_is_running(pLoop))
+ main_loop_run(pLoop);
+
+ g_main_loop_unref(pLoop);
+ g_signal_handler_disconnect(pMenu, nSignalId);
+ gtk_menu_detach(GTK_MENU(pMenu));
+
+ if (bActivate)
+ {
+ // open screenshot annotation dialog
+ VclAbstractDialogFactory* pFact = VclAbstractDialogFactory::Create();
+ VclPtr<AbstractScreenshotAnnotationDlg> xTmp = pFact->CreateScreenshotAnnotationDlg(*this);
+ ScopedVclPtr<AbstractScreenshotAnnotationDlg> xDialog(xTmp);
+ xDialog->Execute();
+ }
+
+ return false;
+ }
+#endif
+
+ static gboolean signalScreenshotPopupMenu(GtkWidget*, gpointer widget)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
+ return pThis->signal_screenshot_popup_menu(nullptr);
+#else
+ (void)widget;
+ return false;
+#endif
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalScreenshotButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_screenshot_button(pEvent);
+ }
+
+ bool signal_screenshot_button(GdkEventButton* pEvent)
+ {
+ if (gdk_event_triggers_context_menu(reinterpret_cast<GdkEvent*>(pEvent)) && pEvent->type == GDK_BUTTON_PRESS)
+ {
+ //if handled for context menu, stop processing
+ return signal_screenshot_popup_menu(pEvent);
+ }
+ return false;
+ }
+#endif
+
+public:
+ GtkInstanceDialog(GtkWindow* pDialog, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWindow(pDialog, pBuilder, bTakeOwnership)
+ , m_pDialog(pDialog)
+ , m_aDialogRun(pDialog, this)
+ , m_nResponseSignalId(0)
+ , m_nCancelSignalId(0)
+ , m_nSignalDeleteId(0)
+ , m_pRefEdit(nullptr)
+ , m_nOldEditWidth(0)
+ , m_nOldEditWidthReq(0)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_nOldBorderWidth(0)
+#endif
+ {
+ if (GTK_IS_DIALOG(m_pDialog) || GTK_IS_ASSISTANT(m_pDialog))
+ m_nCloseSignalId = g_signal_connect(m_pDialog, "close", G_CALLBACK(signalClose), this);
+ else
+ m_nCloseSignalId = 0;
+ const bool bScreenshotMode(officecfg::Office::Common::Misc::ScreenshotMode::get());
+ if (bScreenshotMode)
+ {
+ g_signal_connect(m_pDialog, "popup-menu", G_CALLBACK(signalScreenshotPopupMenu), this);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_connect(m_pDialog, "button-press-event", G_CALLBACK(signalScreenshotButton), this);
+#endif
+ }
+ }
+
+ virtual bool runAsync(std::shared_ptr<weld::DialogController> rDialogController, const std::function<void(sal_Int32)>& func) override
+ {
+ assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
+
+ m_xDialogController = rDialogController;
+ m_aFunc = func;
+
+ if (get_modal())
+ m_aDialogRun.inc_modal_count();
+ show();
+
+ m_nResponseSignalId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signalAsyncResponse), this) : 0;
+ m_nCancelSignalId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signalAsyncCancel), this) : 0;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);
+#endif
+
+ return true;
+ }
+
+ virtual bool runAsync(std::shared_ptr<Dialog> const & rxSelf, const std::function<void(sal_Int32)>& func) override
+ {
+ assert( rxSelf.get() == this );
+ assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
+
+ // In order to store a shared_ptr to ourself, we have to have been constructed by make_shared,
+ // which is that rxSelf enforces.
+ m_xRunAsyncSelf = rxSelf;
+ m_aFunc = func;
+
+ if (get_modal())
+ m_aDialogRun.inc_modal_count();
+ show();
+
+ m_nResponseSignalId = GTK_IS_DIALOG(m_pDialog) ? g_signal_connect(m_pDialog, "response", G_CALLBACK(signalAsyncResponse), this) : 0;
+ m_nCancelSignalId = GTK_IS_ASSISTANT(m_pDialog) ? g_signal_connect(m_pDialog, "cancel", G_CALLBACK(signalAsyncCancel), this) : 0;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);
+#endif
+
+ return true;
+ }
+
+ GtkInstanceButton* has_click_handler(int nResponse);
+
+ virtual int run() override;
+
+ virtual void show() override
+ {
+ if (gtk_widget_get_visible(m_pWidget))
+ return;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_DIALOG(m_pDialog))
+ sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
+#endif
+ GtkInstanceWindow::show();
+ }
+
+ virtual void set_modal(bool bModal) override
+ {
+ if (get_modal() == bModal)
+ return;
+ GtkInstanceWindow::set_modal(bModal);
+ /* if change the dialog modality while its running, then also change the parent LibreOffice window
+ modal count, we typically expect the dialog modality to be restored to its original state
+
+ This change modality while running case is for...
+
+ a) the calc/chart dialogs which put up an extra range chooser
+ dialog, hides the original, the user can select a range of cells and
+ on completion the original dialog is restored
+
+ b) the validity dialog in calc
+ */
+ // tdf#135567 we know we are running in the sync case if loop_is_running is true
+ // but for the async case we instead check for m_xDialogController which is set in
+ // runAsync and cleared in asyncresponse
+ if (m_aDialogRun.loop_is_running() || m_xDialogController)
+ {
+ if (bModal)
+ m_aDialogRun.inc_modal_count();
+ else
+ m_aDialogRun.dec_modal_count();
+ }
+ }
+
+ virtual void response(int nResponse) override;
+
+ virtual void add_button(const OUString& rText, int nResponse, const OString& rHelpId) override
+ {
+ GtkWidget* pWidget = gtk_dialog_add_button(GTK_DIALOG(m_pDialog), MapToGtkAccelerator(rText).getStr(), VclToGtk(nResponse));
+ if (!rHelpId.isEmpty())
+ ::set_help_id(pWidget, rHelpId);
+ }
+
+ virtual void set_default_response(int nResponse) override
+ {
+ gtk_dialog_set_default_response(GTK_DIALOG(m_pDialog), VclToGtk(nResponse));
+ }
+
+ virtual GtkButton* get_widget_for_response(int nGtkResponse)
+ {
+ return GTK_BUTTON(gtk_dialog_get_widget_for_response(GTK_DIALOG(m_pDialog), nGtkResponse));
+ }
+
+ virtual weld::Button* weld_widget_for_response(int nVclResponse) override;
+
+ virtual Container* weld_content_area() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return new GtkInstanceContainer(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog))), m_pBuilder, false);
+#else
+ return new GtkInstanceContainer(gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog)), m_pBuilder, false);
+#endif
+ }
+
+ virtual void collapse(weld::Widget* pEdit, weld::Widget* pButton) override
+ {
+ GtkInstanceWidget* pVclEdit = dynamic_cast<GtkInstanceWidget*>(pEdit);
+ assert(pVclEdit);
+ GtkInstanceWidget* pVclButton = dynamic_cast<GtkInstanceWidget*>(pButton);
+
+ GtkWidget* pRefEdit = pVclEdit->getWidget();
+ GtkWidget* pRefBtn = pVclButton ? pVclButton->getWidget() : nullptr;
+
+ m_nOldEditWidth = gtk_widget_get_allocated_width(pRefEdit);
+
+ gtk_widget_get_size_request(pRefEdit, &m_nOldEditWidthReq, nullptr);
+
+ //We want just pRefBtn and pRefEdit to be shown
+ //mark widgets we want to be visible, starting with pRefEdit
+ //and all its direct parents.
+ winset aVisibleWidgets;
+ GtkWidget *pContentArea = gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog));
+ for (GtkWidget *pCandidate = pRefEdit;
+ pCandidate && pCandidate != pContentArea && gtk_widget_get_visible(pCandidate);
+ pCandidate = gtk_widget_get_parent(pCandidate))
+ {
+ aVisibleWidgets.insert(pCandidate);
+ }
+#if GTK_CHECK_VERSION(4, 0, 0)
+ collectVisibleChildren(pRefEdit, aVisibleWidgets);
+#endif
+ if (pRefBtn)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ collectVisibleChildren(pRefBtn, aVisibleWidgets);
+#endif
+ //same again with pRefBtn, except stop if there's a
+ //shared parent in the existing widgets
+ for (GtkWidget *pCandidate = pRefBtn;
+ pCandidate && pCandidate != pContentArea && gtk_widget_get_visible(pCandidate);
+ pCandidate = gtk_widget_get_parent(pCandidate))
+ {
+ if (aVisibleWidgets.insert(pCandidate).second)
+ break;
+ }
+ }
+
+ //hide everything except the aVisibleWidgets
+ hideUnless(pContentArea, aVisibleWidgets, m_aHiddenWidgets);
+ gtk_widget_set_size_request(pRefEdit, m_nOldEditWidth, -1);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_nOldBorderWidth = gtk_container_get_border_width(GTK_CONTAINER(m_pDialog));
+ gtk_container_set_border_width(GTK_CONTAINER(m_pDialog), 0);
+ if (GtkWidget* pActionArea = gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog)))
+ gtk_widget_hide(pActionArea);
+ gtk_widget_show_all(pRefEdit);
+ if (pRefBtn)
+ gtk_widget_show_all(pRefBtn);
+#else
+ if (GtkWidget* pActionArea = gtk_dialog_get_header_bar(GTK_DIALOG(m_pDialog)))
+ gtk_widget_hide(pActionArea);
+#endif
+
+ // calc's insert->function is springing back to its original size if the ref-button
+ // is used to shrink the dialog down and then the user clicks in the calc area to do
+ // the selection
+ bool bWorkaroundSizeSpringingBack = DLSYM_GDK_IS_WAYLAND_DISPLAY(gtk_widget_get_display(m_pWidget));
+ if (bWorkaroundSizeSpringingBack)
+ gtk_widget_unmap(GTK_WIDGET(m_pDialog));
+
+ resize_to_request();
+
+ if (bWorkaroundSizeSpringingBack)
+ gtk_widget_map(GTK_WIDGET(m_pDialog));
+
+ m_pRefEdit = pRefEdit;
+ }
+
+ virtual void undo_collapse() override
+ {
+ // All others: Show();
+ for (GtkWidget* pWindow : m_aHiddenWidgets)
+ {
+ gtk_widget_show(pWindow);
+ g_object_unref(pWindow);
+ }
+ m_aHiddenWidgets.clear();
+
+ gtk_widget_set_size_request(m_pRefEdit, m_nOldEditWidthReq, -1);
+ m_pRefEdit = nullptr;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_set_border_width(GTK_CONTAINER(m_pDialog), m_nOldBorderWidth);
+ if (GtkWidget* pActionArea = gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog)))
+ gtk_widget_show(pActionArea);
+#else
+ if (GtkWidget* pActionArea = gtk_dialog_get_header_bar(GTK_DIALOG(m_pDialog)))
+ gtk_widget_show(pActionArea);
+#endif
+ resize_to_request();
+ present();
+ }
+
+ void close(bool bCloseSignal);
+
+ virtual void SetInstallLOKNotifierHdl(const Link<void*, vcl::ILibreOfficeKitNotifier*>&) override
+ {
+ //not implemented for the gtk variant
+ }
+
+ virtual ~GtkInstanceDialog() override
+ {
+ if (!m_aHiddenWidgets.empty())
+ {
+ for (GtkWidget* pWindow : m_aHiddenWidgets)
+ g_object_unref(pWindow);
+ m_aHiddenWidgets.clear();
+ }
+
+ if (m_nCloseSignalId)
+ g_signal_handler_disconnect(m_pDialog, m_nCloseSignalId);
+ assert(!m_nResponseSignalId && !m_nCancelSignalId && !m_nSignalDeleteId);
+ }
+};
+
+}
+
+void DialogRunner::signal_response(GtkDialog*, gint nResponseId, gpointer data)
+{
+ DialogRunner* pThis = static_cast<DialogRunner*>(data);
+
+ // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
+ if (nResponseId == GTK_RESPONSE_DELETE_EVENT)
+ {
+ pThis->m_pInstance->close(false);
+ return;
+ }
+
+ pThis->m_nResponseId = nResponseId;
+ pThis->loop_quit();
+}
+
+void DialogRunner::signal_cancel(GtkAssistant*, gpointer data)
+{
+ DialogRunner* pThis = static_cast<DialogRunner*>(data);
+
+ // make esc in an assistant act as if cancel button was pressed
+ pThis->m_pInstance->close(false);
+}
+
+namespace {
+
+class GtkInstanceMessageDialog : public GtkInstanceDialog, public virtual weld::MessageDialog
+{
+private:
+ GtkMessageDialog* m_pMessageDialog;
+public:
+ GtkInstanceMessageDialog(GtkMessageDialog* pMessageDialog, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceDialog(GTK_WINDOW(pMessageDialog), pBuilder, bTakeOwnership)
+ , m_pMessageDialog(pMessageDialog)
+ {
+ }
+
+ virtual void set_primary_text(const OUString& rText) override
+ {
+ ::set_primary_text(m_pMessageDialog, rText);
+ }
+
+ virtual OUString get_primary_text() const override
+ {
+ return ::get_primary_text(m_pMessageDialog);
+ }
+
+ virtual void set_secondary_text(const OUString& rText) override
+ {
+ ::set_secondary_text(m_pMessageDialog, rText);
+ }
+
+ virtual OUString get_secondary_text() const override
+ {
+ return ::get_secondary_text(m_pMessageDialog);
+ }
+
+ virtual Container* weld_message_area() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return new GtkInstanceContainer(GTK_CONTAINER(gtk_message_dialog_get_message_area(m_pMessageDialog)), m_pBuilder, false);
+#else
+ return new GtkInstanceContainer(gtk_message_dialog_get_message_area(m_pMessageDialog), m_pBuilder, false);
+#endif
+ }
+};
+
+void set_label_wrap(GtkLabel* pLabel, bool bWrap)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_label_set_wrap(pLabel, bWrap);
+#else
+ gtk_label_set_line_wrap(pLabel, bWrap);
+#endif
+}
+
+class GtkInstanceAssistant : public GtkInstanceDialog, public virtual weld::Assistant
+{
+private:
+ GtkAssistant* m_pAssistant;
+ GtkWidget* m_pSidebar;
+ GtkWidget* m_pSidebarEventBox;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkButtonBox* m_pButtonBox;
+#else
+ GtkBox* m_pButtonBox;
+ GtkEventController* m_pSidebarClickController;
+#endif
+ GtkButton* m_pHelp;
+ GtkButton* m_pBack;
+ GtkButton* m_pNext;
+ GtkButton* m_pFinish;
+ GtkButton* m_pCancel;
+ gulong m_nButtonPressSignalId;
+ std::vector<std::unique_ptr<GtkInstanceContainer>> m_aPages;
+ std::map<OString, bool> m_aNotClickable;
+
+ int find_page(std::string_view ident) const
+ {
+ int nPages = gtk_assistant_get_n_pages(m_pAssistant);
+ for (int i = 0; i < nPages; ++i)
+ {
+ GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, i);
+ OString sBuildableName = ::get_buildable_id(GTK_BUILDABLE(pPage));
+ if (sBuildableName == ident)
+ return i;
+ }
+ return -1;
+ }
+
+ static void wrap_sidebar_label(GtkWidget *pWidget, gpointer /*user_data*/)
+ {
+ if (GTK_IS_LABEL(pWidget))
+ {
+ ::set_label_wrap(GTK_LABEL(pWidget), true);
+ gtk_label_set_width_chars(GTK_LABEL(pWidget), 22);
+ gtk_label_set_max_width_chars(GTK_LABEL(pWidget), 22);
+ }
+ }
+
+ static void find_sidebar(GtkWidget *pWidget, gpointer user_data)
+ {
+ OString sBuildableName = ::get_buildable_id(GTK_BUILDABLE(pWidget));
+ if (sBuildableName == "sidebar")
+ {
+ GtkWidget **ppSidebar = static_cast<GtkWidget**>(user_data);
+ *ppSidebar = pWidget;
+ }
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_CONTAINER(pWidget))
+ gtk_container_forall(GTK_CONTAINER(pWidget), find_sidebar, user_data);
+#endif
+ }
+
+ static void signalHelpClicked(GtkButton*, gpointer widget)
+ {
+ GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
+ pThis->signal_help_clicked();
+ }
+
+ void signal_help_clicked()
+ {
+ help();
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalButton(GtkGestureClick* /*pGesture*/, int /*n_press*/, gdouble x, gdouble y, gpointer widget)
+ {
+ GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_button(x, y);
+ }
+#else
+ static gboolean signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_button(pEvent->x, pEvent->y);
+ }
+#endif
+
+ bool signal_button(gtk_coord event_x, gtk_coord event_y)
+ {
+ int nNewCurrentPage = -1;
+
+ GtkAllocation allocation;
+
+ int nPageIndex = 0;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (GtkWidget* pWidget = gtk_widget_get_first_child(m_pSidebar);
+ pWidget; pWidget = gtk_widget_get_next_sibling(pWidget))
+ {
+#else
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pSidebar));
+ for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
+ {
+ GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
+#endif
+ if (!gtk_widget_get_visible(pWidget))
+ continue;
+
+ gtk_widget_get_allocation(pWidget, &allocation);
+
+ gtk_coord dest_x1, dest_y1;
+ gtk_widget_translate_coordinates(pWidget,
+ m_pSidebarEventBox,
+ 0,
+ 0,
+ &dest_x1,
+ &dest_y1);
+
+ gtk_coord dest_x2, dest_y2;
+ gtk_widget_translate_coordinates(pWidget,
+ m_pSidebarEventBox,
+ allocation.width,
+ allocation.height,
+ &dest_x2,
+ &dest_y2);
+
+
+ if (event_x >= dest_x1 && event_x <= dest_x2 && event_y >= dest_y1 && event_y <= dest_y2)
+ {
+ nNewCurrentPage = nPageIndex;
+ break;
+ }
+
+ ++nPageIndex;
+ }
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_list_free(pChildren);
+#endif
+
+ if (nNewCurrentPage != -1 && nNewCurrentPage != get_current_page())
+ {
+ OString sIdent = get_page_ident(nNewCurrentPage);
+ if (!m_aNotClickable[sIdent] && !signal_jump_page(sIdent))
+ set_current_page(nNewCurrentPage);
+ }
+
+ return false;
+ }
+
+public:
+ GtkInstanceAssistant(GtkAssistant* pAssistant, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceDialog(GTK_WINDOW(pAssistant), pBuilder, bTakeOwnership)
+ , m_pAssistant(pAssistant)
+ , m_pSidebar(nullptr)
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_pSidebarClickController(nullptr)
+#endif
+ , m_nButtonPressSignalId(0)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_pButtonBox = GTK_BUTTON_BOX(gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL));
+ gtk_button_box_set_layout(m_pButtonBox, GTK_BUTTONBOX_END);
+ gtk_box_set_spacing(GTK_BOX(m_pButtonBox), 6);
+#else
+ m_pButtonBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
+#endif
+
+ m_pBack = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Back)).getStr()));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_can_default(GTK_WIDGET(m_pBack), true);
+#endif
+ ::set_buildable_id(GTK_BUILDABLE(m_pBack), "previous");
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_append(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pBack));
+#else
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pBack), false, false, 0);
+#endif
+
+ m_pNext = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Next)).getStr()));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_can_default(GTK_WIDGET(m_pNext), true);
+#endif
+ ::set_buildable_id(GTK_BUILDABLE(m_pNext), "next");
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_append(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pNext));
+#else
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pNext), false, false, 0);
+#endif
+
+ m_pCancel = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Cancel)).getStr()));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_can_default(GTK_WIDGET(m_pCancel), true);
+#endif
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_append(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pCancel));
+#else
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pCancel), false, false, 0);
+#endif
+
+ m_pFinish = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Finish)).getStr()));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_can_default(GTK_WIDGET(m_pFinish), true);
+#endif
+ ::set_buildable_id(GTK_BUILDABLE(m_pFinish), "finish");
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_append(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pFinish));
+#else
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pFinish), false, false, 0);
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_pHelp = GTK_BUTTON(gtk_button_new_from_icon_name("help-browser-symbolic"));
+#else
+ m_pHelp = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Help)).getStr()));
+#endif
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_can_default(GTK_WIDGET(m_pHelp), true);
+#endif
+ g_signal_connect(m_pHelp, "clicked", G_CALLBACK(signalHelpClicked), this);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_prepend(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pHelp));
+ gtk_widget_set_hexpand(GTK_WIDGET(m_pHelp), true);
+ gtk_widget_set_halign(GTK_WIDGET(m_pHelp), GTK_ALIGN_START);
+#else
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pHelp), false, false, 0);
+#endif
+
+ gtk_assistant_add_action_widget(pAssistant, GTK_WIDGET(m_pButtonBox));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_button_box_set_child_secondary(m_pButtonBox, GTK_WIDGET(m_pHelp), true);
+#endif
+ gtk_widget_set_hexpand(GTK_WIDGET(m_pButtonBox), true);
+
+ GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pButtonBox));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_child_set(GTK_CONTAINER(pParent), GTK_WIDGET(m_pButtonBox), "expand", true, "fill", true, nullptr);
+#endif
+ gtk_widget_set_halign(pParent, GTK_ALIGN_FILL);
+
+ // Hide the built-in ones early so we get a nice optimal size for the width without
+ // including the unused contents
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (GtkWidget* pChild = gtk_widget_get_first_child(pParent);
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ gtk_widget_hide(pChild);
+ }
+#else
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pParent));
+ for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
+ {
+ GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
+ gtk_widget_hide(pWidget);
+ }
+ g_list_free(pChildren);
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_show_all(GTK_WIDGET(m_pButtonBox));
+#else
+ gtk_widget_show(GTK_WIDGET(m_pButtonBox));
+#endif
+
+ find_sidebar(GTK_WIDGET(m_pAssistant), &m_pSidebar);
+
+ m_pSidebarEventBox = ::ensureEventWidget(m_pSidebar);
+ if (m_pSidebarEventBox)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkGesture *pClick = gtk_gesture_click_new();
+ gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pClick), 0);
+ m_pSidebarClickController = GTK_EVENT_CONTROLLER(pClick);
+ gtk_widget_add_controller(m_pSidebarEventBox, m_pSidebarClickController);
+ m_nButtonPressSignalId = g_signal_connect(m_pSidebarClickController, "pressed", G_CALLBACK(signalButton), this);
+#else
+ m_nButtonPressSignalId = g_signal_connect(m_pSidebarEventBox, "button-press-event", G_CALLBACK(signalButton), this);
+#endif
+ }
+ }
+
+ virtual int get_current_page() const override
+ {
+ return gtk_assistant_get_current_page(m_pAssistant);
+ }
+
+ virtual int get_n_pages() const override
+ {
+ return gtk_assistant_get_n_pages(m_pAssistant);
+ }
+
+ virtual OString get_page_ident(int nPage) const override
+ {
+ const GtkWidget* pWidget = gtk_assistant_get_nth_page(m_pAssistant, nPage);
+ return ::get_buildable_id(GTK_BUILDABLE(pWidget));
+ }
+
+ virtual OString get_current_page_ident() const override
+ {
+ return get_page_ident(get_current_page());
+ }
+
+ virtual void set_current_page(int nPage) override
+ {
+ OString sDialogTitle(gtk_window_get_title(GTK_WINDOW(m_pAssistant)));
+
+ gtk_assistant_set_current_page(m_pAssistant, nPage);
+
+ // if the page doesn't have a title, then the dialog will now have no
+ // title, so restore the original title as a fallback
+ GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nPage);
+ if (!gtk_assistant_get_page_title(m_pAssistant, pPage))
+ gtk_window_set_title(GTK_WINDOW(m_pAssistant), sDialogTitle.getStr());
+ }
+
+ virtual void set_current_page(const OString& rIdent) override
+ {
+ int nPage = find_page(rIdent);
+ if (nPage == -1)
+ return;
+ set_current_page(nPage);
+ }
+
+ virtual void set_page_title(const OString& rIdent, const OUString& rTitle) override
+ {
+ int nIndex = find_page(rIdent);
+ if (nIndex == -1)
+ return;
+ GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nIndex);
+ gtk_assistant_set_page_title(m_pAssistant, pPage,
+ OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_forall(GTK_CONTAINER(m_pSidebar), wrap_sidebar_label, nullptr);
+#endif
+ }
+
+ virtual OUString get_page_title(const OString& rIdent) const override
+ {
+ int nIndex = find_page(rIdent);
+ if (nIndex == -1)
+ return OUString();
+ GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nIndex);
+ const gchar* pStr = gtk_assistant_get_page_title(m_pAssistant, pPage);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ virtual void set_page_sensitive(const OString& rIdent, bool bSensitive) override
+ {
+ m_aNotClickable[rIdent] = !bSensitive;
+ }
+
+ virtual void set_page_index(const OString& rIdent, int nNewIndex) override
+ {
+ int nOldIndex = find_page(rIdent);
+ if (nOldIndex == -1)
+ return;
+
+ if (nOldIndex == nNewIndex)
+ return;
+
+ GtkWidget* pPage = gtk_assistant_get_nth_page(m_pAssistant, nOldIndex);
+
+ g_object_ref(pPage);
+ OString sTitle(gtk_assistant_get_page_title(m_pAssistant, pPage));
+ gtk_assistant_remove_page(m_pAssistant, nOldIndex);
+ gtk_assistant_insert_page(m_pAssistant, pPage, nNewIndex);
+ gtk_assistant_set_page_type(m_pAssistant, pPage, GTK_ASSISTANT_PAGE_CUSTOM);
+ gtk_assistant_set_page_title(m_pAssistant, pPage, sTitle.getStr());
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_forall(GTK_CONTAINER(m_pSidebar), wrap_sidebar_label, nullptr);
+#endif
+ g_object_unref(pPage);
+ }
+
+ virtual weld::Container* append_page(const OString& rIdent) override
+ {
+ disable_notify_events();
+
+ GtkWidget *pChild = gtk_grid_new();
+ ::set_buildable_id(GTK_BUILDABLE(pChild), rIdent);
+ gtk_assistant_append_page(m_pAssistant, pChild);
+ gtk_assistant_set_page_type(m_pAssistant, pChild, GTK_ASSISTANT_PAGE_CUSTOM);
+ gtk_widget_show(pChild);
+
+ enable_notify_events();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_aPages.emplace_back(new GtkInstanceContainer(GTK_CONTAINER(pChild), m_pBuilder, false));
+#else
+ m_aPages.emplace_back(new GtkInstanceContainer(pChild, m_pBuilder, false));
+#endif
+
+ return m_aPages.back().get();
+ }
+
+ virtual void set_page_side_help_id(const OString& rHelpId) override
+ {
+ if (!m_pSidebar)
+ return;
+ ::set_help_id(m_pSidebar, rHelpId);
+ }
+
+ virtual GtkButton* get_widget_for_response(int nGtkResponse) override
+ {
+ GtkButton* pButton = nullptr;
+ if (nGtkResponse == GTK_RESPONSE_YES)
+ pButton = m_pNext;
+ else if (nGtkResponse == GTK_RESPONSE_NO)
+ pButton = m_pBack;
+ else if (nGtkResponse == GTK_RESPONSE_OK)
+ pButton = m_pFinish;
+ else if (nGtkResponse == GTK_RESPONSE_CANCEL)
+ pButton = m_pCancel;
+ else if (nGtkResponse == GTK_RESPONSE_HELP)
+ pButton = m_pHelp;
+ return pButton;
+ }
+
+ virtual ~GtkInstanceAssistant() override
+ {
+ if (m_nButtonPressSignalId)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pSidebarClickController, m_nButtonPressSignalId);
+#else
+ g_signal_handler_disconnect(m_pSidebarEventBox, m_nButtonPressSignalId);
+#endif
+ }
+ }
+};
+
+class GtkInstanceFrame : public GtkInstanceContainer, public virtual weld::Frame
+{
+private:
+ GtkFrame* m_pFrame;
+public:
+ GtkInstanceFrame(GtkFrame* pFrame, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ : GtkInstanceContainer(GTK_CONTAINER(pFrame), pBuilder, bTakeOwnership)
+#else
+ : GtkInstanceContainer(GTK_WIDGET(pFrame), pBuilder, bTakeOwnership)
+#endif
+ , m_pFrame(pFrame)
+ {
+ }
+
+ virtual void set_label(const OUString& rText) override
+ {
+ gtk_label_set_label(GTK_LABEL(gtk_frame_get_label_widget(m_pFrame)), rText.replaceFirst("~", "").toUtf8().getStr());
+ }
+
+ virtual OUString get_label() const override
+ {
+ const gchar* pStr = gtk_frame_get_label(m_pFrame);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ virtual std::unique_ptr<weld::Label> weld_label_widget() const override;
+};
+
+class GtkInstancePaned : public GtkInstanceContainer, public virtual weld::Paned
+{
+private:
+ GtkPaned* m_pPaned;
+public:
+ GtkInstancePaned(GtkPaned* pPaned, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ : GtkInstanceContainer(GTK_CONTAINER(pPaned), pBuilder, bTakeOwnership)
+#else
+ : GtkInstanceContainer(GTK_WIDGET(pPaned), pBuilder, bTakeOwnership)
+#endif
+ , m_pPaned(pPaned)
+ {
+ }
+
+ virtual void set_position(int nPos) override
+ {
+ gtk_paned_set_position(m_pPaned, nPos);
+ }
+
+ virtual int get_position() const override
+ {
+ return gtk_paned_get_position(m_pPaned);
+ }
+};
+
+}
+
+static GType immobilized_viewport_get_type();
+static gpointer immobilized_viewport_parent_class;
+
+#ifndef NDEBUG
+# define IMMOBILIZED_TYPE_VIEWPORT (immobilized_viewport_get_type())
+# define IMMOBILIZED_IS_VIEWPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), IMMOBILIZED_TYPE_VIEWPORT))
+#endif
+
+namespace {
+
+struct ImmobilizedViewportPrivate
+{
+ GtkAdjustment *hadjustment;
+ GtkAdjustment *vadjustment;
+};
+
+}
+
+#define IMMOBILIZED_VIEWPORT_PRIVATE_DATA "ImmobilizedViewportPrivateData"
+
+enum
+{
+ PROP_0,
+ PROP_HADJUSTMENT,
+ PROP_VADJUSTMENT,
+ PROP_HSCROLL_POLICY,
+ PROP_VSCROLL_POLICY,
+ PROP_SHADOW_TYPE
+};
+
+static void viewport_set_adjustment(GtkViewport *viewport,
+ GtkOrientation orientation,
+ GtkAdjustment *adjustment)
+{
+ ImmobilizedViewportPrivate* priv =
+ static_cast<ImmobilizedViewportPrivate*>(g_object_get_data(G_OBJECT(viewport),
+ IMMOBILIZED_VIEWPORT_PRIVATE_DATA));
+
+ if (!adjustment)
+ adjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (priv->hadjustment)
+ g_object_unref(priv->hadjustment);
+ priv->hadjustment = adjustment;
+ }
+ else
+ {
+ if (priv->vadjustment)
+ g_object_unref(priv->vadjustment);
+ priv->vadjustment = adjustment;
+ }
+
+ g_object_ref_sink(adjustment);
+}
+
+static void
+immobilized_viewport_set_property(GObject* object,
+ guint prop_id,
+ const GValue* value,
+ GParamSpec* /*pspec*/)
+{
+ GtkViewport *viewport = GTK_VIEWPORT(object);
+
+ switch (prop_id)
+ {
+ case PROP_HADJUSTMENT:
+ viewport_set_adjustment(viewport, GTK_ORIENTATION_HORIZONTAL, GTK_ADJUSTMENT(g_value_get_object(value)));
+ break;
+ case PROP_VADJUSTMENT:
+ viewport_set_adjustment(viewport, GTK_ORIENTATION_VERTICAL, GTK_ADJUSTMENT(g_value_get_object(value)));
+ break;
+ case PROP_HSCROLL_POLICY:
+ case PROP_VSCROLL_POLICY:
+ break;
+ default:
+ SAL_WARN( "vcl.gtk", "unknown property\n");
+ break;
+ }
+}
+
+static void
+immobilized_viewport_get_property(GObject* object,
+ guint prop_id,
+ GValue* value,
+ GParamSpec* /*pspec*/)
+{
+ ImmobilizedViewportPrivate* priv =
+ static_cast<ImmobilizedViewportPrivate*>(g_object_get_data(object,
+ IMMOBILIZED_VIEWPORT_PRIVATE_DATA));
+
+ switch (prop_id)
+ {
+ case PROP_HADJUSTMENT:
+ g_value_set_object(value, priv->hadjustment);
+ break;
+ case PROP_VADJUSTMENT:
+ g_value_set_object(value, priv->vadjustment);
+ break;
+ case PROP_HSCROLL_POLICY:
+ g_value_set_enum(value, GTK_SCROLL_MINIMUM);
+ break;
+ case PROP_VSCROLL_POLICY:
+ g_value_set_enum(value, GTK_SCROLL_MINIMUM);
+ break;
+ default:
+ SAL_WARN( "vcl.gtk", "unknown property\n");
+ break;
+ }
+}
+
+static ImmobilizedViewportPrivate*
+immobilized_viewport_new_private_data()
+{
+ ImmobilizedViewportPrivate* priv = g_slice_new0(ImmobilizedViewportPrivate);
+ priv->hadjustment = nullptr;
+ priv->vadjustment = nullptr;
+ return priv;
+}
+
+static void
+immobilized_viewport_instance_init(GTypeInstance *instance, gpointer /*klass*/)
+{
+ GObject* object = G_OBJECT(instance);
+ g_object_set_data(object, IMMOBILIZED_VIEWPORT_PRIVATE_DATA,
+ immobilized_viewport_new_private_data());
+}
+
+static void
+immobilized_viewport_finalize(GObject* object)
+{
+ void* priv = g_object_get_data(object, IMMOBILIZED_VIEWPORT_PRIVATE_DATA);
+ if (priv)
+ {
+ g_slice_free(ImmobilizedViewportPrivate, priv);
+ g_object_set_data(object, IMMOBILIZED_VIEWPORT_PRIVATE_DATA, nullptr);
+ }
+ G_OBJECT_CLASS(immobilized_viewport_parent_class)->finalize(object);
+}
+
+static void immobilized_viewport_class_init(GtkWidgetClass* klass)
+{
+ immobilized_viewport_parent_class = g_type_class_peek_parent(klass);
+
+ GObjectClass* o_class = G_OBJECT_CLASS(klass);
+
+ /* GObject signals */
+ o_class->finalize = immobilized_viewport_finalize;
+ o_class->set_property = immobilized_viewport_set_property;
+ o_class->get_property = immobilized_viewport_get_property;
+
+ /* Properties */
+ g_object_class_override_property(o_class, PROP_HADJUSTMENT, "hadjustment");
+ g_object_class_override_property(o_class, PROP_VADJUSTMENT, "vadjustment");
+ g_object_class_override_property(o_class, PROP_HSCROLL_POLICY, "hscroll-policy");
+ g_object_class_override_property(o_class, PROP_VSCROLL_POLICY, "vscroll-policy");
+}
+
+GType immobilized_viewport_get_type()
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ GTypeQuery query;
+ g_type_query(gtk_viewport_get_type(), &query);
+
+ static const GTypeInfo tinfo =
+ {
+ static_cast<guint16>(query.class_size),
+ nullptr, /* base init */
+ nullptr, /* base finalize */
+ reinterpret_cast<GClassInitFunc>(immobilized_viewport_class_init), /* class init */
+ nullptr, /* class finalize */
+ nullptr, /* class data */
+ static_cast<guint16>(query.instance_size), /* instance size */
+ 0, /* nb preallocs */
+ immobilized_viewport_instance_init, /* instance init */
+ nullptr /* value table */
+ };
+
+ type = g_type_register_static(GTK_TYPE_VIEWPORT, "ImmobilizedViewport",
+ &tinfo, GTypeFlags(0));
+ }
+
+ return type;
+}
+
+static VclPolicyType GtkToVcl(GtkPolicyType eType)
+{
+ VclPolicyType eRet(VclPolicyType::NEVER);
+ switch (eType)
+ {
+ case GTK_POLICY_ALWAYS:
+ eRet = VclPolicyType::ALWAYS;
+ break;
+ case GTK_POLICY_AUTOMATIC:
+ eRet = VclPolicyType::AUTOMATIC;
+ break;
+ case GTK_POLICY_EXTERNAL:
+ case GTK_POLICY_NEVER:
+ eRet = VclPolicyType::NEVER;
+ break;
+ }
+ return eRet;
+}
+
+static GtkPolicyType VclToGtk(VclPolicyType eType)
+{
+ GtkPolicyType eRet(GTK_POLICY_ALWAYS);
+ switch (eType)
+ {
+ case VclPolicyType::ALWAYS:
+ eRet = GTK_POLICY_ALWAYS;
+ break;
+ case VclPolicyType::AUTOMATIC:
+ eRet = GTK_POLICY_AUTOMATIC;
+ break;
+ case VclPolicyType::NEVER:
+ eRet = GTK_POLICY_NEVER;
+ break;
+ }
+ return eRet;
+}
+
+static GtkMessageType VclToGtk(VclMessageType eType)
+{
+ GtkMessageType eRet(GTK_MESSAGE_INFO);
+ switch (eType)
+ {
+ case VclMessageType::Info:
+ eRet = GTK_MESSAGE_INFO;
+ break;
+ case VclMessageType::Warning:
+ eRet = GTK_MESSAGE_WARNING;
+ break;
+ case VclMessageType::Question:
+ eRet = GTK_MESSAGE_QUESTION;
+ break;
+ case VclMessageType::Error:
+ eRet = GTK_MESSAGE_ERROR;
+ break;
+ case VclMessageType::Other:
+ eRet = GTK_MESSAGE_OTHER;
+ break;
+ }
+ return eRet;
+}
+
+static GtkButtonsType VclToGtk(VclButtonsType eType)
+{
+ GtkButtonsType eRet(GTK_BUTTONS_NONE);
+ switch (eType)
+ {
+ case VclButtonsType::NONE:
+ eRet = GTK_BUTTONS_NONE;
+ break;
+ case VclButtonsType::Ok:
+ eRet = GTK_BUTTONS_OK;
+ break;
+ case VclButtonsType::Close:
+ eRet = GTK_BUTTONS_CLOSE;
+ break;
+ case VclButtonsType::Cancel:
+ eRet = GTK_BUTTONS_CANCEL;
+ break;
+ case VclButtonsType::YesNo:
+ eRet = GTK_BUTTONS_YES_NO;
+ break;
+ case VclButtonsType::OkCancel:
+ eRet = GTK_BUTTONS_OK_CANCEL;
+ break;
+ }
+ return eRet;
+}
+
+static GtkSelectionMode VclToGtk(SelectionMode eType)
+{
+ GtkSelectionMode eRet(GTK_SELECTION_NONE);
+ switch (eType)
+ {
+ case SelectionMode::NONE:
+ eRet = GTK_SELECTION_NONE;
+ break;
+ case SelectionMode::Single:
+ eRet = GTK_SELECTION_SINGLE;
+ break;
+ case SelectionMode::Range:
+ eRet = GTK_SELECTION_BROWSE;
+ break;
+ case SelectionMode::Multiple:
+ eRet = GTK_SELECTION_MULTIPLE;
+ break;
+ }
+ return eRet;
+}
+
+namespace {
+
+class GtkInstanceScrolledWindow final : public GtkInstanceContainer, public virtual weld::ScrolledWindow
+{
+private:
+ GtkScrolledWindow* m_pScrolledWindow;
+ GtkWidget *m_pOrigViewport;
+ GtkCssProvider* m_pScrollBarCssProvider;
+ GtkAdjustment* m_pVAdjustment;
+ GtkAdjustment* m_pHAdjustment;
+ gulong m_nVAdjustChangedSignalId;
+ gulong m_nHAdjustChangedSignalId;
+
+ static void signalVAdjustValueChanged(GtkAdjustment*, gpointer widget)
+ {
+ GtkInstanceScrolledWindow* pThis = static_cast<GtkInstanceScrolledWindow*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_vadjustment_changed();
+ }
+
+ static void signalHAdjustValueChanged(GtkAdjustment*, gpointer widget)
+ {
+ GtkInstanceScrolledWindow* pThis = static_cast<GtkInstanceScrolledWindow*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_hadjustment_changed();
+ }
+
+public:
+ GtkInstanceScrolledWindow(GtkScrolledWindow* pScrolledWindow, GtkInstanceBuilder* pBuilder, bool bTakeOwnership, bool bUserManagedScrolling)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ : GtkInstanceContainer(GTK_CONTAINER(pScrolledWindow), pBuilder, bTakeOwnership)
+#else
+ : GtkInstanceContainer(GTK_WIDGET(pScrolledWindow), pBuilder, bTakeOwnership)
+#endif
+ , m_pScrolledWindow(pScrolledWindow)
+ , m_pOrigViewport(nullptr)
+ , m_pScrollBarCssProvider(nullptr)
+ , m_pVAdjustment(gtk_scrolled_window_get_vadjustment(m_pScrolledWindow))
+ , m_pHAdjustment(gtk_scrolled_window_get_hadjustment(m_pScrolledWindow))
+ , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustValueChanged), this))
+ , m_nHAdjustChangedSignalId(g_signal_connect(m_pHAdjustment, "value-changed", G_CALLBACK(signalHAdjustValueChanged), this))
+ {
+ if (bUserManagedScrolling)
+ set_user_managed_scrolling();
+ }
+
+ void set_user_managed_scrolling()
+ {
+ disable_notify_events();
+ //remove the original viewport and replace it with our bodged one which
+ //doesn't do any scrolling and expects its child to figure it out somehow
+ assert(!m_pOrigViewport);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget *pViewport = gtk_scrolled_window_get_child(m_pScrolledWindow);
+#else
+ GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
+#endif
+ assert(GTK_IS_VIEWPORT(pViewport));
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget *pChild= gtk_viewport_get_child(GTK_VIEWPORT(pViewport));
+#else
+ GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
+#endif
+ g_object_ref(pChild);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_viewport_set_child(GTK_VIEWPORT(pViewport), nullptr);
+#else
+ gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
+#endif
+ g_object_ref(pViewport);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_scrolled_window_set_child(m_pScrolledWindow, nullptr);
+#else
+ gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
+#endif
+ GtkWidget* pNewViewport = GTK_WIDGET(g_object_new(immobilized_viewport_get_type(), nullptr));
+ gtk_widget_show(pNewViewport);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_scrolled_window_set_child(m_pScrolledWindow, pNewViewport);
+ gtk_viewport_set_child(GTK_VIEWPORT(pNewViewport), pChild);
+#else
+ gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), pNewViewport);
+ gtk_container_add(GTK_CONTAINER(pNewViewport), pChild);
+#endif
+ g_object_unref(pChild);
+ m_pOrigViewport = pViewport;
+ enable_notify_events();
+ }
+
+ virtual void hadjustment_configure(int value, int lower, int upper,
+ int step_increment, int page_increment,
+ int page_size) override
+ {
+ disable_notify_events();
+ if (SwapForRTL())
+ value = upper - (value - lower + page_size);
+ gtk_adjustment_configure(m_pHAdjustment, value, lower, upper, step_increment, page_increment, page_size);
+ enable_notify_events();
+ }
+
+ virtual int hadjustment_get_value() const override
+ {
+ int value = gtk_adjustment_get_value(m_pHAdjustment);
+
+ if (SwapForRTL())
+ {
+ int upper = gtk_adjustment_get_upper(m_pHAdjustment);
+ int lower = gtk_adjustment_get_lower(m_pHAdjustment);
+ int page_size = gtk_adjustment_get_page_size(m_pHAdjustment);
+ value = lower + (upper - value - page_size);
+ }
+
+ return value;
+ }
+
+ virtual void hadjustment_set_value(int value) override
+ {
+ disable_notify_events();
+
+ if (SwapForRTL())
+ {
+ int upper = gtk_adjustment_get_upper(m_pHAdjustment);
+ int lower = gtk_adjustment_get_lower(m_pHAdjustment);
+ int page_size = gtk_adjustment_get_page_size(m_pHAdjustment);
+ value = upper - (value - lower + page_size);
+ }
+
+ gtk_adjustment_set_value(m_pHAdjustment, value);
+ enable_notify_events();
+ }
+
+ virtual int hadjustment_get_upper() const override
+ {
+ return gtk_adjustment_get_upper(m_pHAdjustment);
+ }
+
+ virtual void hadjustment_set_upper(int upper) override
+ {
+ disable_notify_events();
+ gtk_adjustment_set_upper(m_pHAdjustment, upper);
+ enable_notify_events();
+ }
+
+ virtual int hadjustment_get_page_size() const override
+ {
+ return gtk_adjustment_get_page_size(m_pHAdjustment);
+ }
+
+ virtual void hadjustment_set_page_size(int size) override
+ {
+ gtk_adjustment_set_page_size(m_pHAdjustment, size);
+ }
+
+ virtual void hadjustment_set_page_increment(int size) override
+ {
+ gtk_adjustment_set_page_increment(m_pHAdjustment, size);
+ }
+
+ virtual void hadjustment_set_step_increment(int size) override
+ {
+ gtk_adjustment_set_step_increment(m_pHAdjustment, size);
+ }
+
+ virtual void set_hpolicy(VclPolicyType eHPolicy) override
+ {
+ GtkPolicyType eGtkVPolicy;
+ gtk_scrolled_window_get_policy(m_pScrolledWindow, nullptr, &eGtkVPolicy);
+ gtk_scrolled_window_set_policy(m_pScrolledWindow, VclToGtk(eHPolicy), eGtkVPolicy);
+ }
+
+ virtual VclPolicyType get_hpolicy() const override
+ {
+ GtkPolicyType eGtkHPolicy;
+ gtk_scrolled_window_get_policy(m_pScrolledWindow, &eGtkHPolicy, nullptr);
+ return GtkToVcl(eGtkHPolicy);
+ }
+
+ virtual void vadjustment_configure(int value, int lower, int upper,
+ int step_increment, int page_increment,
+ int page_size) override
+ {
+ disable_notify_events();
+ gtk_adjustment_configure(m_pVAdjustment, value, lower, upper, step_increment, page_increment, page_size);
+ enable_notify_events();
+ }
+
+ virtual int vadjustment_get_value() const override
+ {
+ return gtk_adjustment_get_value(m_pVAdjustment);
+ }
+
+ virtual void vadjustment_set_value(int value) override
+ {
+ disable_notify_events();
+ gtk_adjustment_set_value(m_pVAdjustment, value);
+ enable_notify_events();
+ }
+
+ virtual int vadjustment_get_upper() const override
+ {
+ return gtk_adjustment_get_upper(m_pVAdjustment);
+ }
+
+ virtual void vadjustment_set_upper(int upper) override
+ {
+ disable_notify_events();
+ gtk_adjustment_set_upper(m_pVAdjustment, upper);
+ enable_notify_events();
+ }
+
+ virtual int vadjustment_get_lower() const override
+ {
+ return gtk_adjustment_get_lower(m_pVAdjustment);
+ }
+
+ virtual void vadjustment_set_lower(int lower) override
+ {
+ disable_notify_events();
+ gtk_adjustment_set_lower(m_pVAdjustment, lower);
+ enable_notify_events();
+ }
+
+ virtual int vadjustment_get_page_size() const override
+ {
+ return gtk_adjustment_get_page_size(m_pVAdjustment);
+ }
+
+ virtual void vadjustment_set_page_size(int size) override
+ {
+ gtk_adjustment_set_page_size(m_pVAdjustment, size);
+ }
+
+ virtual void vadjustment_set_page_increment(int size) override
+ {
+ gtk_adjustment_set_page_increment(m_pVAdjustment, size);
+ }
+
+ virtual void vadjustment_set_step_increment(int size) override
+ {
+ gtk_adjustment_set_step_increment(m_pVAdjustment, size);
+ }
+
+ virtual void set_vpolicy(VclPolicyType eVPolicy) override
+ {
+ GtkPolicyType eGtkHPolicy;
+ gtk_scrolled_window_get_policy(m_pScrolledWindow, &eGtkHPolicy, nullptr);
+ gtk_scrolled_window_set_policy(m_pScrolledWindow, eGtkHPolicy, VclToGtk(eVPolicy));
+ }
+
+ virtual VclPolicyType get_vpolicy() const override
+ {
+ GtkPolicyType eGtkVPolicy;
+ gtk_scrolled_window_get_policy(m_pScrolledWindow, nullptr, &eGtkVPolicy);
+ return GtkToVcl(eGtkVPolicy);
+ }
+
+ virtual int get_scroll_thickness() const override
+ {
+ if (gtk_scrolled_window_get_overlay_scrolling(m_pScrolledWindow))
+ return 0;
+ return gtk_widget_get_allocated_width(gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow));
+ }
+
+ virtual void set_scroll_thickness(int nThickness) override
+ {
+ GtkWidget *pHorzBar = gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow);
+ GtkWidget *pVertBar = gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow);
+ gtk_widget_set_size_request(pHorzBar, -1, nThickness);
+ gtk_widget_set_size_request(pVertBar, nThickness, -1);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pVAdjustment, m_nVAdjustChangedSignalId);
+ g_signal_handler_block(m_pHAdjustment, m_nHAdjustChangedSignalId);
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::enable_notify_events();
+ g_signal_handler_unblock(m_pVAdjustment, m_nVAdjustChangedSignalId);
+ g_signal_handler_unblock(m_pHAdjustment, m_nHAdjustChangedSignalId);
+ }
+
+ virtual void customize_scrollbars(const Color& rBackgroundColor,
+ const Color& rShadowColor,
+ const Color& rFaceColor) override
+ {
+ GtkWidget *pHorzBar = gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow);
+ GtkWidget *pVertBar = gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow);
+ GtkStyleContext *pHorzContext = gtk_widget_get_style_context(pHorzBar);
+ GtkStyleContext *pVertContext = gtk_widget_get_style_context(pVertBar);
+ if (m_pScrollBarCssProvider)
+ {
+ gtk_style_context_remove_provider(pHorzContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider));
+ gtk_style_context_remove_provider(pVertContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider));
+ }
+
+ m_pScrollBarCssProvider = gtk_css_provider_new();
+ // intentionally 'trough' a long, narrow open container.
+ OUString aBuffer = "scrollbar contents trough { background-color: #" + rBackgroundColor.AsRGBHexString() + "; } "
+ "scrollbar contents trough slider { background-color: #" + rShadowColor.AsRGBHexString() + "; } "
+ "scrollbar contents button { background-color: #" + rFaceColor.AsRGBHexString() + "; } "
+ "scrollbar contents button { color: #000000; } "
+ "scrollbar contents button:disabled { color: #7f7f7f; }";
+ OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
+ css_provider_load_from_data(m_pScrollBarCssProvider, aResult.getStr(), aResult.getLength());
+
+ gtk_style_context_add_provider(pHorzContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ gtk_style_context_add_provider(pVertContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+ virtual ~GtkInstanceScrolledWindow() override
+ {
+ // we use GtkInstanceContainer::[disable|enable]_notify_events later on
+ // to avoid touching these removed handlers
+ g_signal_handler_disconnect(m_pVAdjustment, m_nVAdjustChangedSignalId);
+ g_signal_handler_disconnect(m_pHAdjustment, m_nHAdjustChangedSignalId);
+
+ if (m_pScrollBarCssProvider)
+ {
+ GtkStyleContext *pHorzContext = gtk_widget_get_style_context(gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow));
+ GtkStyleContext *pVertContext = gtk_widget_get_style_context(gtk_scrolled_window_get_vscrollbar(m_pScrolledWindow));
+ gtk_style_context_remove_provider(pHorzContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider));
+ gtk_style_context_remove_provider(pVertContext, GTK_STYLE_PROVIDER(m_pScrollBarCssProvider));
+ m_pScrollBarCssProvider = nullptr;
+ }
+
+ //put it back the way it was
+ if (!m_pOrigViewport)
+ return;
+
+ GtkInstanceContainer::disable_notify_events();
+
+ // force in new adjustment to drop the built-in handlers on value-changed
+ // which are getting called eventually by the gtk_container_add call
+ // and which access the scrolled window indicators which, in the case
+ // of user-managed scrolling windows in toolbar popups during popdown
+ // are nullptr causing crashes when the scrolling windows is not at its
+ // initial 0,0 position
+ GtkAdjustment *pVAdjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+ gtk_scrolled_window_set_vadjustment(m_pScrolledWindow, pVAdjustment);
+ GtkAdjustment *pHAdjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+ gtk_scrolled_window_set_hadjustment(m_pScrolledWindow, pHAdjustment);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget *pViewport = gtk_scrolled_window_get_child(m_pScrolledWindow);
+#else
+ GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
+#endif
+ assert(IMMOBILIZED_IS_VIEWPORT(pViewport));
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget *pChild= gtk_viewport_get_child(GTK_VIEWPORT(pViewport));
+#else
+ GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
+#endif
+ g_object_ref(pChild);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_viewport_set_child(GTK_VIEWPORT(pViewport), nullptr);
+#else
+ gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
+#endif
+ g_object_ref(pViewport);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_scrolled_window_set_child(m_pScrolledWindow, nullptr);
+#else
+ gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_scrolled_window_set_child(m_pScrolledWindow, m_pOrigViewport);
+#else
+ gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), m_pOrigViewport);
+#endif
+ // coverity[freed_arg : FALSE] - this does not free m_pOrigViewport, it is reffed by m_pScrolledWindow
+ g_object_unref(m_pOrigViewport);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_viewport_set_child(GTK_VIEWPORT(m_pOrigViewport), pChild);
+#else
+ gtk_container_add(GTK_CONTAINER(m_pOrigViewport), pChild);
+#endif
+ g_object_unref(pChild);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(pViewport);
+#endif
+ g_object_unref(pViewport);
+ m_pOrigViewport = nullptr;
+ GtkInstanceContainer::enable_notify_events();
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstanceNotebook : public GtkInstanceWidget, public virtual weld::Notebook
+{
+private:
+ GtkNotebook* m_pNotebook;
+ GtkBox* m_pOverFlowBox;
+ GtkNotebook* m_pOverFlowNotebook;
+ gulong m_nSwitchPageSignalId;
+ gulong m_nOverFlowSwitchPageSignalId;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ NotifyingLayout* m_pLayout;
+#else
+ gulong m_nNotebookSizeAllocateSignalId;
+ gulong m_nFocusSignalId;
+#endif
+ gulong m_nChangeCurrentPageId;
+ guint m_nLaunchSplitTimeoutId;
+ bool m_bOverFlowBoxActive;
+ bool m_bOverFlowBoxIsStart;
+ bool m_bInternalPageChange;
+ int m_nStartTabCount;
+ int m_nEndTabCount;
+ mutable std::vector<std::unique_ptr<GtkInstanceContainer>> m_aPages;
+
+ static void signalSwitchPage(GtkNotebook*, GtkWidget*, guint nNewPage, gpointer widget)
+ {
+ GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_switch_page(nNewPage);
+ }
+
+ static gboolean launch_overflow_switch_page(GtkInstanceNotebook* pThis)
+ {
+ SolarMutexGuard aGuard;
+ pThis->signal_overflow_switch_page();
+ return false;
+ }
+
+ static void signalOverFlowSwitchPage(GtkNotebook*, GtkWidget*, guint, gpointer widget)
+ {
+ g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(launch_overflow_switch_page), widget, nullptr);
+ }
+
+ void signal_switch_page(int nNewPage)
+ {
+ if (m_bOverFlowBoxIsStart)
+ {
+ auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
+ // add count of overflow pages, minus the extra tab
+ nNewPage += nOverFlowLen;
+ }
+
+ bool bAllow = m_bInternalPageChange || !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
+ if (!bAllow)
+ {
+ g_signal_stop_emission_by_name(m_pNotebook, "switch-page");
+ return;
+ }
+ if (m_bOverFlowBoxActive)
+ gtk_notebook_set_current_page(m_pOverFlowNotebook, gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1);
+ OString sNewIdent(get_page_ident(nNewPage));
+ if (!m_bInternalPageChange)
+ m_aEnterPageHdl.Call(sNewIdent);
+ }
+
+ void unsplit_notebooks()
+ {
+ int nOverFlowPages = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
+ int nMainPages = gtk_notebook_get_n_pages(m_pNotebook);
+ int nPageIndex = 0;
+ if (!m_bOverFlowBoxIsStart)
+ nPageIndex += nMainPages;
+
+ // take the overflow pages, and put them back at the end of the normal one
+ int i = nMainPages;
+ while (nOverFlowPages)
+ {
+ OString sIdent(get_page_ident(m_pOverFlowNotebook, 0));
+ OUString sLabel(get_tab_label_text(m_pOverFlowNotebook, 0));
+ remove_page(m_pOverFlowNotebook, sIdent);
+
+ GtkWidget* pPage = m_aPages[nPageIndex]->getWidget();
+ insert_page(m_pNotebook, sIdent, sLabel, pPage, -1);
+
+ GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook,
+ gtk_notebook_get_nth_page(m_pNotebook, i));
+ gtk_widget_set_hexpand(pTabWidget, true);
+ --nOverFlowPages;
+ ++i;
+ ++nPageIndex;
+ }
+
+ // remove the dangling placeholder tab page
+ remove_page(m_pOverFlowNotebook, "useless");
+ }
+
+ // a tab has been selected on the overflow notebook
+ void signal_overflow_switch_page()
+ {
+ int nNewPage = gtk_notebook_get_current_page(m_pOverFlowNotebook);
+ int nOverFlowPages = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
+ if (nNewPage == nOverFlowPages)
+ {
+ // the useless tab which is there because there has to be an active tab
+ return;
+ }
+
+ // check if we are allowed leave before attempting to resplit the notebooks
+ bool bAllow = !m_aLeavePageHdl.IsSet() || m_aLeavePageHdl.Call(get_current_page_ident());
+ if (!bAllow)
+ return;
+
+ disable_notify_events();
+
+ // take the overflow pages, and put them back at the end of the normal one
+ unsplit_notebooks();
+
+ // now redo the split, the pages will be split the other way around this time
+ std::swap(m_nStartTabCount, m_nEndTabCount);
+ split_notebooks();
+
+ // coverity[pass_freed_arg : FALSE] - m_pNotebook is not freed here
+ gtk_notebook_set_current_page(m_pNotebook, nNewPage);
+
+ enable_notify_events();
+
+ // trigger main notebook switch-page callback
+ OString sNewIdent(get_page_ident(m_pNotebook, nNewPage));
+ m_aEnterPageHdl.Call(sNewIdent);
+ }
+
+ static OString get_page_ident(GtkNotebook *pNotebook, guint nPage)
+ {
+ const GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, gtk_notebook_get_nth_page(pNotebook, nPage));
+ return ::get_buildable_id(GTK_BUILDABLE(pTabWidget));
+ }
+
+ static gint get_page_number(GtkNotebook *pNotebook, std::string_view ident)
+ {
+ gint nPages = gtk_notebook_get_n_pages(pNotebook);
+ for (gint i = 0; i < nPages; ++i)
+ {
+ const GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, gtk_notebook_get_nth_page(pNotebook, i));
+ OString sBuildableName = ::get_buildable_id(GTK_BUILDABLE(pTabWidget));
+ if (sBuildableName == ident)
+ return i;
+ }
+ return -1;
+ }
+
+ int remove_page(GtkNotebook *pNotebook, std::string_view ident)
+ {
+ disable_notify_events();
+ int nPageNumber = get_page_number(pNotebook, ident);
+ gtk_notebook_remove_page(pNotebook, nPageNumber);
+ enable_notify_events();
+ return nPageNumber;
+ }
+
+ static OUString get_tab_label_text(GtkNotebook *pNotebook, guint nPage)
+ {
+ const gchar* pStr = gtk_notebook_get_tab_label_text(pNotebook, gtk_notebook_get_nth_page(pNotebook, nPage));
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ static void set_tab_label_text(GtkNotebook *pNotebook, guint nPage, const OUString& rText)
+ {
+ OString sUtf8(rText.toUtf8());
+
+ GtkWidget* pPage = gtk_notebook_get_nth_page(pNotebook, nPage);
+
+ // tdf#128241 if there's already a label here, reuse it so the buildable
+ // name remains the same, gtk_notebook_set_tab_label_text will replace
+ // the label widget with a new one
+ GtkWidget* pTabWidget = gtk_notebook_get_tab_label(pNotebook, pPage);
+ if (pTabWidget && GTK_IS_LABEL(pTabWidget))
+ {
+ gtk_label_set_label(GTK_LABEL(pTabWidget), sUtf8.getStr());
+ return;
+ }
+
+ gtk_notebook_set_tab_label_text(pNotebook, pPage, sUtf8.getStr());
+ }
+
+ void append_useless_page(GtkNotebook *pNotebook)
+ {
+ disable_notify_events();
+
+ GtkWidget *pTabWidget = gtk_fixed_new();
+ ::set_buildable_id(GTK_BUILDABLE(pTabWidget), "useless");
+
+ GtkWidget *pChild = gtk_grid_new();
+ gtk_notebook_append_page(pNotebook, pChild, pTabWidget);
+ gtk_widget_show(pChild);
+ gtk_widget_show(pTabWidget);
+
+ enable_notify_events();
+ }
+
+ void insert_page(GtkNotebook *pNotebook, const OString& rIdent, const OUString& rLabel, GtkWidget *pChild, int nPos)
+ {
+ disable_notify_events();
+
+ GtkWidget *pTabWidget = gtk_label_new_with_mnemonic(MapToGtkAccelerator(rLabel).getStr());
+ ::set_buildable_id(GTK_BUILDABLE(pTabWidget), rIdent);
+ gtk_notebook_insert_page(pNotebook, pChild, pTabWidget, nPos);
+ gtk_widget_show(pChild);
+ gtk_widget_show(pTabWidget);
+
+ if (nPos != -1)
+ {
+ unsigned int nPageIndex = static_cast<unsigned int>(nPos);
+ if (nPageIndex < m_aPages.size())
+ m_aPages.insert(m_aPages.begin() + nPageIndex, nullptr);
+ }
+
+ enable_notify_events();
+ }
+
+ void make_overflow_boxes()
+ {
+ m_pOverFlowBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
+ GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pNotebook));
+ container_add(pParent, GTK_WIDGET(m_pOverFlowBox));
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_append(m_pOverFlowBox, GTK_WIDGET(m_pOverFlowNotebook));
+#else
+ gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pOverFlowNotebook), false, false, 0);
+#endif
+ g_object_ref(m_pNotebook);
+ container_remove(pParent, GTK_WIDGET(m_pNotebook));
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_append(m_pOverFlowBox, GTK_WIDGET(m_pNotebook));
+#else
+ gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pNotebook), true, true, 0);
+#endif
+ // coverity[freed_arg : FALSE] - this does not free m_pNotebook , it is reffed by pParent
+ g_object_unref(m_pNotebook);
+ gtk_widget_show(GTK_WIDGET(m_pOverFlowBox));
+ }
+
+ void split_notebooks()
+ {
+ // get the original preferred size for the notebook, the sane width
+ // expected here depends on the notebooks all initially having
+ // scrollable tabs enabled
+ GtkAllocation alloc;
+ gtk_widget_get_allocation(GTK_WIDGET(m_pNotebook), &alloc);
+
+ // toggle the direction of the split since the last time
+ m_bOverFlowBoxIsStart = !m_bOverFlowBoxIsStart;
+ if (!m_pOverFlowBox)
+ make_overflow_boxes();
+
+ // don't scroll the tabs anymore
+ // coverity[pass_freed_arg : FALSE] - m_pNotebook is not freed here
+ gtk_notebook_set_scrollable(m_pNotebook, false);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_freeze_child_notify(GTK_WIDGET(m_pNotebook));
+ gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
+#else
+ g_object_freeze_notify(G_OBJECT(m_pNotebook));
+ g_object_freeze_notify(G_OBJECT(m_pOverFlowNotebook));
+#endif
+
+ gtk_widget_show(GTK_WIDGET(m_pOverFlowNotebook));
+
+ gint nPages;
+
+ GtkRequisition size1, size2;
+
+ if (!m_nStartTabCount && !m_nEndTabCount)
+ {
+ nPages = gtk_notebook_get_n_pages(m_pNotebook);
+
+ std::vector<int> aLabelWidths;
+ //move tabs to the overflow notebook
+ for (int i = 0; i < nPages; ++i)
+ {
+ OUString sLabel(get_tab_label_text(m_pNotebook, i));
+ aLabelWidths.push_back(get_pixel_size(sLabel).Width());
+ }
+ int row_width = std::accumulate(aLabelWidths.begin(), aLabelWidths.end(), 0) / 2;
+ int count = 0;
+ for (int i = 0; i < nPages; ++i)
+ {
+ count += aLabelWidths[i];
+ if (count >= row_width)
+ {
+ m_nStartTabCount = i;
+ break;
+ }
+ }
+
+ m_nEndTabCount = nPages - m_nStartTabCount;
+ }
+
+ //move the tabs to the overflow notebook
+ int i = 0;
+ int nOverFlowPages = m_nStartTabCount;
+ while (nOverFlowPages)
+ {
+ OString sIdent(get_page_ident(m_pNotebook, 0));
+ OUString sLabel(get_tab_label_text(m_pNotebook, 0));
+ remove_page(m_pNotebook, sIdent);
+ insert_page(m_pOverFlowNotebook, sIdent, sLabel, gtk_grid_new(), -1);
+ GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pOverFlowNotebook,
+ gtk_notebook_get_nth_page(m_pOverFlowNotebook, i));
+ gtk_widget_set_hexpand(pTabWidget, true);
+
+ --nOverFlowPages;
+ ++i;
+ }
+
+ for (i = 0; i < m_nEndTabCount; ++i)
+ {
+ GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook,
+ gtk_notebook_get_nth_page(m_pNotebook, i));
+ gtk_widget_set_hexpand(pTabWidget, true);
+ }
+
+ // have to have some tab as the active tab of the overflow notebook
+ append_useless_page(m_pOverFlowNotebook);
+ gtk_notebook_set_current_page(m_pOverFlowNotebook, gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1);
+ if (gtk_widget_has_focus(GTK_WIDGET(m_pOverFlowNotebook)))
+ gtk_widget_grab_focus(GTK_WIDGET(m_pNotebook));
+
+ // add this temporarily to the normal notebook to measure how wide
+ // the row would be if switched to the other notebook
+ append_useless_page(m_pNotebook);
+
+ gtk_widget_get_preferred_size(GTK_WIDGET(m_pNotebook), nullptr, &size1);
+ gtk_widget_get_preferred_size(GTK_WIDGET(m_pOverFlowNotebook), nullptr, &size2);
+
+ auto nWidth = std::max(size1.width, size2.width);
+ gtk_widget_set_size_request(GTK_WIDGET(m_pNotebook), nWidth, alloc.height);
+ gtk_widget_set_size_request(GTK_WIDGET(m_pOverFlowNotebook), nWidth, -1);
+
+ // remove it once we've measured it
+ remove_page(m_pNotebook, "useless");
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
+ gtk_widget_thaw_child_notify(GTK_WIDGET(m_pNotebook));
+#else
+ g_object_thaw_notify(G_OBJECT(m_pOverFlowNotebook));
+ g_object_thaw_notify(G_OBJECT(m_pNotebook));
+#endif
+
+ m_bOverFlowBoxActive = true;
+ }
+
+ static gboolean launch_split_notebooks(GtkInstanceNotebook* pThis)
+ {
+ int nCurrentPage = pThis->get_current_page();
+ pThis->split_notebooks();
+ pThis->set_current_page(nCurrentPage);
+ pThis->m_nLaunchSplitTimeoutId = 0;
+ return false;
+ }
+
+ // tdf#120371
+ // https://developer.gnome.org/hig-book/unstable/controls-notebooks.html.en#controls-too-many-tabs
+ // if no of tabs > 6, but only if the notebook would auto-scroll, then split the tabs over
+ // two notebooks. Checking for the auto-scroll allows themes like Ambience under Ubuntu 16.04 to keep
+ // tabs in a single row when they would fit
+ void signal_notebook_size_allocate()
+ {
+ if (m_bOverFlowBoxActive || m_nLaunchSplitTimeoutId)
+ return;
+ disable_notify_events();
+ gint nPages = gtk_notebook_get_n_pages(m_pNotebook);
+ if (nPages > 6 && gtk_notebook_get_tab_pos(m_pNotebook) == GTK_POS_TOP)
+ {
+ for (gint i = 0; i < nPages; ++i)
+ {
+ GtkWidget* pTabWidget = gtk_notebook_get_tab_label(m_pNotebook, gtk_notebook_get_nth_page(m_pNotebook, i));
+#if GTK_CHECK_VERSION(4, 0, 0)
+ bool bTabVisible = gtk_widget_get_child_visible(gtk_widget_get_parent(pTabWidget));
+#else
+ bool bTabVisible = gtk_widget_get_child_visible(pTabWidget);
+#endif
+ if (!bTabVisible)
+ {
+ m_nLaunchSplitTimeoutId = g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(launch_split_notebooks), this, nullptr);
+ break;
+ }
+ }
+ }
+ enable_notify_events();
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ DECL_LINK(SizeAllocateHdl, void*, void);
+#else
+ static void signalSizeAllocate(GtkWidget*, GdkRectangle*, gpointer widget)
+ {
+ GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
+ pThis->signal_notebook_size_allocate();
+ }
+#endif
+
+ bool signal_focus(GtkDirectionType direction)
+ {
+ if (!m_bOverFlowBoxActive)
+ return false;
+
+ int nPage = gtk_notebook_get_current_page(m_pNotebook);
+ if (direction == GTK_DIR_LEFT && nPage == 0)
+ {
+ auto nOverFlowLen = gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
+ gtk_notebook_set_current_page(m_pOverFlowNotebook, nOverFlowLen - 1);
+ return true;
+ }
+ else if (direction == GTK_DIR_RIGHT && nPage == gtk_notebook_get_n_pages(m_pNotebook) - 1)
+ {
+ gtk_notebook_set_current_page(m_pOverFlowNotebook, 0);
+ return true;
+ }
+
+ return false;
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalFocus(GtkNotebook* notebook, GtkDirectionType direction, gpointer widget)
+ {
+ // if the notebook widget itself has focus
+ if (gtk_widget_is_focus(GTK_WIDGET(notebook)))
+ {
+ GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
+ return pThis->signal_focus(direction);
+ }
+ return false;
+ }
+#endif
+
+ // ctrl + page_up/ page_down
+ bool signal_change_current_page(gint arg1)
+ {
+ bool bHandled = signal_focus(arg1 < 0 ? GTK_DIR_LEFT : GTK_DIR_RIGHT);
+ if (bHandled)
+ g_signal_stop_emission_by_name(m_pNotebook, "change-current-page");
+ return false;
+ }
+
+ static gboolean signalChangeCurrentPage(GtkNotebook*, gint arg1, gpointer widget)
+ {
+ if (arg1 == 0)
+ return true;
+ GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
+ return pThis->signal_change_current_page(arg1);
+ }
+
+public:
+ GtkInstanceNotebook(GtkNotebook* pNotebook, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pNotebook), pBuilder, bTakeOwnership)
+ , m_pNotebook(pNotebook)
+ , m_pOverFlowBox(nullptr)
+ , m_pOverFlowNotebook(GTK_NOTEBOOK(gtk_notebook_new()))
+ , m_nSwitchPageSignalId(g_signal_connect(pNotebook, "switch-page", G_CALLBACK(signalSwitchPage), this))
+ , m_nOverFlowSwitchPageSignalId(g_signal_connect(m_pOverFlowNotebook, "switch-page", G_CALLBACK(signalOverFlowSwitchPage), this))
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_pLayout(nullptr)
+#else
+ , m_nNotebookSizeAllocateSignalId(0)
+ , m_nFocusSignalId(g_signal_connect(pNotebook, "focus", G_CALLBACK(signalFocus), this))
+#endif
+ , m_nChangeCurrentPageId(g_signal_connect(pNotebook, "change-current-page", G_CALLBACK(signalChangeCurrentPage), this))
+ , m_nLaunchSplitTimeoutId(0)
+ , m_bOverFlowBoxActive(false)
+ , m_bOverFlowBoxIsStart(false)
+ , m_bInternalPageChange(false)
+ , m_nStartTabCount(0)
+ , m_nEndTabCount(0)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_add_events(GTK_WIDGET(pNotebook), GDK_SCROLL_MASK);
+#endif
+ gint nPages = gtk_notebook_get_n_pages(m_pNotebook);
+ if (nPages > 6)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_nNotebookSizeAllocateSignalId = g_signal_connect_after(pNotebook, "size-allocate", G_CALLBACK(signalSizeAllocate), this);
+#else
+ m_pLayout = NOTIFYING_LAYOUT(g_object_new(notifying_layout_get_type(), nullptr));
+ notifying_layout_start_watch(m_pLayout, GTK_WIDGET(pNotebook), LINK(this, GtkInstanceNotebook, SizeAllocateHdl));
+#endif
+ }
+ gtk_notebook_set_show_border(m_pOverFlowNotebook, false);
+
+ // tdf#122623 it's nigh impossible to have a GtkNotebook without an active (checked) tab, so try and theme
+ // the unwanted tab into invisibility via the 'overflow' class themed by global CreateStyleProvider
+ GtkStyleContext *pNotebookContext = gtk_widget_get_style_context(GTK_WIDGET(m_pOverFlowNotebook));
+ gtk_style_context_add_class(pNotebookContext, "overflow");
+ }
+
+ virtual int get_current_page() const override
+ {
+ int nPage = gtk_notebook_get_current_page(m_pNotebook);
+ if (nPage == -1)
+ return nPage;
+ if (m_bOverFlowBoxIsStart)
+ {
+ auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
+ // add count of overflow pages, minus the extra tab
+ nPage += nOverFlowLen;
+ }
+ return nPage;
+ }
+
+ virtual OString get_page_ident(int nPage) const override
+ {
+ auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
+ auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
+ if (m_bOverFlowBoxIsStart)
+ {
+ if (nPage < nOverFlowLen)
+ return get_page_ident(m_pOverFlowNotebook, nPage);
+ nPage -= nOverFlowLen;
+ return get_page_ident(m_pNotebook, nPage);
+ }
+ else
+ {
+ if (nPage < nMainLen)
+ return get_page_ident(m_pNotebook, nPage);
+ nPage -= nMainLen;
+ return get_page_ident(m_pOverFlowNotebook, nPage);
+ }
+ }
+
+ virtual OString get_current_page_ident() const override
+ {
+ const int nPage = get_current_page();
+ return nPage != -1 ? get_page_ident(nPage) : OString();
+ }
+
+ virtual int get_page_index(const OString& rIdent) const override
+ {
+ auto nMainIndex = get_page_number(m_pNotebook, rIdent);
+ auto nOverFlowIndex = get_page_number(m_pOverFlowNotebook, rIdent);
+
+ if (nMainIndex == -1 && nOverFlowIndex == -1)
+ return -1;
+
+ if (m_bOverFlowBoxIsStart)
+ {
+ if (nOverFlowIndex != -1)
+ return nOverFlowIndex;
+ else
+ {
+ auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
+ return nMainIndex + nOverFlowLen;
+ }
+ }
+ else
+ {
+ if (nMainIndex != -1)
+ return nMainIndex;
+ else
+ {
+ auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
+ return nOverFlowIndex + nMainLen;
+ }
+ }
+ }
+
+ virtual weld::Container* get_page(const OString& rIdent) const override
+ {
+ int nPage = get_page_index(rIdent);
+ if (nPage < 0)
+ return nullptr;
+
+ GtkWidget* pChild;
+ if (m_bOverFlowBoxIsStart)
+ {
+ auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
+ if (nPage < nOverFlowLen)
+ pChild = gtk_notebook_get_nth_page(m_pOverFlowNotebook, nPage);
+ else
+ {
+ nPage -= nOverFlowLen;
+ pChild = gtk_notebook_get_nth_page(m_pNotebook, nPage);
+ }
+ }
+ else
+ {
+ auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
+ if (nPage < nMainLen)
+ pChild = gtk_notebook_get_nth_page(m_pNotebook, nPage);
+ else
+ {
+ nPage -= nMainLen;
+ pChild = gtk_notebook_get_nth_page(m_pOverFlowNotebook, nPage);
+ }
+ }
+
+ unsigned int nPageIndex = static_cast<unsigned int>(nPage);
+ if (m_aPages.size() < nPageIndex + 1)
+ m_aPages.resize(nPageIndex + 1);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!m_aPages[nPageIndex])
+ m_aPages[nPageIndex].reset(new GtkInstanceContainer(GTK_CONTAINER(pChild), m_pBuilder, false));
+#else
+ if (!m_aPages[nPageIndex])
+ m_aPages[nPageIndex].reset(new GtkInstanceContainer(pChild, m_pBuilder, false));
+#endif
+ return m_aPages[nPageIndex].get();
+ }
+
+ virtual void set_current_page(int nPage) override
+ {
+ // normally we'd call disable_notify_events/enable_notify_events here,
+ // but the notebook is complicated by the need to support the
+ // double-decker hackery so for simplicity just flag that the page
+ // change is not a directly user-triggered one
+ bool bInternalPageChange = m_bInternalPageChange;
+ m_bInternalPageChange = true;
+
+ if (m_bOverFlowBoxIsStart)
+ {
+ auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
+ if (nPage < nOverFlowLen)
+ gtk_notebook_set_current_page(m_pOverFlowNotebook, nPage);
+ else
+ {
+ nPage -= nOverFlowLen;
+ gtk_notebook_set_current_page(m_pNotebook, nPage);
+ }
+ }
+ else
+ {
+ auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
+ if (nPage < nMainLen)
+ gtk_notebook_set_current_page(m_pNotebook, nPage);
+ else
+ {
+ nPage -= nMainLen;
+ gtk_notebook_set_current_page(m_pOverFlowNotebook, nPage);
+ }
+ }
+
+ m_bInternalPageChange = bInternalPageChange;
+ }
+
+ virtual void set_current_page(const OString& rIdent) override
+ {
+ gint nPage = get_page_index(rIdent);
+ set_current_page(nPage);
+ }
+
+ virtual int get_n_pages() const override
+ {
+ int nLen = gtk_notebook_get_n_pages(m_pNotebook);
+ if (m_bOverFlowBoxActive)
+ nLen += gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1;
+ return nLen;
+ }
+
+ virtual OUString get_tab_label_text(const OString& rIdent) const override
+ {
+ gint nPageNum = get_page_number(m_pNotebook, rIdent);
+ if (nPageNum != -1)
+ return get_tab_label_text(m_pNotebook, nPageNum);
+ nPageNum = get_page_number(m_pOverFlowNotebook, rIdent);
+ if (nPageNum != -1)
+ return get_tab_label_text(m_pOverFlowNotebook, nPageNum);
+ return OUString();
+ }
+
+ virtual void set_tab_label_text(const OString& rIdent, const OUString& rText) override
+ {
+ gint nPageNum = get_page_number(m_pNotebook, rIdent);
+ if (nPageNum != -1)
+ {
+ set_tab_label_text(m_pNotebook, nPageNum, rText);
+ return;
+ }
+ nPageNum = get_page_number(m_pOverFlowNotebook, rIdent);
+ if (nPageNum != -1)
+ {
+ set_tab_label_text(m_pOverFlowNotebook, nPageNum, rText);
+ }
+ }
+
+ virtual void set_show_tabs(bool bShow) override
+ {
+ if (m_bOverFlowBoxActive)
+ {
+ unsplit_notebooks();
+ reset_split_data();
+ }
+
+ gtk_notebook_set_show_tabs(m_pNotebook, bShow);
+ gtk_notebook_set_show_tabs(m_pOverFlowNotebook, bShow);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pNotebook, m_nSwitchPageSignalId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_block(m_pNotebook, m_nFocusSignalId);
+#endif
+ g_signal_handler_block(m_pNotebook, m_nChangeCurrentPageId);
+ g_signal_handler_block(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
+#endif
+ g_object_freeze_notify(G_OBJECT(m_pOverFlowNotebook));
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_object_thaw_notify(G_OBJECT(m_pOverFlowNotebook));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
+#endif
+ g_signal_handler_unblock(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
+ g_signal_handler_unblock(m_pNotebook, m_nSwitchPageSignalId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_unblock(m_pNotebook, m_nFocusSignalId);
+#endif
+ g_signal_handler_unblock(m_pNotebook, m_nChangeCurrentPageId);
+ }
+
+ void reset_split_data()
+ {
+ // reset overflow and allow it to be recalculated if necessary
+ gtk_widget_hide(GTK_WIDGET(m_pOverFlowNotebook));
+ m_bOverFlowBoxActive = false;
+ m_nStartTabCount = 0;
+ m_nEndTabCount = 0;
+ }
+
+ virtual void remove_page(const OString& rIdent) override
+ {
+ if (m_bOverFlowBoxActive)
+ {
+ unsplit_notebooks();
+ reset_split_data();
+ }
+
+ unsigned int nPageIndex = remove_page(m_pNotebook, rIdent);
+ if (nPageIndex < m_aPages.size())
+ m_aPages.erase(m_aPages.begin() + nPageIndex);
+ }
+
+ virtual void insert_page(const OString& rIdent, const OUString& rLabel, int nPos) override
+ {
+ if (m_bOverFlowBoxActive)
+ {
+ unsplit_notebooks();
+ reset_split_data();
+ }
+
+ // reset overflow and allow it to be recalculated if necessary
+ gtk_widget_hide(GTK_WIDGET(m_pOverFlowNotebook));
+ m_bOverFlowBoxActive = false;
+
+ insert_page(m_pNotebook, rIdent, rLabel, gtk_grid_new(), nPos);
+ }
+
+ virtual ~GtkInstanceNotebook() override
+ {
+ if (m_nLaunchSplitTimeoutId)
+ g_source_remove(m_nLaunchSplitTimeoutId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (m_nNotebookSizeAllocateSignalId)
+ g_signal_handler_disconnect(m_pNotebook, m_nNotebookSizeAllocateSignalId);
+#else
+ if (m_pLayout)
+ {
+ // put it back how we found it initially
+ notifying_layout_stop_watch(m_pLayout);
+ }
+#endif
+ g_signal_handler_disconnect(m_pNotebook, m_nSwitchPageSignalId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pNotebook, m_nFocusSignalId);
+#endif
+ g_signal_handler_disconnect(m_pNotebook, m_nChangeCurrentPageId);
+ g_signal_handler_disconnect(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(GTK_WIDGET(m_pOverFlowNotebook));
+#else
+ GtkWidget* pOverFlowWidget = GTK_WIDGET(m_pOverFlowNotebook);
+ g_clear_pointer(&pOverFlowWidget, gtk_widget_unparent);
+#endif
+ if (!m_pOverFlowBox)
+ return;
+
+ // put it back to how we found it initially
+ GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pOverFlowBox));
+ g_object_ref(m_pNotebook);
+ container_remove(GTK_WIDGET(m_pOverFlowBox), GTK_WIDGET(m_pNotebook));
+ container_add(GTK_WIDGET(pParent), GTK_WIDGET(m_pNotebook));
+ g_object_unref(m_pNotebook);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(GTK_WIDGET(m_pOverFlowBox));
+#else
+ GtkWidget* pOverFlowBox = GTK_WIDGET(m_pOverFlowBox);
+ g_clear_pointer(&pOverFlowBox, gtk_widget_unparent);
+#endif
+ }
+};
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+IMPL_LINK_NOARG(GtkInstanceNotebook, SizeAllocateHdl, void*, void)
+{
+ signal_notebook_size_allocate();
+}
+#endif
+
+
+OUString vcl_font_to_css(const vcl::Font& rFont)
+{
+ OUStringBuffer sCSS;
+ sCSS.append("font-family: \"" + rFont.GetFamilyName() + "\"; ");
+ sCSS.append("font-size: " + OUString::number(rFont.GetFontSize().Height()) + "pt; ");
+ switch (rFont.GetItalic())
+ {
+ case ITALIC_NONE:
+ sCSS.append("font-style: normal; ");
+ break;
+ case ITALIC_NORMAL:
+ sCSS.append("font-style: italic; ");
+ break;
+ case ITALIC_OBLIQUE:
+ sCSS.append("font-style: oblique; ");
+ break;
+ default:
+ break;
+ }
+ switch (rFont.GetWeight())
+ {
+ case WEIGHT_ULTRALIGHT:
+ sCSS.append("font-weight: 200; ");
+ break;
+ case WEIGHT_LIGHT:
+ sCSS.append("font-weight: 300; ");
+ break;
+ case WEIGHT_NORMAL:
+ sCSS.append("font-weight: 400; ");
+ break;
+ case WEIGHT_BOLD:
+ sCSS.append("font-weight: 700; ");
+ break;
+ case WEIGHT_ULTRABOLD:
+ sCSS.append("font-weight: 800; ");
+ break;
+ default:
+ break;
+ }
+ switch (rFont.GetWidthType())
+ {
+ case WIDTH_ULTRA_CONDENSED:
+ sCSS.append("font-stretch: ultra-condensed; ");
+ break;
+ case WIDTH_EXTRA_CONDENSED:
+ sCSS.append("font-stretch: extra-condensed; ");
+ break;
+ case WIDTH_CONDENSED:
+ sCSS.append("font-stretch: condensed; ");
+ break;
+ case WIDTH_SEMI_CONDENSED:
+ sCSS.append("font-stretch: semi-condensed; ");
+ break;
+ case WIDTH_NORMAL:
+ sCSS.append("font-stretch: normal; ");
+ break;
+ case WIDTH_SEMI_EXPANDED:
+ sCSS.append("font-stretch: semi-expanded; ");
+ break;
+ case WIDTH_EXPANDED:
+ sCSS.append("font-stretch: expanded; ");
+ break;
+ case WIDTH_EXTRA_EXPANDED:
+ sCSS.append("font-stretch: extra-expanded; ");
+ break;
+ case WIDTH_ULTRA_EXPANDED:
+ sCSS.append("font-stretch: ultra-expanded; ");
+ break;
+ default:
+ break;
+ }
+ return sCSS.toString();
+}
+
+void update_attr_list(PangoAttrList* pAttrList, const vcl::Font& rFont)
+{
+ pango_attr_list_change(pAttrList, pango_attr_family_new(OUStringToOString(rFont.GetFamilyName(), RTL_TEXTENCODING_UTF8).getStr()));
+ pango_attr_list_change(pAttrList, pango_attr_size_new(rFont.GetFontSize().Height() * PANGO_SCALE));
+
+ switch (rFont.GetItalic())
+ {
+ case ITALIC_NONE:
+ pango_attr_list_change(pAttrList, pango_attr_style_new(PANGO_STYLE_NORMAL));
+ break;
+ case ITALIC_NORMAL:
+ pango_attr_list_change(pAttrList, pango_attr_style_new(PANGO_STYLE_ITALIC));
+ break;
+ case ITALIC_OBLIQUE:
+ pango_attr_list_change(pAttrList, pango_attr_style_new(PANGO_STYLE_OBLIQUE));
+ break;
+ default:
+ break;
+ }
+ switch (rFont.GetWeight())
+ {
+ case WEIGHT_ULTRALIGHT:
+ pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRALIGHT));
+ break;
+ case WEIGHT_LIGHT:
+ pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_LIGHT));
+ break;
+ case WEIGHT_NORMAL:
+ pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_NORMAL));
+ break;
+ case WEIGHT_BOLD:
+ pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
+ break;
+ case WEIGHT_ULTRABOLD:
+ pango_attr_list_change(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRABOLD));
+ break;
+ default:
+ break;
+ }
+ switch (rFont.GetWidthType())
+ {
+ case WIDTH_ULTRA_CONDENSED:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_CONDENSED));
+ break;
+ case WIDTH_EXTRA_CONDENSED:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_CONDENSED));
+ break;
+ case WIDTH_CONDENSED:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_CONDENSED));
+ break;
+ case WIDTH_SEMI_CONDENSED:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_CONDENSED));
+ break;
+ case WIDTH_NORMAL:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_NORMAL));
+ break;
+ case WIDTH_SEMI_EXPANDED:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_EXPANDED));
+ break;
+ case WIDTH_EXPANDED:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXPANDED));
+ break;
+ case WIDTH_EXTRA_EXPANDED:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_EXPANDED));
+ break;
+ case WIDTH_ULTRA_EXPANDED:
+ pango_attr_list_change(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_EXPANDED));
+ break;
+ default:
+ break;
+ }
+}
+
+gboolean filter_pango_attrs(PangoAttribute *attr, gpointer data)
+{
+ PangoAttrType* pFilterAttrs = static_cast<PangoAttrType*>(data);
+ while (*pFilterAttrs)
+ {
+ if (attr->klass->type == *pFilterAttrs)
+ return true;
+ ++pFilterAttrs;
+ }
+ return false;
+}
+
+void set_font(GtkLabel* pLabel, const vcl::Font& rFont)
+{
+ PangoAttrList* pOrigList = gtk_label_get_attributes(pLabel);
+ PangoAttrList* pAttrList = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
+
+ if (pOrigList)
+ {
+ // tdf#143443 remove both PANGO_ATTR_ABSOLUTE_SIZE and PANGO_ATTR_SIZE
+ // because pango_attr_list_change(..., pango_attr_size_new...) isn't
+ // sufficient on its own to ensure a new size sticks.
+ PangoAttrType aFilterAttrs[] = {PANGO_ATTR_ABSOLUTE_SIZE, PANGO_ATTR_SIZE, PANGO_ATTR_INVALID};
+ PangoAttrList* pRemovedAttrs = pango_attr_list_filter(pAttrList, filter_pango_attrs, &aFilterAttrs);
+ pango_attr_list_unref(pRemovedAttrs);
+ }
+
+ update_attr_list(pAttrList, rFont);
+ gtk_label_set_attributes(pLabel, pAttrList);
+ pango_attr_list_unref(pAttrList);
+}
+
+}
+
+namespace {
+
+class WidgetBackground
+{
+private:
+ GtkWidget* m_pWidget;
+ GtkCssProvider* m_pCustomCssProvider;
+ std::unique_ptr<utl::TempFile> m_xCustomImage;
+
+public:
+ // See: https://developer.gnome.org/Buttons/
+ void use_custom_content(const VirtualDevice* pDevice)
+ {
+ GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(m_pWidget);
+
+ if (m_pCustomCssProvider)
+ {
+ gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pCustomCssProvider));
+ m_pCustomCssProvider = nullptr;
+ }
+
+ m_xCustomImage.reset();
+
+ if (!pDevice)
+ return;
+
+ m_xCustomImage.reset(new utl::TempFile);
+ m_xCustomImage->EnableKillingFile(true);
+
+ cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
+ Size aSize = pDevice->GetOutputSizePixel();
+ cairo_surface_write_to_png(surface, OUStringToOString(m_xCustomImage->GetFileName(), osl_getThreadTextEncoding()).getStr());
+
+ m_pCustomCssProvider = gtk_css_provider_new();
+ OUString aBuffer = "* { background-image: url(\"" + m_xCustomImage->GetURL() + "\"); "
+ "background-size: " + OUString::number(aSize.Width()) + "px " + OUString::number(aSize.Height()) + "px; "
+ "border-radius: 0; border-width: 0; }";
+ OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
+ css_provider_load_from_data(m_pCustomCssProvider, aResult.getStr(), aResult.getLength());
+ gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pCustomCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+public:
+ WidgetBackground(GtkWidget* pWidget)
+ : m_pWidget(pWidget)
+ , m_pCustomCssProvider(nullptr)
+ {
+ }
+
+ ~WidgetBackground()
+ {
+ if (m_pCustomCssProvider)
+ use_custom_content(nullptr);
+ assert(!m_pCustomCssProvider);
+ }
+};
+
+class WidgetFont
+{
+private:
+ GtkWidget* m_pWidget;
+ GtkCssProvider* m_pFontCssProvider;
+ std::unique_ptr<vcl::Font> m_xFont;
+public:
+ WidgetFont(GtkWidget* pWidget)
+ : m_pWidget(pWidget)
+ , m_pFontCssProvider(nullptr)
+ {
+ }
+
+ void use_custom_font(const vcl::Font* pFont, std::u16string_view rCSSSelector)
+ {
+ GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(m_pWidget);
+ if (m_pFontCssProvider)
+ {
+ gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFontCssProvider));
+ m_pFontCssProvider = nullptr;
+ }
+
+ m_xFont.reset();
+
+ if (!pFont)
+ return;
+
+ m_xFont.reset(new vcl::Font(*pFont));
+ m_pFontCssProvider = gtk_css_provider_new();
+ OUString aBuffer = rCSSSelector + OUString::Concat(" { ") + vcl_font_to_css(*pFont) + OUString::Concat(" }");
+ OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
+ css_provider_load_from_data(m_pFontCssProvider, aResult.getStr(), aResult.getLength());
+ gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFontCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+ const vcl::Font* get_custom_font() const
+ {
+ return m_xFont.get();
+ }
+
+ ~WidgetFont()
+ {
+ if (m_pFontCssProvider)
+ use_custom_font(nullptr, u"");
+ assert(!m_pFontCssProvider);
+ }
+};
+
+class GtkInstanceButton : public GtkInstanceWidget, public virtual weld::Button
+{
+private:
+ GtkButton* m_pButton;
+ gulong m_nSignalId;
+ std::optional<vcl::Font> m_xFont;
+ WidgetBackground m_aCustomBackground;
+
+ static void signalClicked(GtkButton*, gpointer widget)
+ {
+ GtkInstanceButton* pThis = static_cast<GtkInstanceButton*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_clicked();
+ }
+
+ virtual void ensureMouseEventWidget() override
+ {
+ // The GtkButton is sufficient to get mouse events without an intermediate GtkEventBox
+ if (!m_pMouseEventBox)
+ m_pMouseEventBox = m_pWidget;
+ }
+
+public:
+ GtkInstanceButton(GtkButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
+ , m_pButton(pButton)
+ , m_nSignalId(g_signal_connect(pButton, "clicked", G_CALLBACK(signalClicked), this))
+ , m_aCustomBackground(GTK_WIDGET(pButton))
+ {
+ g_object_set_data(G_OBJECT(m_pButton), "g-lo-GtkInstanceButton", this);
+ }
+
+ virtual void set_label(const OUString& rText) override
+ {
+ ::button_set_label(m_pButton, rText);
+ }
+
+ virtual void set_image(VirtualDevice* pDevice) override
+ {
+ ::button_set_image(m_pButton, pDevice);
+ }
+
+ virtual void set_from_icon_name(const OUString& rIconName) override
+ {
+ ::button_set_from_icon_name(m_pButton, rIconName);
+ }
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
+ {
+ ::button_set_image(m_pButton, rImage);
+ }
+
+ virtual void set_custom_button(VirtualDevice* pDevice) override
+ {
+ m_aCustomBackground.use_custom_content(pDevice);
+ }
+
+ virtual OUString get_label() const override
+ {
+ return ::button_get_label(m_pButton);
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_xFont = rFont;
+ GtkLabel* pChild = ::get_label_widget(GTK_WIDGET(m_pButton));
+ ::set_font(pChild, rFont);
+ }
+
+ virtual vcl::Font get_font() override
+ {
+ if (m_xFont)
+ return *m_xFont;
+ return GtkInstanceWidget::get_font();
+ }
+
+ // allow us to block buttons with click handlers making dialogs return a response
+ bool has_click_handler() const
+ {
+ return m_aClickHdl.IsSet();
+ }
+
+ void clear_click_handler()
+ {
+ m_aClickHdl = Link<Button&, void>();
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pButton, m_nSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pButton, m_nSignalId);
+ }
+
+ virtual ~GtkInstanceButton() override
+ {
+ g_object_steal_data(G_OBJECT(m_pButton), "g-lo-GtkInstanceButton");
+ g_signal_handler_disconnect(m_pButton, m_nSignalId);
+ }
+};
+
+}
+
+void GtkInstanceDialog::asyncresponse(gint ret)
+{
+ SolarMutexGuard aGuard;
+
+ if (ret == GTK_RESPONSE_HELP)
+ {
+ help();
+ return;
+ }
+
+ GtkInstanceButton* pClickHandler = has_click_handler(ret);
+ if (pClickHandler)
+ {
+ // make GTK_RESPONSE_DELETE_EVENT act as if cancel button was pressed
+ if (ret == GTK_RESPONSE_DELETE_EVENT)
+ close(false);
+ return;
+ }
+
+ if (get_modal())
+ m_aDialogRun.dec_modal_count();
+ hide();
+
+ // move the self pointer, otherwise it might be de-allocated by time we try to reset it
+ auto xRunAsyncSelf = std::move(m_xRunAsyncSelf);
+ auto xDialogController = std::move(m_xDialogController);
+ auto aFunc = std::move(m_aFunc);
+
+ auto nResponseSignalId = m_nResponseSignalId;
+ auto nCancelSignalId = m_nCancelSignalId;
+ auto nSignalDeleteId = m_nSignalDeleteId;
+ m_nResponseSignalId = 0;
+ m_nCancelSignalId = 0;
+ m_nSignalDeleteId = 0;
+
+ if (aFunc)
+ aFunc(GtkToVcl(ret));
+
+ if (nResponseSignalId)
+ g_signal_handler_disconnect(m_pDialog, nResponseSignalId);
+ if (nCancelSignalId)
+ g_signal_handler_disconnect(m_pDialog, nCancelSignalId);
+ if (nSignalDeleteId)
+ g_signal_handler_disconnect(m_pDialog, nSignalDeleteId);
+
+ xDialogController.reset();
+ xRunAsyncSelf.reset();
+}
+
+int GtkInstanceDialog::run()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_DIALOG(m_pDialog))
+ sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
+#endif
+ int ret;
+ while (true)
+ {
+ ret = m_aDialogRun.run();
+ if (ret == GTK_RESPONSE_HELP)
+ {
+ help();
+ continue;
+ }
+ else if (has_click_handler(ret))
+ continue;
+ break;
+ }
+ hide();
+ return GtkToVcl(ret);
+}
+
+weld::Button* GtkInstanceDialog::weld_widget_for_response(int nVclResponse)
+{
+ GtkButton* pButton = get_widget_for_response(VclToGtk(nVclResponse));
+ if (!pButton)
+ return nullptr;
+ return new GtkInstanceButton(pButton, m_pBuilder, false);
+}
+
+void GtkInstanceDialog::response(int nResponse)
+{
+ int nGtkResponse = VclToGtk(nResponse);
+ //unblock this response now when activated through code
+ if (GtkButton* pWidget = get_widget_for_response(nGtkResponse))
+ {
+ void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceButton");
+ GtkInstanceButton* pButton = static_cast<GtkInstanceButton*>(pData);
+ if (pButton)
+ pButton->clear_click_handler();
+ }
+ if (GTK_IS_DIALOG(m_pDialog))
+ gtk_dialog_response(GTK_DIALOG(m_pDialog), nGtkResponse);
+ else if (GTK_IS_ASSISTANT(m_pDialog))
+ {
+ if (!m_aDialogRun.loop_is_running())
+ asyncresponse(nGtkResponse);
+ else
+ {
+ m_aDialogRun.m_nResponseId = nGtkResponse;
+ m_aDialogRun.loop_quit();
+ }
+ }
+}
+
+void GtkInstanceDialog::close(bool bCloseSignal)
+{
+ GtkInstanceButton* pClickHandler = has_click_handler(GTK_RESPONSE_CANCEL);
+ if (pClickHandler)
+ {
+ if (bCloseSignal)
+ g_signal_stop_emission_by_name(m_pDialog, "close");
+ // make esc (bCloseSignal == true) or window-delete (bCloseSignal == false)
+ // act as if cancel button was pressed
+ pClickHandler->clicked();
+ return;
+ }
+ response(RET_CANCEL);
+}
+
+GtkInstanceButton* GtkInstanceDialog::has_click_handler(int nResponse)
+{
+ GtkInstanceButton* pButton = nullptr;
+ // e.g. map GTK_RESPONSE_DELETE_EVENT to GTK_RESPONSE_CANCEL
+ nResponse = VclToGtk(GtkToVcl(nResponse));
+ if (GtkButton* pWidget = get_widget_for_response(nResponse))
+ {
+ void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceButton");
+ pButton = static_cast<GtkInstanceButton*>(pData);
+ if (pButton && !pButton->has_click_handler())
+ pButton = nullptr;
+ }
+ return pButton;
+}
+
+namespace {
+
+class GtkInstanceToggleButton : public GtkInstanceButton, public virtual weld::ToggleButton
+{
+protected:
+ GtkToggleButton* m_pToggleButton;
+ gulong m_nToggledSignalId;
+private:
+ static void signalToggled(GtkToggleButton*, gpointer widget)
+ {
+ GtkInstanceToggleButton* pThis = static_cast<GtkInstanceToggleButton*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_toggled();
+ }
+public:
+ GtkInstanceToggleButton(GtkToggleButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceButton(GTK_BUTTON(pButton), pBuilder, bTakeOwnership)
+ , m_pToggleButton(pButton)
+ , m_nToggledSignalId(g_signal_connect(m_pToggleButton, "toggled", G_CALLBACK(signalToggled), this))
+ {
+ }
+
+ virtual void set_active(bool active) override
+ {
+ disable_notify_events();
+ set_inconsistent(false);
+ gtk_toggle_button_set_active(m_pToggleButton, active);
+ enable_notify_events();
+ }
+
+ virtual bool get_active() const override
+ {
+ return gtk_toggle_button_get_active(m_pToggleButton);
+ }
+
+ virtual void set_inconsistent(bool inconsistent) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (inconsistent)
+ gtk_widget_set_state_flags(GTK_WIDGET(m_pToggleButton), GTK_STATE_FLAG_INCONSISTENT, false);
+ else
+ gtk_widget_unset_state_flags(GTK_WIDGET(m_pToggleButton), GTK_STATE_FLAG_INCONSISTENT);
+#else
+ gtk_toggle_button_set_inconsistent(m_pToggleButton, inconsistent);
+#endif
+ }
+
+ virtual bool get_inconsistent() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_widget_get_state_flags(GTK_WIDGET(m_pToggleButton)) & GTK_STATE_FLAG_INCONSISTENT;
+#else
+ return gtk_toggle_button_get_inconsistent(m_pToggleButton);
+#endif
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pToggleButton, m_nToggledSignalId);
+ GtkInstanceButton::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceButton::enable_notify_events();
+ g_signal_handler_unblock(m_pToggleButton, m_nToggledSignalId);
+ }
+
+ virtual ~GtkInstanceToggleButton() override
+ {
+ g_signal_handler_disconnect(m_pToggleButton, m_nToggledSignalId);
+ }
+};
+
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+
+namespace {
+
+void do_grab(GtkWidget* pWidget)
+{
+ GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
+ GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
+ gdk_seat_grab(pSeat, widget_get_surface(pWidget),
+ GDK_SEAT_CAPABILITY_KEYBOARD, true, nullptr, nullptr, nullptr, nullptr);
+}
+
+void do_ungrab(GtkWidget* pWidget)
+{
+ GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
+ GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
+ gdk_seat_ungrab(pSeat);
+}
+
+GtkPositionType show_menu_older_gtk(GtkWidget* pMenuButton, GtkWindow* pMenu, const GdkRectangle& rAnchor,
+ weld::Placement ePlace, bool bTryShrink)
+{
+ //place the toplevel just below its launcher button
+ GtkWidget* pToplevel = widget_get_toplevel(pMenuButton);
+ gtk_coord x, y, absx, absy;
+ gtk_widget_translate_coordinates(pMenuButton, pToplevel, rAnchor.x, rAnchor.y, &x, &y);
+ GdkSurface* pWindow = widget_get_surface(pToplevel);
+ gdk_window_get_position(pWindow, &absx, &absy);
+
+ x += absx;
+ y += absy;
+
+ gint nButtonHeight = rAnchor.height;
+ gint nButtonWidth = rAnchor.width;
+ if (ePlace == weld::Placement::Under)
+ y += nButtonHeight;
+ else
+ x += nButtonWidth;
+
+ gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), pMenu);
+ gtk_window_set_transient_for(pMenu, GTK_WINDOW(pToplevel));
+
+ gint nMenuWidth, nMenuHeight;
+ gtk_widget_get_size_request(GTK_WIDGET(pMenu), &nMenuWidth, &nMenuHeight);
+
+ if (nMenuWidth == -1 || nMenuHeight == -1)
+ {
+ GtkRequisition req;
+ gtk_widget_get_preferred_size(GTK_WIDGET(pMenu), nullptr, &req);
+ if (nMenuWidth == -1)
+ nMenuWidth = req.width;
+ if (nMenuHeight == -1)
+ nMenuHeight = req.height;
+ }
+
+ bool bSwapForRTL = SwapForRTL(pMenuButton);
+ if (bSwapForRTL)
+ {
+ if (ePlace == weld::Placement::Under)
+ x += nButtonWidth;
+ else
+ x -= nButtonWidth;
+ x -= nMenuWidth;
+ }
+
+ tools::Rectangle aWorkArea(::get_monitor_workarea(pMenuButton));
+
+ // shrink it a little, I find it reassuring to see a little margin with a
+ // long menu to know the menu is fully on screen
+ aWorkArea.AdjustTop(8);
+ aWorkArea.AdjustBottom(-8);
+ aWorkArea.AdjustLeft(8);
+ aWorkArea.AdjustRight(-8);
+
+ GtkPositionType ePosUsed;
+
+ if (ePlace == weld::Placement::Under)
+ {
+ gint endx = x + nMenuWidth;
+ if (endx > aWorkArea.Right())
+ x -= endx - aWorkArea.Right();
+ if (x < 0)
+ x = 0;
+
+ ePosUsed = GTK_POS_BOTTOM;
+ gint endy = y + nMenuHeight;
+ gint nMissingBelow = endy - aWorkArea.Bottom();
+ if (nMissingBelow > 0)
+ {
+ gint nNewY = y - (nButtonHeight + nMenuHeight);
+ gint nMissingAbove = aWorkArea.Top() - nNewY;
+ if (nMissingAbove > 0)
+ {
+ if (bTryShrink)
+ {
+ if (nMissingBelow <= nMissingAbove)
+ nMenuHeight -= nMissingBelow;
+ else
+ {
+ nMenuHeight -= nMissingAbove;
+ y = aWorkArea.Top();
+ ePosUsed = GTK_POS_TOP;
+ }
+ gtk_widget_set_size_request(GTK_WIDGET(pMenu), nMenuWidth, nMenuHeight);
+ }
+ else
+ {
+ if (nMissingBelow <= nMissingAbove)
+ y -= nMissingBelow;
+ else
+ {
+ y = aWorkArea.Top();
+ ePosUsed = GTK_POS_TOP;
+ }
+ }
+ }
+ else
+ {
+ y = nNewY;
+ ePosUsed = GTK_POS_TOP;
+ }
+ }
+ }
+ else
+ {
+ if (!bSwapForRTL)
+ {
+ ePosUsed = GTK_POS_RIGHT;
+ gint endx = x + nMenuWidth;
+ gint nMissingAfter = endx - aWorkArea.Right();
+ if (nMissingAfter > 0)
+ {
+ gint nNewX = x - (nButtonWidth + nMenuWidth);
+ if (nNewX >= aWorkArea.Left())
+ {
+ x = nNewX;
+ ePosUsed = GTK_POS_LEFT;
+ }
+ }
+ }
+ else
+ {
+ ePosUsed = GTK_POS_LEFT;
+ gint startx = x;
+ gint nMissingBefore = aWorkArea.Left() - startx;
+ if (nMissingBefore > 0)
+ {
+ gint nNewX = x + (nButtonWidth + nMenuWidth);
+ if (nNewX + nMenuWidth < aWorkArea.Right())
+ {
+ x = nNewX;
+ ePosUsed = GTK_POS_RIGHT;
+ }
+ }
+ }
+ }
+
+ gtk_window_move(pMenu, x, y);
+
+ return ePosUsed;
+}
+
+bool show_menu_newer_gtk(GtkWidget* pComboBox, GtkWindow* pMenu, const GdkRectangle &rAnchor,
+ weld::Placement ePlace, bool bTryShrink)
+{
+ static auto window_move_to_rect = reinterpret_cast<void (*) (GdkWindow*, const GdkRectangle*, GdkGravity,
+ GdkGravity, GdkAnchorHints, gint, gint)>(
+ dlsym(nullptr, "gdk_window_move_to_rect"));
+ if (!window_move_to_rect)
+ return false;
+
+ // under wayland gdk_window_move_to_rect works great for me, but in my current
+ // gtk 3.24 under X it leaves part of long menus outside the work area
+ GdkDisplay *pDisplay = gtk_widget_get_display(pComboBox);
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ return false;
+
+ //place the toplevel just below its launcher button
+ GtkWidget* pToplevel = widget_get_toplevel(pComboBox);
+ gtk_coord x, y;
+ gtk_widget_translate_coordinates(pComboBox, pToplevel, rAnchor.x, rAnchor.y, &x, &y);
+
+ gtk_widget_realize(GTK_WIDGET(pMenu));
+ gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), pMenu);
+ gtk_window_set_transient_for(pMenu, GTK_WINDOW(pToplevel));
+
+ bool bSwapForRTL = SwapForRTL(GTK_WIDGET(pComboBox));
+
+ GdkGravity rect_anchor;
+ GdkGravity menu_anchor;
+
+ if (ePlace == weld::Placement::Under)
+ {
+ rect_anchor = !bSwapForRTL ? GDK_GRAVITY_SOUTH_WEST : GDK_GRAVITY_SOUTH_EAST;
+ menu_anchor = !bSwapForRTL ? GDK_GRAVITY_NORTH_WEST : GDK_GRAVITY_NORTH_EAST;
+ }
+ else
+ {
+ rect_anchor = !bSwapForRTL ? GDK_GRAVITY_NORTH_EAST : GDK_GRAVITY_NORTH_WEST;
+ menu_anchor = !bSwapForRTL ? GDK_GRAVITY_NORTH_WEST : GDK_GRAVITY_NORTH_EAST;
+ }
+
+ GdkAnchorHints anchor_hints = static_cast<GdkAnchorHints>(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE);
+ if (bTryShrink)
+ anchor_hints = static_cast<GdkAnchorHints>(anchor_hints | GDK_ANCHOR_RESIZE);
+ GdkRectangle rect {x, y, rAnchor.width, rAnchor.height};
+ GdkSurface* toplevel = widget_get_surface(GTK_WIDGET(pMenu));
+
+ window_move_to_rect(toplevel, &rect, rect_anchor, menu_anchor, anchor_hints,
+ 0, 0);
+
+ return true;
+}
+
+GtkPositionType show_menu(GtkWidget* pMenuButton, GtkWindow* pMenu, const GdkRectangle& rAnchor,
+ weld::Placement ePlace, bool bTryShrink)
+{
+ // we only use ePosUsed in the replacement-for-X-popover case of a
+ // MenuButton, so we only need it when show_menu_older_gtk is used
+ GtkPositionType ePosUsed = GTK_POS_BOTTOM;
+
+ // tdf#120764 It isn't allowed under wayland to have two visible popups that share
+ // the same top level parent. The problem is that since gtk 3.24 tooltips are also
+ // implemented as popups, which means that we cannot show any popup if there is a
+ // visible tooltip.
+ GtkWidget* pParent = widget_get_toplevel(pMenuButton);
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
+ if (pFrame)
+ {
+ // hide any current tooltip
+ pFrame->HideTooltip();
+ // don't allow any more to appear until menu is dismissed
+ pFrame->BlockTooltip();
+ }
+
+ // try with gdk_window_move_to_rect, but if that's not available, try without
+ if (!show_menu_newer_gtk(pMenuButton, pMenu, rAnchor, ePlace, bTryShrink))
+ ePosUsed = show_menu_older_gtk(pMenuButton, pMenu, rAnchor, ePlace, bTryShrink);
+ gtk_widget_show_all(GTK_WIDGET(pMenu));
+ gtk_widget_grab_focus(GTK_WIDGET(pMenu));
+ do_grab(GTK_WIDGET(pMenu));
+
+ return ePosUsed;
+}
+
+}
+#endif
+
+namespace {
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+bool button_event_is_outside(GtkWidget* pMenuHack, GdkEventButton* pEvent)
+{
+ //we want to pop down if the button was released outside our popup
+ gdouble x = pEvent->x_root;
+ gdouble y = pEvent->y_root;
+
+ gint window_x, window_y;
+ GdkSurface* pWindow = widget_get_surface(pMenuHack);
+ gdk_window_get_position(pWindow, &window_x, &window_y);
+
+ GtkAllocation alloc;
+ gtk_widget_get_allocation(pMenuHack, &alloc);
+ gint x1 = window_x;
+ gint y1 = window_y;
+ gint x2 = x1 + alloc.width;
+ gint y2 = y1 + alloc.height;
+
+ if (x > x1 && x < x2 && y > y1 && y < y2)
+ return false;
+
+ return true;
+}
+
+GtkPositionType MovePopoverContentsToWindow(GtkWidget* pPopover, GtkWindow* pMenuHack, GtkWidget* pAnchor,
+ const GdkRectangle& rAnchor, weld::Placement ePlace)
+{
+ //set border width
+ gtk_container_set_border_width(GTK_CONTAINER(pMenuHack), gtk_container_get_border_width(GTK_CONTAINER(pPopover)));
+
+ //steal popover contents and smuggle into toplevel display window
+ GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pPopover));
+ g_object_ref(pChild);
+ gtk_container_remove(GTK_CONTAINER(pPopover), pChild);
+ gtk_container_add(GTK_CONTAINER(pMenuHack), pChild);
+ g_object_unref(pChild);
+
+ GtkPositionType eRet = show_menu(pAnchor, pMenuHack, rAnchor, ePlace, false);
+
+ gtk_grab_add(GTK_WIDGET(pMenuHack));
+
+ GdkSurface* pSurface = widget_get_surface(GTK_WIDGET(pMenuHack));
+ g_object_set_data(G_OBJECT(pSurface), "g-lo-InstancePopup", GINT_TO_POINTER(true));
+
+ return eRet;
+}
+
+void MoveWindowContentsToPopover(GtkWindow* pMenuHack, GtkWidget* pPopover, GtkWidget* pAnchor)
+{
+ bool bHadFocus = gtk_window_has_toplevel_focus(pMenuHack);
+
+ do_ungrab(GTK_WIDGET(pMenuHack));
+
+ gtk_grab_remove(GTK_WIDGET(pMenuHack));
+
+ gtk_widget_hide(GTK_WIDGET(pMenuHack));
+ //put contents back from where the came from
+ GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pMenuHack));
+ g_object_ref(pChild);
+ gtk_container_remove(GTK_CONTAINER(pMenuHack), pChild);
+ gtk_container_add(GTK_CONTAINER(pPopover), pChild);
+ g_object_unref(pChild);
+
+ GdkSurface* pSurface = widget_get_surface(GTK_WIDGET(pMenuHack));
+ g_object_set_data(G_OBJECT(pSurface), "g-lo-InstancePopup", GINT_TO_POINTER(false));
+
+ // so gdk_window_move_to_rect will work again the next time
+ gtk_widget_unrealize(GTK_WIDGET(pMenuHack));
+
+ gtk_widget_set_size_request(GTK_WIDGET(pMenuHack), -1, -1);
+
+ // undo show_menu tooltip blocking
+ GtkWidget* pParent = widget_get_toplevel(pAnchor);
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
+ if (pFrame)
+ pFrame->UnblockTooltip();
+
+ if (bHadFocus)
+ {
+ GdkSurface* pParentSurface = pParent ? widget_get_surface(pParent) : nullptr;
+ void* pParentIsPopover = pParentSurface ? g_object_get_data(G_OBJECT(pParentSurface), "g-lo-InstancePopup") : nullptr;
+ if (pParentIsPopover)
+ do_grab(pAnchor);
+ gtk_widget_grab_focus(pAnchor);
+ }
+}
+
+#endif
+
+/* four types of uses of this
+ a) textual menubutton, always with pan-down symbol, e.g. math, format, font, modify
+ b) image + text, always with additional pan-down symbol, e.g. writer, format, watermark
+ c) gear menu, never with text and without pan-down symbol where there is a replacement
+ icon for pan-down, e.g. file, new, templates
+ d) image, always with additional pan-down symbol, e.g. calc, insert, header/footer */
+#if !GTK_CHECK_VERSION(4, 0, 0)
+class GtkInstanceMenuButton : public GtkInstanceToggleButton, public MenuHelper, public virtual weld::MenuButton
+#else
+class GtkInstanceMenuButton : public GtkInstanceWidget, public MenuHelper, public virtual weld::MenuButton
+#endif
+{
+protected:
+ GtkMenuButton* m_pMenuButton;
+private:
+ GtkBox* m_pBox;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkImage* m_pImage;
+#else
+ GtkPicture* m_pImage;
+ GtkToggleButton* m_pMenuButtonToggleButton;
+#endif
+ GtkWidget* m_pLabel;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ //popover cannot escape dialog under X so stick up own window instead
+ GtkWindow* m_pMenuHack;
+ //when doing so, if it's a toolbar menubutton align the menu to the full toolitem
+ GtkWidget* m_pMenuHackAlign;
+ bool m_nButtonPressSeen;
+ gulong m_nSignalId;
+#endif
+ GtkWidget* m_pPopover;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nToggledSignalId;
+ std::optional<vcl::Font> m_xFont;
+ WidgetBackground m_aCustomBackground;
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void signalMenuButtonToggled(GtkWidget*, gpointer widget)
+ {
+ GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->menu_toggled();
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ void menu_toggled()
+ {
+ if (!m_pMenuHack)
+ return;
+ if (!get_active())
+ {
+ m_nButtonPressSeen = false;
+ MoveWindowContentsToPopover(m_pMenuHack, m_pPopover, GTK_WIDGET(m_pMenuButton));
+ }
+ else
+ {
+ GtkWidget* pAnchor = m_pMenuHackAlign ? m_pMenuHackAlign : GTK_WIDGET(m_pMenuButton);
+ GdkRectangle aAnchor {0, 0, gtk_widget_get_allocated_width(pAnchor), gtk_widget_get_allocated_height(pAnchor) };
+ GtkPositionType ePosUsed = MovePopoverContentsToWindow(m_pPopover, m_pMenuHack, pAnchor, aAnchor, weld::Placement::Under);
+ // tdf#132540 keep the placeholder popover on this same side as the replacement menu
+ gtk_popover_set_position(gtk_menu_button_get_popover(m_pMenuButton), ePosUsed);
+ }
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
+ {
+ GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
+ pThis->grab_broken(pEvent);
+ }
+
+ void grab_broken(const GdkEventGrabBroken *event)
+ {
+ if (event->grab_window == nullptr)
+ {
+ set_active(false);
+ }
+ else if (!g_object_get_data(G_OBJECT(event->grab_window), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
+ {
+ //try and regrab, so when we lose the grab to the menu of the color palette
+ //combobox we regain it so the color palette doesn't itself disappear on next
+ //click on the color palette combobox
+ do_grab(GTK_WIDGET(m_pMenuHack));
+ }
+ }
+
+ static gboolean signalButtonPress(GtkWidget* /*pWidget*/, GdkEventButton* /*pEvent*/, gpointer widget)
+ {
+ GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
+ pThis->m_nButtonPressSeen = true;
+ return false;
+ }
+
+ static gboolean signalButtonRelease(GtkWidget* /*pWidget*/, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
+ if (pThis->m_nButtonPressSeen && button_event_is_outside(GTK_WIDGET(pThis->m_pMenuHack), pEvent))
+ pThis->set_active(false);
+ return false;
+ }
+
+ static gboolean keyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
+ return pThis->key_press(pEvent);
+ }
+
+ bool key_press(const GdkEventKey* pEvent)
+ {
+ if (pEvent->keyval == GDK_KEY_Escape)
+ {
+ set_active(false);
+ return true;
+ }
+ return false;
+ }
+#endif
+
+ void ensure_image_widget()
+ {
+ if (m_pImage)
+ return;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_pImage = GTK_IMAGE(gtk_image_new());
+ gtk_box_pack_start(m_pBox, GTK_WIDGET(m_pImage), false, false, 0);
+ gtk_box_reorder_child(m_pBox, GTK_WIDGET(m_pImage), 0);
+#else
+ m_pImage = GTK_PICTURE(gtk_picture_new());
+ gtk_widget_set_halign(GTK_WIDGET(m_pImage), GTK_ALIGN_CENTER);
+ gtk_widget_set_valign(GTK_WIDGET(m_pImage), GTK_ALIGN_CENTER);
+ gtk_box_prepend(m_pBox, GTK_WIDGET(m_pImage));
+ gtk_widget_set_halign(m_pLabel, GTK_ALIGN_START);
+#endif
+ gtk_widget_show(GTK_WIDGET(m_pImage));
+ }
+
+ static void signalFlagsChanged(GtkToggleButton* pToggleButton, GtkStateFlags flags, gpointer widget)
+ {
+ GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
+ bool bOldChecked = flags & GTK_STATE_FLAG_CHECKED;
+ bool bNewChecked = gtk_widget_get_state_flags(GTK_WIDGET(pToggleButton)) & GTK_STATE_FLAG_CHECKED;
+ if (bOldChecked == bNewChecked)
+ return;
+ if (bOldChecked && gtk_widget_get_focus_on_click(GTK_WIDGET(pToggleButton)))
+ {
+ // grab focus back to the toggle button if the menu was popped down
+ gtk_widget_grab_focus(GTK_WIDGET(pToggleButton));
+ }
+ SolarMutexGuard aGuard;
+ pThis->signal_toggled();
+ }
+
+public:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkInstanceMenuButton(GtkMenuButton* pMenuButton, GtkWidget* pMenuAlign, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pMenuButton), pBuilder, bTakeOwnership)
+ , MenuHelper(gtk_menu_button_get_popup(pMenuButton), false)
+#else
+ GtkInstanceMenuButton(GtkMenuButton* pMenuButton, GtkWidget* pMenuAlign, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pMenuButton), pBuilder, bTakeOwnership)
+ , MenuHelper(GTK_POPOVER_MENU(gtk_menu_button_get_popover(pMenuButton)), false)
+#endif
+ , m_pMenuButton(pMenuButton)
+ , m_pImage(nullptr)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_pMenuHack(nullptr)
+ , m_pMenuHackAlign(pMenuAlign)
+ , m_nButtonPressSeen(true)
+ , m_nSignalId(0)
+#endif
+ , m_pPopover(nullptr)
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_aCustomBackground(GTK_WIDGET(pMenuButton))
+#endif
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // tdf#142924 "toggled" is too late to use to populate changes to the menu,
+ // so use "state-flag-changed" on GTK_STATE_FLAG_CHECKED instead which
+ // happens before "toggled"
+ g_signal_handler_disconnect(m_pToggleButton, m_nToggledSignalId);
+ m_nToggledSignalId = g_signal_connect(m_pToggleButton, "state-flags-changed", G_CALLBACK(signalFlagsChanged), this);
+
+ m_pLabel = gtk_bin_get_child(GTK_BIN(m_pMenuButton));
+ m_pImage = get_image_widget(GTK_WIDGET(m_pMenuButton));
+ m_pBox = formatMenuButton(m_pLabel);
+#else
+ GtkWidget* pToggleButton = gtk_widget_get_first_child(GTK_WIDGET(m_pMenuButton));
+ assert(GTK_IS_TOGGLE_BUTTON(pToggleButton));
+ m_pMenuButtonToggleButton = GTK_TOGGLE_BUTTON(pToggleButton);
+ m_nToggledSignalId = g_signal_connect(m_pMenuButtonToggleButton, "state-flags-changed", G_CALLBACK(signalFlagsChanged), this);
+ GtkWidget* pChild = gtk_button_get_child(GTK_BUTTON(pToggleButton));
+ m_pBox = GTK_IS_BOX(pChild) ? GTK_BOX(pChild) : nullptr;
+ m_pLabel = m_pBox ? gtk_widget_get_first_child(GTK_WIDGET(m_pBox)) : nullptr;
+ (void)pMenuAlign;
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_insert_action_group(GTK_WIDGET(m_pMenuButton), "menu", m_pActionGroup);
+
+ update_action_group_from_popover_model();
+#endif
+ }
+
+ virtual void set_size_request(int nWidth, int nHeight) override
+ {
+ // tweak the label to get a narrower size to stick
+ if (GTK_IS_LABEL(m_pLabel))
+ gtk_label_set_ellipsize(GTK_LABEL(m_pLabel), PANGO_ELLIPSIZE_MIDDLE);
+ gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
+ }
+
+ virtual void set_label(const OUString& rText) override
+ {
+ ::set_label(GTK_LABEL(m_pLabel), rText);
+ }
+
+ virtual OUString get_label() const override
+ {
+ return ::get_label(GTK_LABEL(m_pLabel));
+ }
+
+ virtual void set_image(VirtualDevice* pDevice) override
+ {
+ ensure_image_widget();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ picture_set_from_virtual_device(m_pImage, pDevice);
+#else
+ image_set_from_virtual_device(m_pImage, pDevice);
+#endif
+ }
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
+ {
+ ensure_image_widget();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ picture_set_from_xgraphic(m_pImage, rImage);
+#else
+ image_set_from_xgraphic(m_pImage, rImage);
+#endif
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ virtual void set_from_icon_name(const OUString& rIconName) override
+ {
+ ensure_image_widget();
+ picture_set_from_icon_name(m_pImage, rIconName);
+ }
+
+ virtual void set_custom_button(VirtualDevice* pDevice) override
+ {
+ m_aCustomBackground.use_custom_content(pDevice);
+ }
+
+ virtual void set_inconsistent(bool inconsistent) override
+ {
+ if (inconsistent)
+ gtk_widget_set_state_flags(GTK_WIDGET(m_pMenuButton), GTK_STATE_FLAG_INCONSISTENT, false);
+ else
+ gtk_widget_unset_state_flags(GTK_WIDGET(m_pMenuButton), GTK_STATE_FLAG_INCONSISTENT);
+ }
+
+ virtual bool get_inconsistent() const override
+ {
+ return gtk_widget_get_state_flags(GTK_WIDGET(m_pMenuButton)) & GTK_STATE_FLAG_INCONSISTENT;
+ }
+
+ virtual void set_active(bool active) override
+ {
+ disable_notify_events();
+ set_inconsistent(false);
+ if (active)
+ gtk_menu_button_popup(m_pMenuButton);
+ else
+ gtk_menu_button_popdown(m_pMenuButton);
+ enable_notify_events();
+ }
+
+ virtual bool get_active() const override
+ {
+ GtkPopover* pPopover = gtk_menu_button_get_popover(m_pMenuButton);
+ return pPopover && gtk_widget_get_visible(GTK_WIDGET(pPopover));
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_xFont = rFont;
+ GtkLabel* pChild = ::get_label_widget(GTK_WIDGET(m_pMenuButton));
+ ::set_font(pChild, rFont);
+ }
+
+ virtual vcl::Font get_font() override
+ {
+ if (m_xFont)
+ return *m_xFont;
+ return GtkInstanceWidget::get_font();
+ }
+#else
+ virtual void set_active(bool bActive) override
+ {
+ bool bWasActive = get_active();
+ GtkInstanceToggleButton::set_active(bActive);
+ if (bWasActive && !bActive && gtk_widget_get_focus_on_click(GTK_WIDGET(m_pMenuButton)))
+ {
+ // grab focus back to the toggle button if the menu was popped down
+ gtk_widget_grab_focus(GTK_WIDGET(m_pMenuButton));
+ }
+ }
+#endif
+
+ virtual void insert_item(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, VirtualDevice* pImageSurface, TriState eCheckRadioFalse) override
+ {
+ MenuHelper::insert_item(pos, rId, rStr, pIconName, pImageSurface, eCheckRadioFalse);
+ }
+
+ virtual void insert_separator(int pos, const OUString& rId) override
+ {
+ MenuHelper::insert_separator(pos, rId);
+ }
+
+ virtual void remove_item(const OString& rId) override
+ {
+ MenuHelper::remove_item(rId);
+ }
+
+ virtual void clear() override
+ {
+ MenuHelper::clear_items();
+ }
+
+ virtual void set_item_active(const OString& rIdent, bool bActive) override
+ {
+ MenuHelper::set_item_active(rIdent, bActive);
+ }
+
+ virtual void set_item_sensitive(const OString& rIdent, bool bSensitive) override
+ {
+ MenuHelper::set_item_sensitive(rIdent, bSensitive);
+ }
+
+ virtual void set_item_label(const OString& rIdent, const OUString& rLabel) override
+ {
+ MenuHelper::set_item_label(rIdent, rLabel);
+ }
+
+ virtual OUString get_item_label(const OString& rIdent) const override
+ {
+ return MenuHelper::get_item_label(rIdent);
+ }
+
+ virtual void set_item_visible(const OString& rIdent, bool bVisible) override
+ {
+ MenuHelper::set_item_visible(rIdent, bVisible);
+ }
+
+ virtual void signal_item_activate(const OString& rIdent) override
+ {
+ signal_selected(rIdent);
+ }
+
+ virtual void set_popover(weld::Widget* pPopover) override
+ {
+ GtkInstanceWidget* pPopoverWidget = dynamic_cast<GtkInstanceWidget*>(pPopover);
+ m_pPopover = pPopoverWidget ? pPopoverWidget->getWidget() : nullptr;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_menu_button_set_popover(m_pMenuButton, m_pPopover);
+ update_action_group_from_popover_model();
+ return;
+#else
+
+ if (!m_pPopover)
+ {
+ gtk_menu_button_set_popover(m_pMenuButton, nullptr);
+ return;
+ }
+
+ if (!m_pMenuHack)
+ {
+ //under wayland a Popover will work to "escape" the parent dialog, not
+ //so under X, so come up with this hack to use a raw GtkWindow
+ GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay) && gtk_popover_get_constrain_to(GTK_POPOVER(m_pPopover)) == GTK_POPOVER_CONSTRAINT_NONE)
+ {
+ m_pMenuHack = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
+ gtk_window_set_type_hint(m_pMenuHack, GDK_WINDOW_TYPE_HINT_COMBO);
+ // See writer "format, watermark" for true here. Can't interact with the replacement popover otherwise.
+ gtk_window_set_modal(m_pMenuHack, true);
+ gtk_window_set_resizable(m_pMenuHack, false);
+ m_nSignalId = g_signal_connect(GTK_TOGGLE_BUTTON(m_pMenuButton), "toggled", G_CALLBACK(signalMenuButtonToggled), this);
+ g_signal_connect(m_pMenuHack, "key-press-event", G_CALLBACK(keyPress), this);
+ g_signal_connect(m_pMenuHack, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
+ g_signal_connect(m_pMenuHack, "button-press-event", G_CALLBACK(signalButtonPress), this);
+ g_signal_connect(m_pMenuHack, "button-release-event", G_CALLBACK(signalButtonRelease), this);
+ }
+ }
+
+ if (m_pMenuHack)
+ {
+ GtkWidget* pPlaceHolder = gtk_popover_new(GTK_WIDGET(m_pMenuButton));
+ gtk_popover_set_transitions_enabled(GTK_POPOVER(pPlaceHolder), false);
+
+ // tdf#132540 theme the unwanted popover into invisibility
+ GtkStyleContext *pPopoverContext = gtk_widget_get_style_context(pPlaceHolder);
+ GtkCssProvider *pProvider = gtk_css_provider_new();
+ static const gchar data[] = "popover { box-shadow: none; padding: 0 0 0 0; margin: 0 0 0 0; border-image: none; border-image-width: 0 0 0 0; background-image: none; background-color: transparent; border-radius: 0 0 0 0; border-width: 0 0 0 0; border-style: none; border-color: transparent; opacity: 0; min-height: 0; min-width: 0; }";
+ css_provider_load_from_data(pProvider, data, -1);
+ gtk_style_context_add_provider(pPopoverContext, GTK_STYLE_PROVIDER(pProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ gtk_menu_button_set_popover(m_pMenuButton, pPlaceHolder);
+ }
+ else
+ {
+ gtk_menu_button_set_popover(m_pMenuButton, m_pPopover);
+ gtk_widget_show_all(m_pPopover);
+ }
+#endif
+ }
+
+ void set_menu(weld::Menu* pMenu);
+
+ static GtkBox* formatMenuButton(GtkWidget* pLabel)
+ {
+ // format the GtkMenuButton "manually" so we can have the dropdown image in GtkMenuButtons shown
+ // on the right at the same time as an image is shown on the left
+ g_object_ref(pLabel);
+ GtkWidget* pContainer = gtk_widget_get_parent(pLabel);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_remove(GTK_CONTAINER(pContainer), pLabel);
+#else
+ gtk_box_remove(GTK_BOX(pContainer), pLabel);
+#endif
+
+ gint nImageSpacing(2);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkStyleContext *pContext = gtk_widget_get_style_context(pContainer);
+ gtk_style_context_get_style(pContext, "image-spacing", &nImageSpacing, nullptr);
+#endif
+ GtkBox* pBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, nImageSpacing));
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_box_pack_start(pBox, pLabel, true, true, 0);
+#else
+ gtk_widget_set_halign(pLabel, GTK_ALIGN_START);
+ gtk_box_prepend(pBox, pLabel);
+#endif
+ g_object_unref(pLabel);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (gtk_toggle_button_get_mode(GTK_TOGGLE_BUTTON(pContainer)))
+ gtk_box_pack_end(pBox, gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_BUTTON), false, false, 0);
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(pContainer), GTK_WIDGET(pBox));
+#else
+ gtk_box_prepend(GTK_BOX(pContainer), GTK_WIDGET(pBox));
+#endif
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_show_all(GTK_WIDGET(pBox));
+#else
+ gtk_widget_show(GTK_WIDGET(pBox));
+#endif
+
+ return pBox;
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pMenuButtonToggleButton, m_nToggledSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pMenuButtonToggleButton, m_nToggledSignalId);
+ }
+#endif
+
+ virtual ~GtkInstanceMenuButton() override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pMenuButtonToggleButton, m_nToggledSignalId);
+#else
+ if (m_pMenuHack)
+ {
+ g_signal_handler_disconnect(m_pMenuButton, m_nSignalId);
+ gtk_menu_button_set_popover(m_pMenuButton, nullptr);
+ gtk_widget_destroy(GTK_WIDGET(m_pMenuHack));
+ }
+#endif
+ }
+};
+
+class GtkInstanceMenuToggleButton : public GtkInstanceToggleButton, public MenuHelper
+ , public virtual weld::MenuToggleButton
+{
+private:
+ GtkBox* m_pContainer;
+ GtkButton* m_pToggleMenuButton;
+ GtkMenuButton* m_pMenuButton;
+ gulong m_nMenuBtnClickedId;
+ gulong m_nToggleStateFlagsChangedId;
+ gulong m_nMenuBtnStateFlagsChangedId;
+
+ static void signalToggleStateFlagsChanged(GtkWidget* pWidget, GtkStateFlags /*eFlags*/, gpointer widget)
+ {
+ GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
+ // mirror togglebutton state to menubutton
+ gtk_widget_set_state_flags(GTK_WIDGET(pThis->m_pToggleMenuButton), gtk_widget_get_state_flags(pWidget), true);
+ }
+
+ static void signalMenuBtnStateFlagsChanged(GtkWidget* pWidget, GtkStateFlags /*eFlags*/, gpointer widget)
+ {
+ GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
+ // mirror menubutton to togglebutton, keeping depressed state of menubutton
+ GtkStateFlags eToggleFlags = gtk_widget_get_state_flags(GTK_WIDGET(pThis->m_pToggleButton));
+ GtkStateFlags eFlags = gtk_widget_get_state_flags(pWidget);
+ GtkStateFlags eFinalFlags = static_cast<GtkStateFlags>((eFlags & ~GTK_STATE_FLAG_ACTIVE) |
+ (eToggleFlags & GTK_STATE_FLAG_ACTIVE));
+ gtk_widget_set_state_flags(GTK_WIDGET(pThis->m_pToggleButton), eFinalFlags, true);
+ }
+
+ static void signalMenuBtnClicked(GtkButton*, gpointer widget)
+ {
+ GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
+ pThis->launch_menu();
+ }
+
+ void launch_menu()
+ {
+ gtk_widget_set_state_flags(GTK_WIDGET(m_pToggleMenuButton), gtk_widget_get_state_flags(GTK_WIDGET(m_pToggleButton)), true);
+ GtkWidget* pWidget = GTK_WIDGET(m_pToggleButton);
+
+ //run in a sub main loop because we need to keep vcl PopupMenu alive to use
+ //it during DispatchCommand, returning now to the outer loop causes the
+ //launching PopupMenu to be destroyed, instead run the subloop here
+ //until the gtk menu is destroyed
+ GMainLoop* pLoop = g_main_loop_new(nullptr, true);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "closed", G_CALLBACK(g_main_loop_quit), pLoop);
+
+ g_object_ref(m_pMenu);
+ gtk_menu_button_set_popover(m_pMenuButton, nullptr);
+ gtk_widget_set_parent(GTK_WIDGET(m_pMenu), pWidget);
+ gtk_popover_set_position(GTK_POPOVER(m_pMenu), GTK_POS_BOTTOM);
+ gtk_popover_popup(GTK_POPOVER(m_pMenu));
+#else
+ gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
+
+#if GTK_CHECK_VERSION(3,22,0)
+ if (gtk_check_version(3, 22, 0) == nullptr)
+ {
+ // Send a keyboard event through gtk_main_do_event to toggle any active tooltip offs
+ // before trying to launch the menu
+ // https://gitlab.gnome.org/GNOME/gtk/issues/1785
+ // Fixed in GTK 2.34
+ GdkEvent *pKeyEvent = GtkSalFrame::makeFakeKeyPress(pWidget);
+ gtk_main_do_event(pKeyEvent);
+
+ GdkEvent *pTriggerEvent = gtk_get_current_event();
+ if (!pTriggerEvent)
+ pTriggerEvent = pKeyEvent;
+
+ gtk_menu_popup_at_widget(m_pMenu, pWidget, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, pTriggerEvent);
+
+ gdk_event_free(pKeyEvent);
+ }
+ else
+#endif
+ {
+ guint nButton;
+ guint32 nTime;
+
+ //typically there is an event, and we can then distinguish if this was
+ //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
+ //doesn't)
+ GdkEvent *pEvent = gtk_get_current_event();
+ if (pEvent)
+ {
+ gdk_event_get_button(pEvent, &nButton);
+ nTime = gdk_event_get_time(pEvent);
+ }
+ else
+ {
+ nButton = 0;
+ nTime = GtkSalFrame::GetLastInputEventTime();
+ }
+
+ gtk_menu_popup(m_pMenu, nullptr, nullptr, nullptr, nullptr, nButton, nTime);
+ }
+#endif
+
+ if (g_main_loop_is_running(pLoop))
+ main_loop_run(pLoop);
+
+ g_main_loop_unref(pLoop);
+ g_signal_handler_disconnect(m_pMenu, nSignalId);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_unparent(GTK_WIDGET(m_pMenu));
+ gtk_menu_button_set_popover(m_pMenuButton, GTK_WIDGET(m_pMenu));
+ g_object_unref(m_pMenu);
+#endif
+
+ }
+
+ static gboolean signalMenuToggleButton(GtkWidget*, gboolean bGroupCycling, gpointer widget)
+ {
+ GtkInstanceMenuToggleButton* pThis = static_cast<GtkInstanceMenuToggleButton*>(widget);
+ return gtk_widget_mnemonic_activate(GTK_WIDGET(pThis->m_pToggleButton), bGroupCycling);
+ }
+
+public:
+ GtkInstanceMenuToggleButton(GtkBuilder* pMenuToggleButtonBuilder, GtkMenuButton* pMenuButton,
+ GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(gtk_builder_get_object(pMenuToggleButtonBuilder, "togglebutton")),
+ pBuilder, bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , MenuHelper(gtk_menu_button_get_popup(pMenuButton), false)
+#else
+ , MenuHelper(GTK_POPOVER_MENU(gtk_menu_button_get_popover(pMenuButton)), false)
+#endif
+ , m_pContainer(GTK_BOX(gtk_builder_get_object(pMenuToggleButtonBuilder, "box")))
+ , m_pToggleMenuButton(GTK_BUTTON(gtk_builder_get_object(pMenuToggleButtonBuilder, "menubutton")))
+ , m_pMenuButton(pMenuButton)
+ , m_nMenuBtnClickedId(g_signal_connect(m_pToggleMenuButton, "clicked", G_CALLBACK(signalMenuBtnClicked), this))
+ , m_nToggleStateFlagsChangedId(g_signal_connect(m_pToggleButton, "state-flags-changed", G_CALLBACK(signalToggleStateFlagsChanged), this))
+ , m_nMenuBtnStateFlagsChangedId(g_signal_connect(m_pToggleMenuButton, "state-flags-changed", G_CALLBACK(signalMenuBtnStateFlagsChanged), this))
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkInstanceMenuButton::formatMenuButton(gtk_bin_get_child(GTK_BIN(m_pMenuButton)));
+#endif
+
+ insertAsParent(GTK_WIDGET(m_pMenuButton), GTK_WIDGET(m_pContainer));
+ gtk_widget_hide(GTK_WIDGET(m_pMenuButton));
+
+ // move the first GtkMenuButton child, as created by GtkInstanceMenuButton ctor, into the GtkToggleButton
+ // instead, leaving just the indicator behind in the GtkMenuButton
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pButtonBox = gtk_bin_get_child(GTK_BIN(m_pMenuButton));
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pButtonBox));
+ int nGroup = 0;
+ for (GList* pChild = g_list_first(pChildren); pChild && nGroup < 2; pChild = g_list_next(pChild), ++nGroup)
+ {
+ GtkWidget* pWidget = static_cast<GtkWidget*>(pChild->data);
+ g_object_ref(pWidget);
+ gtk_container_remove(GTK_CONTAINER(pButtonBox), pWidget);
+ if (nGroup == 0)
+ gtk_container_add(GTK_CONTAINER(m_pToggleButton), pWidget);
+ else
+ gtk_container_add(GTK_CONTAINER(m_pToggleMenuButton), pWidget);
+ gtk_widget_show_all(pWidget);
+ g_object_unref(pWidget);
+ }
+ g_list_free(pChildren);
+#else
+ GtkWidget* pChild;
+ if (gtk_check_version(4, 5, 0) == nullptr)
+ {
+ pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pMenuButton));
+ pChild = gtk_widget_get_first_child(pChild);
+ pChild = gtk_widget_get_first_child(pChild);
+ }
+ else
+ pChild = gtk_widget_get_last_child(GTK_WIDGET(m_pMenuButton));
+ g_object_ref(pChild);
+ gtk_widget_unparent(pChild);
+ gtk_button_set_child(GTK_BUTTON(m_pToggleButton), pChild);
+ g_object_unref(pChild);
+#endif
+
+ // match the GtkToggleButton relief to the GtkMenuButton
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ const GtkReliefStyle eStyle = gtk_button_get_relief(GTK_BUTTON(m_pMenuButton));
+ gtk_button_set_relief(GTK_BUTTON(m_pToggleButton), eStyle);
+ gtk_button_set_relief(GTK_BUTTON(m_pToggleMenuButton), eStyle);
+#else
+ const bool bStyle = gtk_menu_button_get_has_frame(GTK_MENU_BUTTON(m_pMenuButton));
+ gtk_button_set_has_frame(GTK_BUTTON(m_pToggleButton), bStyle);
+ gtk_button_set_has_frame(GTK_BUTTON(m_pToggleMenuButton), bStyle);
+#endif
+
+ // move the GtkMenuButton margins up to the new parent
+ gtk_widget_set_margin_top(GTK_WIDGET(m_pContainer),
+ gtk_widget_get_margin_top(GTK_WIDGET(m_pMenuButton)));
+ gtk_widget_set_margin_bottom(GTK_WIDGET(m_pContainer),
+ gtk_widget_get_margin_bottom(GTK_WIDGET(m_pMenuButton)));
+ gtk_widget_set_margin_start(GTK_WIDGET(m_pContainer),
+ gtk_widget_get_margin_start(GTK_WIDGET(m_pMenuButton)));
+ gtk_widget_set_margin_end(GTK_WIDGET(m_pContainer),
+ gtk_widget_get_margin_end(GTK_WIDGET(m_pMenuButton)));
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_menu_detach(m_pMenu);
+ gtk_menu_attach_to_widget(m_pMenu, GTK_WIDGET(m_pToggleButton), nullptr);
+#else
+ gtk_widget_insert_action_group(GTK_WIDGET(m_pContainer), "menu", m_pActionGroup);
+
+ update_action_group_from_popover_model();
+#endif
+
+ g_signal_connect(m_pContainer, "mnemonic-activate", G_CALLBACK(signalMenuToggleButton), this);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pToggleMenuButton, m_nMenuBtnClickedId);
+ GtkInstanceToggleButton::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceToggleButton::enable_notify_events();
+ g_signal_handler_unblock(m_pToggleMenuButton, m_nMenuBtnClickedId);
+ }
+
+ virtual ~GtkInstanceMenuToggleButton()
+ {
+ g_signal_handler_disconnect(m_pToggleButton, m_nToggleStateFlagsChangedId);
+ g_signal_handler_disconnect(m_pToggleMenuButton, m_nMenuBtnStateFlagsChangedId);
+ g_signal_handler_disconnect(m_pToggleMenuButton, m_nMenuBtnClickedId);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pChild = gtk_button_get_child(GTK_BUTTON(m_pToggleButton));
+ g_object_ref(pChild);
+ gtk_button_set_child(GTK_BUTTON(m_pToggleButton), nullptr);
+ gtk_widget_unparent(pChild);
+ gtk_widget_set_parent(pChild, GTK_WIDGET(m_pMenuButton));
+ g_object_unref(pChild);
+#endif
+ }
+
+ virtual void insert_item(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, VirtualDevice* pImageSurface, TriState eCheckRadioFalse) override
+ {
+ MenuHelper::insert_item(pos, rId, rStr, pIconName, pImageSurface, eCheckRadioFalse);
+ }
+
+ virtual void insert_separator(int pos, const OUString& rId) override
+ {
+ MenuHelper::insert_separator(pos, rId);
+ }
+
+ virtual void remove_item(const OString& rId) override
+ {
+ MenuHelper::remove_item(rId);
+ }
+
+ virtual void clear() override
+ {
+ MenuHelper::clear_items();
+ }
+
+ virtual void set_item_active(const OString& rIdent, bool bActive) override
+ {
+ MenuHelper::set_item_active(rIdent, bActive);
+ }
+
+ virtual void set_item_sensitive(const OString& rIdent, bool bSensitive) override
+ {
+ MenuHelper::set_item_sensitive(rIdent, bSensitive);
+ }
+
+ virtual void set_item_label(const OString& rIdent, const OUString& rLabel) override
+ {
+ MenuHelper::set_item_label(rIdent, rLabel);
+ }
+
+ virtual OUString get_item_label(const OString& rIdent) const override
+ {
+ return MenuHelper::get_item_label(rIdent);
+ }
+
+ virtual void set_item_visible(const OString& rIdent, bool bVisible) override
+ {
+ MenuHelper::set_item_visible(rIdent, bVisible);
+ }
+
+ virtual void signal_item_activate(const OString& rIdent) override
+ {
+ signal_selected(rIdent);
+ }
+
+ virtual void set_popover(weld::Widget* /*pPopover*/) override
+ {
+ assert(false && "not implemented");
+ }
+};
+
+class GtkInstanceMenu : public MenuHelper, public virtual weld::Menu
+{
+protected:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ std::vector<GtkMenuItem*> m_aExtraItems;
+#endif
+ OString m_sActivated;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ MenuHelper* m_pTopLevelMenuHelper;
+#endif
+
+private:
+ virtual void signal_item_activate(const OString& rIdent) override
+ {
+ m_sActivated = rIdent;
+ weld::Menu::signal_activate(m_sActivated);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ void clear_extras()
+ {
+ if (m_aExtraItems.empty())
+ return;
+ if (m_pTopLevelMenuHelper)
+ {
+ for (auto a : m_aExtraItems)
+ m_pTopLevelMenuHelper->remove_from_map(a);
+ }
+ m_aExtraItems.clear();
+ }
+#endif
+
+public:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkInstanceMenu(GtkMenu* pMenu, bool bTakeOwnership)
+#else
+ GtkInstanceMenu(GtkPopoverMenu* pMenu, bool bTakeOwnership)
+#endif
+ : MenuHelper(pMenu, bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_pTopLevelMenuHelper(nullptr)
+#endif
+ {
+ g_object_set_data(G_OBJECT(m_pMenu), "g-lo-GtkInstanceMenu", this);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // tdf#122527 if we're welding a submenu of a menu of a MenuButton,
+ // then find that MenuButton parent so that when adding items to this
+ // menu we can inform the MenuButton of their addition
+ GtkMenu* pTopLevelMenu = pMenu;
+ while (true)
+ {
+ GtkWidget* pAttached = gtk_menu_get_attach_widget(pTopLevelMenu);
+ if (!pAttached || !GTK_IS_MENU_ITEM(pAttached))
+ break;
+ GtkWidget* pParent = gtk_widget_get_parent(pAttached);
+ if (!pParent || !GTK_IS_MENU(pParent))
+ break;
+ pTopLevelMenu = GTK_MENU(pParent);
+ }
+ if (pTopLevelMenu == pMenu)
+ return;
+
+ // maybe the toplevel is a menubutton
+ GtkWidget* pAttached = gtk_menu_get_attach_widget(pTopLevelMenu);
+ if (pAttached && GTK_IS_MENU_BUTTON(pAttached))
+ {
+ void* pData = g_object_get_data(G_OBJECT(pAttached), "g-lo-GtkInstanceButton");
+ m_pTopLevelMenuHelper = dynamic_cast<GtkInstanceMenuButton*>(static_cast<GtkInstanceButton*>(pData));
+ }
+ // or maybe a menu
+ if (!m_pTopLevelMenuHelper)
+ {
+ void* pData = g_object_get_data(G_OBJECT(pTopLevelMenu), "g-lo-GtkInstanceMenu");
+ m_pTopLevelMenuHelper = static_cast<GtkInstanceMenu*>(pData);
+ }
+#else
+ update_action_group_from_popover_model();
+#endif
+ }
+
+ virtual OString popup_at_rect(weld::Widget* pParent, const tools::Rectangle& rRect, weld::Placement ePlace) override
+ {
+ m_sActivated.clear();
+
+ GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
+ assert(pGtkWidget);
+ GtkWidget* pWidget = pGtkWidget->getWidget();
+
+ //run in a sub main loop because we need to keep vcl PopupMenu alive to use
+ //it during DispatchCommand, returning now to the outer loop causes the
+ //launching PopupMenu to be destroyed, instead run the subloop here
+ //until the gtk menu is destroyed
+ GMainLoop* pLoop = g_main_loop_new(nullptr, true);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_insert_action_group(pWidget, "menu", m_pActionGroup);
+
+ gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "closed", G_CALLBACK(g_main_loop_quit), pLoop);
+
+ GdkRectangle aRect;
+ pWidget = getPopupRect(pWidget, rRect, aRect);
+
+ GtkWidget* pOrigParent = gtk_widget_get_parent(GTK_WIDGET(m_pMenu));
+ gtk_widget_set_parent(GTK_WIDGET(m_pMenu), pWidget);
+ gtk_popover_set_pointing_to(GTK_POPOVER(m_pMenu), &aRect);
+ if (ePlace == weld::Placement::Under)
+ gtk_popover_set_position(GTK_POPOVER(m_pMenu), GTK_POS_BOTTOM);
+ else
+ {
+ if (SwapForRTL(pWidget))
+ gtk_popover_set_position(GTK_POPOVER(m_pMenu), GTK_POS_LEFT);
+ else
+ gtk_popover_set_position(GTK_POPOVER(m_pMenu), GTK_POS_RIGHT);
+ }
+ gtk_popover_popup(GTK_POPOVER(m_pMenu));
+#else
+ gulong nSignalId = g_signal_connect_swapped(G_OBJECT(m_pMenu), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
+
+#if GTK_CHECK_VERSION(3,22,0)
+ if (gtk_check_version(3, 22, 0) == nullptr)
+ {
+ GdkRectangle aRect;
+ pWidget = getPopupRect(pWidget, rRect, aRect);
+ gtk_menu_attach_to_widget(m_pMenu, pWidget, nullptr);
+
+ // Send a keyboard event through gtk_main_do_event to toggle any active tooltip offs
+ // before trying to launch the menu
+ // https://gitlab.gnome.org/GNOME/gtk/issues/1785
+ // Fixed in GTK 2.34
+ GdkEvent *pKeyEvent = GtkSalFrame::makeFakeKeyPress(pWidget);
+ gtk_main_do_event(pKeyEvent);
+
+ GdkEvent *pTriggerEvent = gtk_get_current_event();
+ if (!pTriggerEvent)
+ pTriggerEvent = pKeyEvent;
+
+ bool bSwapForRTL = SwapForRTL(pWidget);
+
+ if (ePlace == weld::Placement::Under)
+ {
+ if (bSwapForRTL)
+ gtk_menu_popup_at_rect(m_pMenu, widget_get_surface(pWidget), &aRect, GDK_GRAVITY_SOUTH_EAST, GDK_GRAVITY_NORTH_EAST, pTriggerEvent);
+ else
+ gtk_menu_popup_at_rect(m_pMenu, widget_get_surface(pWidget), &aRect, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, pTriggerEvent);
+ }
+ else
+ {
+ if (bSwapForRTL)
+ gtk_menu_popup_at_rect(m_pMenu, widget_get_surface(pWidget), &aRect, GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_NORTH_EAST, pTriggerEvent);
+ else
+ gtk_menu_popup_at_rect(m_pMenu, widget_get_surface(pWidget), &aRect, GDK_GRAVITY_NORTH_EAST, GDK_GRAVITY_NORTH_WEST, pTriggerEvent);
+ }
+
+ gdk_event_free(pKeyEvent);
+ }
+ else
+#else
+ (void) rRect;
+#endif
+ {
+ gtk_menu_attach_to_widget(m_pMenu, pWidget, nullptr);
+
+ guint nButton;
+ guint32 nTime;
+
+ //typically there is an event, and we can then distinguish if this was
+ //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
+ //doesn't)
+ GdkEvent *pEvent = gtk_get_current_event();
+ if (pEvent)
+ {
+ if (!gdk_event_get_button(pEvent, &nButton))
+ nButton = 0;
+ nTime = gdk_event_get_time(pEvent);
+ }
+ else
+ {
+ nButton = 0;
+ nTime = GtkSalFrame::GetLastInputEventTime();
+ }
+
+ gtk_menu_popup(m_pMenu, nullptr, nullptr, nullptr, nullptr, nButton, nTime);
+ }
+#endif
+
+ if (g_main_loop_is_running(pLoop))
+ main_loop_run(pLoop);
+
+ g_main_loop_unref(pLoop);
+ g_signal_handler_disconnect(m_pMenu, nSignalId);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (!pOrigParent)
+ gtk_widget_unparent(GTK_WIDGET(m_pMenu));
+ else
+ gtk_widget_set_parent(GTK_WIDGET(m_pMenu), pOrigParent);
+
+ gtk_widget_insert_action_group(pWidget, "menu", nullptr);
+#else
+ gtk_menu_detach(m_pMenu);
+#endif
+
+ return m_sActivated;
+ }
+
+ virtual void set_sensitive(const OString& rIdent, bool bSensitive) override
+ {
+ set_item_sensitive(rIdent, bSensitive);
+ }
+
+ virtual bool get_sensitive(const OString& rIdent) const override
+ {
+ return get_item_sensitive(rIdent);
+ }
+
+ virtual void set_active(const OString& rIdent, bool bActive) override
+ {
+ set_item_active(rIdent, bActive);
+ }
+
+ virtual bool get_active(const OString& rIdent) const override
+ {
+ return get_item_active(rIdent);
+ }
+
+ virtual void set_visible(const OString& rIdent, bool bShow) override
+ {
+ set_item_visible(rIdent, bShow);
+ }
+
+ virtual void set_label(const OString& rIdent, const OUString& rLabel) override
+ {
+ set_item_label(rIdent, rLabel);
+ }
+
+ virtual OUString get_label(const OString& rIdent) const override
+ {
+ return get_item_label(rIdent);
+ }
+
+ virtual void insert_separator(int pos, const OUString& rId) override
+ {
+ MenuHelper::insert_separator(pos, rId);
+ }
+
+ virtual void clear() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ clear_extras();
+#endif
+ MenuHelper::clear_items();
+ }
+
+ virtual void insert(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, VirtualDevice* pImageSurface,
+ const css::uno::Reference<css::graphic::XGraphic>& rGraphic,
+ TriState eCheckRadioFalse) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pImage = nullptr;
+ if (pIconName)
+ pImage = image_new_from_icon_name(*pIconName);
+ else if (pImageSurface)
+ {
+ pImage = image_new_from_virtual_device(*pImageSurface);
+ }
+ else if (rGraphic)
+ {
+ pImage = image_new_from_xgraphic(rGraphic);
+ }
+
+ 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), OUStringToOString(rId, RTL_TEXTENCODING_UTF8));
+ gtk_menu_shell_append(GTK_MENU_SHELL(m_pMenu), pItem);
+ gtk_widget_show(pItem);
+ GtkMenuItem* pMenuItem = GTK_MENU_ITEM(pItem);
+ m_aExtraItems.push_back(pMenuItem);
+ add_to_map(pMenuItem);
+ if (m_pTopLevelMenuHelper)
+ m_pTopLevelMenuHelper->add_to_map(pMenuItem);
+ if (pos != -1)
+ gtk_menu_reorder_child(m_pMenu, pItem, pos);
+#else
+ SAL_WARN("vcl.gtk", "needs to be implemented for gtk4");
+ (void)pos;
+ (void)rId;
+ (void)rStr;
+ (void)pIconName;
+ (void)pImageSurface;
+ (void)rGraphic;
+ (void)eCheckRadioFalse;
+#endif
+ }
+
+ virtual OString get_id(int pos) const override
+ {
+ return get_item_id(pos);
+ }
+
+ virtual int n_children() const override
+ {
+ return get_n_children();
+ }
+
+ void remove(const OString& rIdent) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!m_aExtraItems.empty())
+ {
+ GtkMenuItem* pMenuItem = m_aMap[rIdent];
+ auto iter = std::find(m_aExtraItems.begin(), m_aExtraItems.end(), pMenuItem);
+ if (iter != m_aExtraItems.end())
+ {
+ if (m_pTopLevelMenuHelper)
+ m_pTopLevelMenuHelper->remove_from_map(pMenuItem);
+ m_aExtraItems.erase(iter);
+ }
+ }
+#endif
+ MenuHelper::remove_item(rIdent);
+ }
+
+ virtual ~GtkInstanceMenu() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ clear_extras();
+#endif
+ g_object_steal_data(G_OBJECT(m_pMenu), "g-lo-GtkInstanceMenu");
+ }
+};
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ vcl::ImageType GtkToVcl(GtkIconSize eSize)
+ {
+ vcl::ImageType eRet;
+ switch (eSize)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ case GTK_ICON_SIZE_MENU:
+ case GTK_ICON_SIZE_SMALL_TOOLBAR:
+ case GTK_ICON_SIZE_BUTTON:
+ eRet = vcl::ImageType::Size16;
+ break;
+ case GTK_ICON_SIZE_LARGE_TOOLBAR:
+ eRet = vcl::ImageType::Size26;
+ break;
+ case GTK_ICON_SIZE_DND:
+ case GTK_ICON_SIZE_DIALOG:
+ eRet = vcl::ImageType::Size32;
+ break;
+ default:
+ case GTK_ICON_SIZE_INVALID:
+ eRet = vcl::ImageType::Small;
+ break;
+#else
+ case GTK_ICON_SIZE_LARGE:
+ eRet = vcl::ImageType::Size32;
+ break;
+ case GTK_ICON_SIZE_NORMAL:
+ default:
+ eRet = vcl::ImageType::Size16;
+ break;
+#endif
+ }
+ return eRet;
+ }
+
+ GtkIconSize VclToGtk(vcl::ImageType eSize)
+ {
+ GtkIconSize eRet;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ switch (eSize)
+ {
+ case vcl::ImageType::Size16:
+ eRet = GTK_ICON_SIZE_SMALL_TOOLBAR;
+ break;
+ case vcl::ImageType::Size26:
+ eRet = GTK_ICON_SIZE_LARGE_TOOLBAR;
+ break;
+ case vcl::ImageType::Size32:
+ eRet = GTK_ICON_SIZE_DIALOG;
+ break;
+ default:
+ O3TL_UNREACHABLE;
+ }
+#else
+ switch (eSize)
+ {
+ case vcl::ImageType::Size26:
+ case vcl::ImageType::Size32:
+ eRet = GTK_ICON_SIZE_LARGE;
+ break;
+ case vcl::ImageType::Size16:
+ default:
+ eRet = GTK_ICON_SIZE_NORMAL;
+ break;
+ }
+#endif
+ return eRet;
+ }
+#endif
+}
+
+void GtkInstanceMenuButton::set_menu(weld::Menu* pMenu)
+{
+ GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
+ m_pPopover = nullptr;
+ m_pMenu = pPopoverWidget ? pPopoverWidget->getMenu() : nullptr;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_menu_button_set_popup(m_pMenuButton, GTK_WIDGET(m_pMenu));
+#else
+ gtk_menu_button_set_popover(m_pMenuButton, GTK_WIDGET(m_pMenu));
+ update_action_group_from_popover_model();
+#endif
+}
+
+namespace {
+
+class GtkInstanceToolbar : public GtkInstanceWidget, public virtual weld::Toolbar
+{
+private:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkToolbar* m_pToolbar;
+#else
+ GtkBox* m_pToolbar;
+ vcl::ImageType m_eImageType;
+#endif
+ GtkCssProvider *m_pMenuButtonProvider;
+
+ std::map<OString, GtkWidget*> m_aMap;
+ std::map<OString, std::unique_ptr<GtkInstanceMenuButton>> m_aMenuButtonMap;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // at the time of writing there is no gtk_menu_tool_button_set_popover available
+ // though there will be in the future
+ // https://gitlab.gnome.org/GNOME/gtk/commit/03e30431a8af9a947a0c4ccab545f24da16bfe17?w=1
+ static void find_menu_button(GtkWidget *pWidget, gpointer user_data)
+ {
+ if (g_strcmp0(gtk_widget_get_name(pWidget), "GtkMenuButton") == 0)
+ {
+ GtkWidget **ppToggleButton = static_cast<GtkWidget**>(user_data);
+ *ppToggleButton = pWidget;
+ }
+ else if (GTK_IS_CONTAINER(pWidget))
+ gtk_container_forall(GTK_CONTAINER(pWidget), find_menu_button, user_data);
+ }
+
+ static void find_menupeer_button(GtkWidget *pWidget, gpointer user_data)
+ {
+ if (g_strcmp0(gtk_widget_get_name(pWidget), "GtkButton") == 0)
+ {
+ GtkWidget **ppButton = static_cast<GtkWidget**>(user_data);
+ *ppButton = pWidget;
+ }
+ else if (GTK_IS_CONTAINER(pWidget))
+ gtk_container_forall(GTK_CONTAINER(pWidget), find_menupeer_button, user_data);
+ }
+#endif
+
+ static void collect(GtkWidget* pItem, gpointer widget)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!GTK_IS_TOOL_ITEM(pItem))
+ return;
+#endif
+ GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
+
+ GtkMenuButton* pMenuButton = nullptr;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_MENU_TOOL_BUTTON(pItem))
+ find_menu_button(pItem, &pMenuButton);
+#else
+ if (GTK_IS_MENU_BUTTON(pItem))
+ pMenuButton = GTK_MENU_BUTTON(pItem);
+#endif
+
+ pThis->add_to_map(pItem, pMenuButton);
+ }
+
+ void add_to_map(GtkWidget* pToolItem, GtkMenuButton* pMenuButton)
+ {
+ OString id = ::get_buildable_id(GTK_BUILDABLE(pToolItem));
+ m_aMap[id] = pToolItem;
+ if (pMenuButton)
+ {
+ m_aMenuButtonMap[id] = std::make_unique<GtkInstanceMenuButton>(pMenuButton, GTK_WIDGET(pToolItem), m_pBuilder, false);
+ // so that, e.g. with focus initially in writer main document then
+ // after clicking the heading menu in the writer navigator focus is
+ // left in the main document and not in the toolbar
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_button_set_focus_on_click(GTK_BUTTON(pMenuButton), false);
+ g_signal_connect(pMenuButton, "toggled", G_CALLBACK(signalItemToggled), this);
+#else
+ gtk_widget_set_focus_on_click(GTK_WIDGET(pMenuButton), false);
+
+ GtkWidget* pToggleButton = gtk_widget_get_first_child(GTK_WIDGET(pMenuButton));
+ assert(GTK_IS_TOGGLE_BUTTON(pToggleButton));
+ g_signal_connect(pToggleButton, "toggled", G_CALLBACK(signalItemToggled), this);
+#endif
+
+ // by default the GtkMenuButton down arrow button is as wide as
+ // a normal button and LibreOffice's original ones are very
+ // narrow, that assumption is fairly baked into the toolbar and
+ // sidebar designs, try and minimize the width of the dropdown
+ // zone.
+ GtkStyleContext *pButtonContext = gtk_widget_get_style_context(GTK_WIDGET(pMenuButton));
+
+ if (!m_pMenuButtonProvider)
+ {
+ m_pMenuButtonProvider = gtk_css_provider_new();
+ static const gchar data[] = "* { "
+ "padding: 0;"
+ "margin-left: 0px;"
+ "margin-right: 0px;"
+ "min-width: 4px;"
+ "}";
+ css_provider_load_from_data(m_pMenuButtonProvider, data, -1);
+ }
+
+ gtk_style_context_add_provider(pButtonContext,
+ GTK_STYLE_PROVIDER(m_pMenuButtonProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!GTK_IS_TOOL_BUTTON(pToolItem))
+#else
+ if (!GTK_IS_BUTTON(pToolItem))
+#endif
+ {
+ return;
+ }
+ g_signal_connect(pToolItem, "clicked", G_CALLBACK(signalItemClicked), this);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void signalItemClicked(GtkToolButton* pItem, gpointer widget)
+#else
+ static void signalItemClicked(GtkButton* pItem, gpointer widget)
+#endif
+ {
+ GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_item_clicked(pItem);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ void signal_item_clicked(GtkToolButton* pItem)
+#else
+ void signal_item_clicked(GtkButton* pItem)
+#endif
+ {
+ signal_clicked(::get_buildable_id(GTK_BUILDABLE(pItem)));
+ }
+
+ static void signalItemToggled(GtkToggleButton* pItem, gpointer widget)
+ {
+ GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_item_toggled(pItem);
+ }
+
+ void signal_item_toggled(GtkToggleButton* pItem)
+ {
+ for (const auto& a : m_aMenuButtonMap)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (a.second->getWidget() == GTK_WIDGET(pItem))
+#else
+ if (a.second->getWidget() == gtk_widget_get_parent(GTK_WIDGET(pItem)))
+#endif
+ {
+ signal_toggle_menu(a.first);
+ break;
+ }
+ }
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void set_item_image(GtkWidget* pItem, GtkWidget* pImage)
+ {
+ if (GTK_IS_BUTTON(pItem))
+ gtk_button_set_child(GTK_BUTTON(pItem), pImage);
+ else if (GTK_IS_MENU_BUTTON(pItem))
+ {
+ // TODO after gtk 4.6 is released require that version and drop this
+ static auto menu_button_set_child = reinterpret_cast<void (*) (GtkMenuButton*, GtkWidget*)>(dlsym(nullptr, "gtk_menu_button_set_child"));
+ if (menu_button_set_child)
+ menu_button_set_child(GTK_MENU_BUTTON(pItem), pImage);
+ }
+ // versions of gtk4 > 4.2.1 might do this on their own
+ gtk_widget_remove_css_class(pItem, "text-button");
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static void set_item_image(GtkToolButton* pItem, const css::uno::Reference<css::graphic::XGraphic>& rIcon)
+#else
+ static void set_item_image(GtkWidget* pItem, const css::uno::Reference<css::graphic::XGraphic>& rIcon)
+#endif
+ {
+ GtkWidget* pImage = image_new_from_xgraphic(rIcon);
+ if (pImage)
+ gtk_widget_show(pImage);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_tool_button_set_icon_widget(pItem, pImage);
+#else
+ set_item_image(pItem, pImage);
+#endif
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ void set_item_image(GtkToolButton* pItem, const VirtualDevice* pDevice)
+#else
+ void set_item_image(GtkWidget* pItem, const VirtualDevice* pDevice)
+#endif
+ {
+ GtkWidget* pImage = nullptr;
+
+ if (pDevice)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ pImage = picture_new_from_virtual_device(*pDevice);
+#else
+ pImage = image_new_from_virtual_device(*pDevice);
+#endif
+ gtk_widget_show(pImage);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_tool_button_set_icon_widget(pItem, pImage);
+#else
+ set_item_image(pItem, pImage);
+#endif
+ gtk_widget_queue_draw(GTK_WIDGET(m_pToolbar));
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* toolbar_get_nth_item(int nIndex) const
+ {
+ return GTK_WIDGET(gtk_toolbar_get_nth_item(m_pToolbar, nIndex));
+ }
+#else
+ GtkWidget* toolbar_get_nth_item(int nIndex) const
+ {
+ int i = 0;
+ for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pToolbar));
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ if (i == nIndex)
+ return pChild;
+ ++i;
+ }
+ return nullptr;
+ }
+#endif
+public:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkInstanceToolbar(GtkToolbar* pToolbar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+#else
+ GtkInstanceToolbar(GtkBox* pToolbar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+#endif
+ : GtkInstanceWidget(GTK_WIDGET(pToolbar), pBuilder, bTakeOwnership)
+ , m_pToolbar(pToolbar)
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_eImageType(vcl::ImageType::Size16)
+#endif
+ , m_pMenuButtonProvider(nullptr)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(pToolbar));
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ collect(pChild, this);
+ }
+#else
+ gtk_container_foreach(GTK_CONTAINER(pToolbar), collect, this);
+#endif
+ }
+
+ void disable_item_notify_events()
+ {
+ for (auto& a : m_aMap)
+ {
+ g_signal_handlers_block_by_func(a.second, reinterpret_cast<void*>(signalItemClicked), this);
+ }
+ }
+
+ void enable_item_notify_events()
+ {
+ for (auto& a : m_aMap)
+ {
+ g_signal_handlers_unblock_by_func(a.second, reinterpret_cast<void*>(signalItemClicked), this);
+ }
+ }
+
+ virtual void set_item_sensitive(const OString& rIdent, bool bSensitive) override
+ {
+ disable_item_notify_events();
+ gtk_widget_set_sensitive(GTK_WIDGET(m_aMap[rIdent]), bSensitive);
+ enable_item_notify_events();
+ }
+
+ virtual bool get_item_sensitive(const OString& rIdent) const override
+ {
+ return gtk_widget_get_sensitive(GTK_WIDGET(m_aMap.find(rIdent)->second));
+ }
+
+ virtual void set_item_visible(const OString& rIdent, bool bVisible) override
+ {
+ disable_item_notify_events();
+ gtk_widget_set_visible(GTK_WIDGET(m_aMap[rIdent]), bVisible);
+ enable_item_notify_events();
+ }
+
+ virtual void set_item_help_id(const OString& rIdent, const OString& rHelpId) override
+ {
+ ::set_help_id(GTK_WIDGET(m_aMap[rIdent]), rHelpId);
+ }
+
+ virtual bool get_item_visible(const OString& rIdent) const override
+ {
+ return gtk_widget_get_visible(GTK_WIDGET(m_aMap.find(rIdent)->second));
+ }
+
+ virtual void set_item_active(const OString& rIdent, bool bActive) override
+ {
+ disable_item_notify_events();
+
+ GtkWidget* pToolButton = m_aMap.find(rIdent)->second;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_TOGGLE_TOOL_BUTTON(pToolButton))
+ gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(pToolButton), bActive);
+ else
+ {
+ GtkButton* pButton = nullptr;
+ // there is no GtkMenuToggleToolButton so abuse the CHECKED state of the GtkMenuToolButton button
+ // to emulate one
+ find_menupeer_button(GTK_WIDGET(pToolButton), &pButton);
+ if (pButton)
+ {
+ auto eState = gtk_widget_get_state_flags(GTK_WIDGET(pButton)) & ~GTK_STATE_FLAG_CHECKED;
+ if (bActive)
+ eState |= GTK_STATE_FLAG_CHECKED;
+ gtk_widget_set_state_flags(GTK_WIDGET(pButton), static_cast<GtkStateFlags>(eState), true);
+ }
+ }
+#else
+ GtkWidget* pWidget;
+ if (GTK_IS_MENU_BUTTON(pToolButton))
+ {
+ pWidget = gtk_widget_get_first_child(pToolButton);
+ assert(GTK_IS_TOGGLE_BUTTON(pWidget));
+ }
+ else
+ pWidget = pToolButton;
+ auto eState = gtk_widget_get_state_flags(pWidget) & ~GTK_STATE_FLAG_CHECKED;
+ if (bActive)
+ eState |= GTK_STATE_FLAG_CHECKED;
+ gtk_widget_set_state_flags(pWidget, static_cast<GtkStateFlags>(eState), true);
+#endif
+
+ enable_item_notify_events();
+ }
+
+ virtual bool get_item_active(const OString& rIdent) const override
+ {
+ GtkWidget* pToolButton = m_aMap.find(rIdent)->second;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_TOGGLE_TOOL_BUTTON(pToolButton))
+ return gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(pToolButton));
+ else
+ {
+ GtkButton* pButton = nullptr;
+ // there is no GtkMenuToggleToolButton so abuse the CHECKED state of the GtkMenuToolButton button
+ // to emulate one
+ find_menupeer_button(GTK_WIDGET(pToolButton), &pButton);
+ if (pButton)
+ {
+ return gtk_widget_get_state_flags(GTK_WIDGET(pButton)) & GTK_STATE_FLAG_CHECKED;
+ }
+ }
+#else
+ GtkWidget* pWidget;
+ if (GTK_IS_MENU_BUTTON(pToolButton))
+ {
+ pWidget = gtk_widget_get_first_child(pToolButton);
+ assert(GTK_IS_TOGGLE_BUTTON(pWidget));
+ }
+ else
+ pWidget = pToolButton;
+ return gtk_widget_get_state_flags(pWidget) & GTK_STATE_FLAG_CHECKED;
+#endif
+
+ return false;
+ }
+
+ virtual void set_menu_item_active(const OString& rIdent, bool bActive) override
+ {
+ disable_item_notify_events();
+
+ auto aFind = m_aMenuButtonMap.find(rIdent);
+ assert (aFind != m_aMenuButtonMap.end());
+ aFind->second->set_active(bActive);
+
+ enable_item_notify_events();
+ }
+
+ virtual bool get_menu_item_active(const OString& rIdent) const override
+ {
+ auto aFind = m_aMenuButtonMap.find(rIdent);
+ assert (aFind != m_aMenuButtonMap.end());
+ return aFind->second->get_active();
+ }
+
+ virtual void insert_item(int pos, const OUString& rId) override
+ {
+ OString sId = OUStringToOString(rId, RTL_TEXTENCODING_UTF8);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkToolItem* pItem = gtk_tool_button_new(nullptr, sId.getStr());
+#else
+ GtkWidget* pItem = gtk_button_new();
+#endif
+ ::set_buildable_id(GTK_BUILDABLE(pItem), sId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_toolbar_insert(m_pToolbar, pItem, pos);
+#else
+ gtk_box_insert_child_after(m_pToolbar, pItem, toolbar_get_nth_item(pos - 1));
+#endif
+ gtk_widget_show(GTK_WIDGET(pItem));
+ add_to_map(GTK_WIDGET(pItem), nullptr);
+ }
+
+ virtual void insert_separator(int pos, const OUString& rId) override
+ {
+ OString sId = OUStringToOString(rId, RTL_TEXTENCODING_UTF8);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkToolItem* pItem = gtk_separator_tool_item_new();
+#else
+ GtkWidget* pItem = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
+#endif
+ ::set_buildable_id(GTK_BUILDABLE(pItem), sId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_toolbar_insert(m_pToolbar, pItem, pos);
+#else
+ gtk_box_insert_child_after(m_pToolbar, pItem, toolbar_get_nth_item(pos - 1));
+#endif
+ gtk_widget_show(GTK_WIDGET(pItem));
+ }
+
+ virtual void set_item_popover(const OString& rIdent, weld::Widget* pPopover) override
+ {
+ m_aMenuButtonMap[rIdent]->set_popover(pPopover);
+ }
+
+ virtual void set_item_menu(const OString& rIdent, weld::Menu* pMenu) override
+ {
+ m_aMenuButtonMap[rIdent]->set_menu(pMenu);
+ }
+
+ virtual int get_n_items() const override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_toolbar_get_n_items(m_pToolbar);
+#else
+ int n_items = 0;
+ for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pToolbar));
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ ++n_items;
+ }
+ return n_items;
+#endif
+ }
+
+ virtual OString get_item_ident(int nIndex) const override
+ {
+ auto* pItem = toolbar_get_nth_item(nIndex);
+ return ::get_buildable_id(GTK_BUILDABLE(pItem));
+ }
+
+ virtual void set_item_ident(int nIndex, const OString& rIdent) override
+ {
+ OString sOldIdent(get_item_ident(nIndex));
+ m_aMap.erase(m_aMap.find(sOldIdent));
+
+ auto* pItem = toolbar_get_nth_item(nIndex);
+ ::set_buildable_id(GTK_BUILDABLE(pItem), rIdent);
+
+ // to keep the ids unique, if the new id is already in use by an item,
+ // change the id of that item to the now unused old ident of this item
+ auto aFind = m_aMap.find(rIdent);
+ if (aFind != m_aMap.end())
+ {
+ GtkWidget* pDupIdItem = aFind->second;
+ ::set_buildable_id(GTK_BUILDABLE(pDupIdItem), sOldIdent);
+ m_aMap[sOldIdent] = pDupIdItem;
+ }
+
+ m_aMap[rIdent] = pItem;
+ }
+
+ virtual void set_item_label(int nIndex, const OUString& rLabel) override
+ {
+ auto* pItem = toolbar_get_nth_item(nIndex);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem), MapToGtkAccelerator(rLabel).getStr());
+#else
+ if (!GTK_IS_BUTTON(pItem))
+ return;
+ ::button_set_label(GTK_BUTTON(pItem), rLabel);
+#endif
+ }
+
+ virtual void set_item_label(const OString& rIdent, const OUString& rLabel) override
+ {
+ GtkWidget* pItem = m_aMap[rIdent];
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem), MapToGtkAccelerator(rLabel).getStr());
+#else
+ if (!pItem || !GTK_IS_BUTTON(pItem))
+ return;
+ ::button_set_label(GTK_BUTTON(pItem), rLabel);
+#endif
+ }
+
+ OUString get_item_label(const OString& rIdent) const override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ const gchar* pText = gtk_tool_button_get_label(GTK_TOOL_BUTTON(m_aMap.find(rIdent)->second));
+#else
+ const gchar* pText = gtk_button_get_label(GTK_BUTTON(m_aMap.find(rIdent)->second));
+#endif
+ return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ virtual void set_item_icon_name(const OString& rIdent, const OUString& rIconName) override
+ {
+ GtkWidget* pItem = m_aMap[rIdent];
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
+ return;
+#else
+ if (!pItem || !GTK_IS_BUTTON(pItem))
+ return;
+#endif
+
+ GtkWidget* pImage = image_new_from_icon_name(rIconName);
+ if (pImage)
+ gtk_widget_show(pImage);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(pItem), pImage);
+#else
+ gtk_button_set_child(GTK_BUTTON(pItem), pImage);
+ // versions of gtk4 > 4.2.1 might do this on their own
+ gtk_widget_remove_css_class(GTK_WIDGET(pItem), "text-button");
+#endif
+ }
+
+ virtual void set_item_image(const OString& rIdent, const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
+ {
+ GtkWidget* pItem = m_aMap[rIdent];
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ set_item_image(GTK_TOOL_BUTTON(pItem), rIcon);
+#else
+ if (!pItem)
+ return;
+ set_item_image(pItem, rIcon);
+#endif
+ }
+
+ virtual void set_item_image(const OString& rIdent, VirtualDevice* pDevice) override
+ {
+ GtkWidget* pItem = m_aMap[rIdent];
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ set_item_image(GTK_TOOL_BUTTON(pItem), pDevice);
+#else
+ if (!pItem)
+ return;
+ set_item_image(pItem, pDevice);
+#endif
+ }
+
+ virtual void set_item_image(int nIndex, const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
+ {
+ auto* pItem = toolbar_get_nth_item(nIndex);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ set_item_image(GTK_TOOL_BUTTON(pItem), rIcon);
+#else
+ set_item_image(pItem, rIcon);
+#endif
+ }
+
+ virtual void set_item_tooltip_text(int nIndex, const OUString& rTip) override
+ {
+ auto* pItem = toolbar_get_nth_item(nIndex);
+ gtk_widget_set_tooltip_text(GTK_WIDGET(pItem), OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ virtual void set_item_tooltip_text(const OString& rIdent, const OUString& rTip) override
+ {
+ GtkWidget* pItem = GTK_WIDGET(m_aMap[rIdent]);
+ gtk_widget_set_tooltip_text(pItem, OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ virtual OUString get_item_tooltip_text(const OString& rIdent) const override
+ {
+ GtkWidget* pItem = GTK_WIDGET(m_aMap.find(rIdent)->second);
+ const gchar* pStr = gtk_widget_get_tooltip_text(pItem);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ virtual vcl::ImageType get_icon_size() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return m_eImageType;
+#else
+ return GtkToVcl(gtk_toolbar_get_icon_size(m_pToolbar));
+#endif
+ }
+
+ virtual void set_icon_size(vcl::ImageType eType) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_eImageType = eType;
+#else
+ gtk_toolbar_set_icon_size(m_pToolbar, VclToGtk(eType));
+#endif
+ }
+
+ virtual sal_uInt16 get_modifier_state() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GdkDisplay* pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pToolbar));
+ GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
+ GdkDevice* pDevice = gdk_seat_get_keyboard(pSeat);
+ guint nState = gdk_device_get_modifier_state(pDevice);
+#else
+ GdkKeymap* pKeymap = gdk_keymap_get_default();
+ guint nState = gdk_keymap_get_modifier_state(pKeymap);
+#endif
+ return GtkSalFrame::GetKeyModCode(nState);
+ }
+
+ virtual int get_drop_index(const Point& rPoint) const override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_toolbar_get_drop_index(m_pToolbar, rPoint.X(), rPoint.Y());
+#else
+ GtkWidget* pToolbar = GTK_WIDGET(m_pToolbar);
+ GtkWidget* pTarget = gtk_widget_pick(pToolbar, rPoint.X(), rPoint.Y(), GTK_PICK_DEFAULT);
+ if (!pTarget || pTarget == pToolbar)
+ return -1;
+ int i = 0;
+ for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pToolbar));
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ if (pChild == pTarget)
+ return i;
+ ++i;
+ }
+ return -1;
+#endif
+ }
+
+ virtual bool has_focus() const override
+ {
+ if (gtk_widget_has_focus(m_pWidget))
+ return true;
+
+ GtkWidget* pTopLevel = widget_get_toplevel(m_pWidget);
+ if (!GTK_IS_WINDOW(pTopLevel))
+ return false;
+ GtkWidget* pFocus = gtk_window_get_focus(GTK_WINDOW(pTopLevel));
+ if (!pFocus)
+ return false;
+ return gtk_widget_is_ancestor(pFocus, m_pWidget);
+ }
+
+ virtual void grab_focus() override
+ {
+ if (has_focus())
+ return;
+ gtk_widget_grab_focus(m_pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ bool bHasFocusChild = gtk_widget_get_focus_child(m_pWidget);
+#else
+ bool bHasFocusChild = gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget));
+#endif
+ if (!bHasFocusChild)
+ {
+ if (auto* pItem = toolbar_get_nth_item(0))
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_focus_child(m_pWidget, GTK_WIDGET(pItem));
+#else
+ gtk_container_set_focus_child(GTK_CONTAINER(m_pWidget), GTK_WIDGET(pItem));
+#endif
+ bHasFocusChild = true;
+ }
+ }
+ if (bHasFocusChild)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_child_focus(gtk_widget_get_focus_child(m_pWidget), GTK_DIR_TAB_FORWARD);
+#else
+ gtk_widget_child_focus(gtk_container_get_focus_child(GTK_CONTAINER(m_pWidget)), GTK_DIR_TAB_FORWARD);
+#endif
+ }
+ }
+
+ virtual ~GtkInstanceToolbar() override
+ {
+ for (auto& a : m_aMap)
+ g_signal_handlers_disconnect_by_data(a.second, this);
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstanceLinkButton : public GtkInstanceWidget, public virtual weld::LinkButton
+{
+private:
+ GtkLinkButton* m_pButton;
+ gulong m_nSignalId;
+
+ static bool signalActivateLink(GtkButton*, gpointer widget)
+ {
+ GtkInstanceLinkButton* pThis = static_cast<GtkInstanceLinkButton*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_activate_link();
+ }
+
+public:
+ GtkInstanceLinkButton(GtkLinkButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
+ , m_pButton(pButton)
+ , m_nSignalId(g_signal_connect(pButton, "activate-link", G_CALLBACK(signalActivateLink), this))
+ {
+ }
+
+ virtual void set_label(const OUString& rText) override
+ {
+ ::button_set_label(GTK_BUTTON(m_pButton), rText);
+ }
+
+ virtual OUString get_label() const override
+ {
+ return ::button_get_label(GTK_BUTTON(m_pButton));
+ }
+
+ virtual void set_uri(const OUString& rText) override
+ {
+ gtk_link_button_set_uri(m_pButton, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ virtual OUString get_uri() const override
+ {
+ const gchar* pStr = gtk_link_button_get_uri(m_pButton);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pButton, m_nSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pButton, m_nSignalId);
+ }
+
+ virtual ~GtkInstanceLinkButton() override
+ {
+ g_signal_handler_disconnect(m_pButton, m_nSignalId);
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstanceCheckButton : public GtkInstanceWidget, public virtual weld::CheckButton
+{
+private:
+ GtkCheckButton* m_pCheckButton;
+ gulong m_nSignalId;
+
+ static void signalToggled(void*, gpointer widget)
+ {
+ GtkInstanceCheckButton* pThis = static_cast<GtkInstanceCheckButton*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_toggled();
+ }
+
+public:
+ GtkInstanceCheckButton(GtkCheckButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
+ , m_pCheckButton(pButton)
+ , m_nSignalId(g_signal_connect(m_pCheckButton, "toggled", G_CALLBACK(signalToggled), this))
+ {
+ }
+
+ virtual void set_active(bool active) override
+ {
+ disable_notify_events();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_check_button_set_inconsistent(m_pCheckButton, false);
+ gtk_check_button_set_active(m_pCheckButton, active);
+#else
+ gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(m_pCheckButton), false);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pCheckButton), active);
+#endif
+ enable_notify_events();
+ }
+
+ virtual bool get_active() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_check_button_get_active(m_pCheckButton);
+#else
+ return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pCheckButton));
+#endif
+ }
+
+ virtual void set_inconsistent(bool inconsistent) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_check_button_set_inconsistent(m_pCheckButton, inconsistent);
+#else
+ gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(m_pCheckButton), inconsistent);
+#endif
+ }
+
+ virtual bool get_inconsistent() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_check_button_get_inconsistent(m_pCheckButton);
+#else
+ return gtk_toggle_button_get_inconsistent(GTK_TOGGLE_BUTTON(m_pCheckButton));
+#endif
+ }
+
+ virtual void set_label(const OUString& rText) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_check_button_set_label(m_pCheckButton, MapToGtkAccelerator(rText).getStr());
+#else
+ ::button_set_label(GTK_BUTTON(m_pCheckButton), rText);
+#endif
+ }
+
+ virtual OUString get_label() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ const gchar* pStr = gtk_check_button_get_label(m_pCheckButton);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+#else
+ return ::button_get_label(GTK_BUTTON(m_pCheckButton));
+#endif
+ }
+
+ virtual void set_label_wrap(bool bWrap) override
+ {
+ GtkLabel* pChild = ::get_label_widget(GTK_WIDGET(m_pCheckButton));
+ ::set_label_wrap(pChild, bWrap);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pCheckButton, m_nSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pCheckButton, m_nSignalId);
+ }
+
+ virtual ~GtkInstanceCheckButton() override
+ {
+ g_signal_handler_disconnect(m_pCheckButton, m_nSignalId);
+ }
+};
+
+class GtkInstanceRadioButton : public GtkInstanceCheckButton, public virtual weld::RadioButton
+{
+public:
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkInstanceRadioButton(GtkCheckButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceCheckButton(pButton, pBuilder, bTakeOwnership)
+#else
+ GtkInstanceRadioButton(GtkRadioButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceCheckButton(GTK_CHECK_BUTTON(pButton), pBuilder, bTakeOwnership)
+#endif
+ {
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstanceScale : public GtkInstanceWidget, public virtual weld::Scale
+{
+private:
+ GtkScale* m_pScale;
+ gulong m_nValueChangedSignalId;
+
+ static void signalValueChanged(GtkScale*, gpointer widget)
+ {
+ GtkInstanceScale* pThis = static_cast<GtkInstanceScale*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_value_changed();
+ }
+
+public:
+ GtkInstanceScale(GtkScale* pScale, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pScale), pBuilder, bTakeOwnership)
+ , m_pScale(pScale)
+ , m_nValueChangedSignalId(g_signal_connect(m_pScale, "value-changed", G_CALLBACK(signalValueChanged), this))
+ {
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pScale, m_nValueChangedSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pScale, m_nValueChangedSignalId);
+ }
+
+ virtual void set_value(int value) override
+ {
+ disable_notify_events();
+ gtk_range_set_value(GTK_RANGE(m_pScale), value);
+ enable_notify_events();
+ }
+
+ virtual void set_range(int min, int max) override
+ {
+ disable_notify_events();
+ gtk_range_set_range(GTK_RANGE(m_pScale), min, max);
+ enable_notify_events();
+ }
+
+ virtual void set_increments(int step, int page) override
+ {
+ disable_notify_events();
+ gtk_range_set_increments(GTK_RANGE(m_pScale), step, page);
+ enable_notify_events();
+ }
+
+ virtual void get_increments(int& step, int& page) const override
+ {
+ GtkAdjustment* pAdjustment = gtk_range_get_adjustment(GTK_RANGE(m_pScale));
+ step = gtk_adjustment_get_step_increment(pAdjustment);
+ page = gtk_adjustment_get_page_increment(pAdjustment);
+ }
+
+ virtual int get_value() const override
+ {
+ return gtk_range_get_value(GTK_RANGE(m_pScale));
+ }
+
+ virtual ~GtkInstanceScale() override
+ {
+ g_signal_handler_disconnect(m_pScale, m_nValueChangedSignalId);
+ }
+};
+
+class GtkInstanceProgressBar : public GtkInstanceWidget, public virtual weld::ProgressBar
+{
+private:
+ GtkProgressBar* m_pProgressBar;
+
+public:
+ GtkInstanceProgressBar(GtkProgressBar* pProgressBar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pProgressBar), pBuilder, bTakeOwnership)
+ , m_pProgressBar(pProgressBar)
+ {
+ }
+
+ virtual void set_percentage(int value) override
+ {
+ gtk_progress_bar_set_fraction(m_pProgressBar, value / 100.0);
+ }
+
+ virtual OUString get_text() const override
+ {
+ const gchar* pText = gtk_progress_bar_get_text(m_pProgressBar);
+ OUString sRet(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ return sRet;
+ }
+
+ virtual void set_text(const OUString& rText) override
+ {
+ gtk_progress_bar_set_text(m_pProgressBar, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+ }
+};
+
+class GtkInstanceSpinner : public GtkInstanceWidget, public virtual weld::Spinner
+{
+private:
+ GtkSpinner* m_pSpinner;
+
+public:
+ GtkInstanceSpinner(GtkSpinner* pSpinner, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pSpinner), pBuilder, bTakeOwnership)
+ , m_pSpinner(pSpinner)
+ {
+ }
+
+ virtual void start() override
+ {
+ gtk_spinner_start(m_pSpinner);
+ }
+
+ virtual void stop() override
+ {
+ gtk_spinner_stop(m_pSpinner);
+ }
+};
+
+class GtkInstanceImage : public GtkInstanceWidget, public virtual weld::Image
+{
+private:
+ GtkImage* m_pImage;
+
+public:
+ GtkInstanceImage(GtkImage* pImage, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pImage), pBuilder, bTakeOwnership)
+ , m_pImage(pImage)
+ {
+ }
+
+ virtual void set_from_icon_name(const OUString& rIconName) override
+ {
+ image_set_from_icon_name(m_pImage, rIconName);
+ }
+
+ virtual void set_image(VirtualDevice* pDevice) override
+ {
+ image_set_from_virtual_device(m_pImage, pDevice);
+ }
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
+ {
+ image_set_from_xgraphic(m_pImage, rImage);
+ }
+};
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+class GtkInstancePicture: public GtkInstanceWidget, public virtual weld::Image
+{
+private:
+ GtkPicture* m_pPicture;
+
+public:
+ GtkInstancePicture(GtkPicture* pPicture, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pPicture), pBuilder, bTakeOwnership)
+ , m_pPicture(pPicture)
+ {
+ gtk_picture_set_can_shrink(m_pPicture, true);
+ }
+
+ virtual void set_from_icon_name(const OUString& rIconName) override
+ {
+ picture_set_from_icon_name(m_pPicture, rIconName);
+ }
+
+ virtual void set_image(VirtualDevice* pDevice) override
+ {
+ picture_set_from_virtual_device(m_pPicture, pDevice);
+ }
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rPicture) override
+ {
+ picture_set_from_xgraphic(m_pPicture, rPicture);
+ }
+};
+#endif
+
+class GtkInstanceCalendar : public GtkInstanceWidget, public virtual weld::Calendar
+{
+private:
+ GtkCalendar* m_pCalendar;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkEventController* m_pKeyController;
+#endif
+ gulong m_nDaySelectedSignalId;
+ gulong m_nDaySelectedDoubleClickSignalId;
+ gulong m_nKeyPressEventSignalId;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nButtonPressEventSignalId;
+#endif
+
+ static void signalDaySelected(GtkCalendar*, gpointer widget)
+ {
+ GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_selected();
+ }
+
+ static void signalDaySelectedDoubleClick(GtkCalendar*, gpointer widget)
+ {
+ GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_activated();
+ }
+
+ bool signal_key_press(guint nKeyVal)
+ {
+ if (nKeyVal == GDK_KEY_Return || nKeyVal == GDK_KEY_KP_Enter)
+ {
+ SolarMutexGuard aGuard;
+ signal_activated();
+ return true;
+ }
+ return false;
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalKeyPress(GtkEventControllerKey*, guint nKeyVal, guint /*nKeyCode*/, GdkModifierType, gpointer widget)
+ {
+ GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
+ return pThis->signal_key_press(nKeyVal);
+ }
+#else
+ static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
+ return pThis->signal_key_press(pEvent->keyval);
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalButton(GtkWidget*, GdkEventButton*, gpointer)
+ {
+ // don't let button press get to parent window, for the case of the
+ // ImplCFieldFloatWin floating window belonging to CalendarField where
+ // the click on the calendar continues to the parent GtkWindow and
+ // closePopup is called by GtkSalFrame::signalButton because the click
+ // window isn't that of the floating parent GtkWindow
+ return true;
+ }
+#endif
+
+public:
+ GtkInstanceCalendar(GtkCalendar* pCalendar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pCalendar), pBuilder, bTakeOwnership)
+ , m_pCalendar(pCalendar)
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_pKeyController(gtk_event_controller_key_new())
+#endif
+ , m_nDaySelectedSignalId(g_signal_connect(pCalendar, "day-selected", G_CALLBACK(signalDaySelected), this))
+ , m_nDaySelectedDoubleClickSignalId(g_signal_connect(pCalendar, "day-selected-double-click", G_CALLBACK(signalDaySelectedDoubleClick), this))
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_nKeyPressEventSignalId(g_signal_connect(m_pKeyController, "key-pressed", G_CALLBACK(signalKeyPress), this))
+#else
+ , m_nKeyPressEventSignalId(g_signal_connect(pCalendar, "key-press-event", G_CALLBACK(signalKeyPress), this))
+ , m_nButtonPressEventSignalId(g_signal_connect_after(pCalendar, "button-press-event", G_CALLBACK(signalButton), this))
+#endif
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_add_controller(GTK_WIDGET(m_pCalendar), m_pKeyController);
+#endif
+ }
+
+ virtual void set_date(const Date& rDate) override
+ {
+ if (!rDate.IsValidAndGregorian())
+ return;
+
+ disable_notify_events();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GDateTime* pDateTime = g_date_time_new_local(rDate.GetYear(), rDate.GetMonth(), rDate.GetDay(), 0, 0, 0);
+ gtk_calendar_select_day(m_pCalendar, pDateTime);
+ g_date_time_unref(pDateTime);
+#else
+ gtk_calendar_select_month(m_pCalendar, rDate.GetMonth() - 1, rDate.GetYear());
+ gtk_calendar_select_day(m_pCalendar, rDate.GetDay());
+#endif
+ enable_notify_events();
+ }
+
+ virtual Date get_date() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GDateTime* pDateTime = gtk_calendar_get_date(m_pCalendar);
+ Date aDate(g_date_time_get_day_of_month(pDateTime),
+ g_date_time_get_month(pDateTime),
+ g_date_time_get_year(pDateTime));
+ g_date_time_unref(pDateTime);
+ return aDate;
+#else
+ guint year, month, day;
+ gtk_calendar_get_date(m_pCalendar, &year, &month, &day);
+ return Date(day, month + 1, year);
+#endif
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
+ g_signal_handler_block(m_pCalendar, m_nDaySelectedSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pCalendar, m_nDaySelectedSignalId);
+ g_signal_handler_unblock(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
+ }
+
+ virtual ~GtkInstanceCalendar() override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pKeyController, m_nKeyPressEventSignalId);
+#else
+ g_signal_handler_disconnect(m_pCalendar, m_nButtonPressEventSignalId);
+ g_signal_handler_disconnect(m_pCalendar, m_nKeyPressEventSignalId);
+#endif
+ g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
+ g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedSignalId);
+ }
+};
+
+}
+
+namespace
+{
+ // CSS nodes: entry[.flat][.warning][.error]
+ void set_widget_css_message_type(GtkWidget* pWidget, weld::EntryMessageType eType)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_remove_css_class(pWidget, "error");
+ gtk_widget_remove_css_class(pWidget, "warning");
+#else
+ GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(pWidget);
+ gtk_style_context_remove_class(pWidgetContext, "error");
+ gtk_style_context_remove_class(pWidgetContext, "warning");
+#endif
+
+ switch (eType)
+ {
+ case weld::EntryMessageType::Normal:
+ break;
+ case weld::EntryMessageType::Warning:
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_add_css_class(pWidget, "warning");
+#else
+ gtk_style_context_add_class(pWidgetContext, "warning");
+#endif
+ break;
+ case weld::EntryMessageType::Error:
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_add_css_class(pWidget, "error");
+#else
+ gtk_style_context_add_class(pWidgetContext, "error");
+#endif
+ break;
+ }
+ }
+
+ void set_entry_message_type(GtkEntry* pEntry, weld::EntryMessageType eType)
+ {
+ set_widget_css_message_type(GTK_WIDGET(pEntry), eType);
+ switch (eType)
+ {
+ case weld::EntryMessageType::Normal:
+ gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, nullptr);
+ break;
+ case weld::EntryMessageType::Warning:
+ gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning");
+ break;
+ case weld::EntryMessageType::Error:
+ gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-error");
+ break;
+ }
+ }
+}
+
+namespace
+{
+
+class GtkInstanceEditable : public GtkInstanceWidget, public virtual weld::Entry
+{
+protected:
+ GtkEditable* m_pEditable;
+ GtkWidget* m_pDelegate;
+ WidgetFont m_aCustomFont;
+private:
+ gulong m_nChangedSignalId;
+ gulong m_nInsertTextSignalId;
+ gulong m_nCursorPosSignalId;
+ gulong m_nSelectionPosSignalId;
+ gulong m_nActivateSignalId;
+
+ static void signalChanged(GtkEditable*, gpointer widget)
+ {
+ GtkInstanceEditable* pThis = static_cast<GtkInstanceEditable*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_changed();
+ }
+
+ static void signalInsertText(GtkEditable* pEditable, const gchar* pNewText, gint nNewTextLength,
+ gint* position, gpointer widget)
+ {
+ GtkInstanceEditable* pThis = static_cast<GtkInstanceEditable*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_insert_text(pEditable, pNewText, nNewTextLength, position);
+ }
+
+ void signal_insert_text(GtkEditable* pEditable, const gchar* pNewText, gint nNewTextLength, gint* position)
+ {
+ if (!m_aInsertTextHdl.IsSet())
+ return;
+ OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
+ const bool bContinue = m_aInsertTextHdl.Call(sText);
+ if (bContinue && !sText.isEmpty())
+ {
+ OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
+ g_signal_handlers_block_by_func(pEditable, reinterpret_cast<gpointer>(signalInsertText), this);
+ gtk_editable_insert_text(pEditable, sFinalText.getStr(), sFinalText.getLength(), position);
+ g_signal_handlers_unblock_by_func(pEditable, reinterpret_cast<gpointer>(signalInsertText), this);
+ }
+ g_signal_stop_emission_by_name(pEditable, "insert-text");
+ }
+
+ static void signalCursorPosition(void*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceEditable* pThis = static_cast<GtkInstanceEditable*>(widget);
+ pThis->signal_cursor_position();
+ }
+
+ static void signalActivate(void*, gpointer widget)
+ {
+ GtkInstanceEditable* pThis = static_cast<GtkInstanceEditable*>(widget);
+ pThis->signal_activate();
+ }
+
+ virtual void ensureMouseEventWidget() override
+ {
+ // The GtkEntry is sufficient to get mouse events without an intermediate GtkEventBox
+ if (!m_pMouseEventBox)
+ m_pMouseEventBox = m_pDelegate;
+ }
+
+protected:
+
+ virtual void signal_activate()
+ {
+ if (m_aActivateHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ if (m_aActivateHdl.Call(*this))
+ g_signal_stop_emission_by_name(m_pDelegate, "activate");
+ }
+ }
+
+ PangoAttrList* get_attributes()
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_text_get_attributes(GTK_TEXT(m_pDelegate));
+#else
+ return gtk_entry_get_attributes(GTK_ENTRY(m_pDelegate));
+#endif
+ }
+
+ void set_attributes(PangoAttrList* pAttrs)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_text_set_attributes(GTK_TEXT(m_pDelegate), pAttrs);
+#else
+ gtk_entry_set_attributes(GTK_ENTRY(m_pDelegate), pAttrs);
+#endif
+ }
+
+public:
+ GtkInstanceEditable(GtkWidget* pWidget, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(pWidget, pBuilder, bTakeOwnership)
+ , m_pEditable(GTK_EDITABLE(pWidget))
+#if GTK_CHECK_VERSION(4, 0, 0)
+ , m_pDelegate(GTK_WIDGET(gtk_editable_get_delegate(m_pEditable)))
+#else
+ , m_pDelegate(pWidget)
+#endif
+ , m_aCustomFont(m_pWidget)
+ , m_nChangedSignalId(g_signal_connect(m_pEditable, "changed", G_CALLBACK(signalChanged), this))
+ , m_nInsertTextSignalId(g_signal_connect(m_pEditable, "insert-text", G_CALLBACK(signalInsertText), this))
+ , m_nCursorPosSignalId(g_signal_connect(m_pEditable, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
+ , m_nSelectionPosSignalId(g_signal_connect(m_pEditable, "notify::selection-bound", G_CALLBACK(signalCursorPosition), this))
+ , m_nActivateSignalId(g_signal_connect(m_pDelegate, "activate", G_CALLBACK(signalActivate), this))
+ {
+ }
+
+ virtual void set_text(const OUString& rText) override
+ {
+ disable_notify_events();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+#else
+ gtk_entry_set_text(GTK_ENTRY(m_pDelegate), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+#endif
+ enable_notify_events();
+ }
+
+ virtual OUString get_text() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ const gchar* pText = gtk_editable_get_text(m_pEditable);
+#else
+ const gchar* pText = gtk_entry_get_text(GTK_ENTRY(m_pDelegate));
+#endif
+ OUString sRet(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ return sRet;
+ }
+
+ virtual void set_width_chars(int nChars) override
+ {
+ disable_notify_events();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_editable_set_width_chars(m_pEditable, nChars);
+ gtk_editable_set_max_width_chars(m_pEditable, nChars);
+#else
+ gtk_entry_set_width_chars(GTK_ENTRY(m_pDelegate), nChars);
+ gtk_entry_set_max_width_chars(GTK_ENTRY(m_pDelegate), nChars);
+#endif
+ enable_notify_events();
+ }
+
+ virtual int get_width_chars() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_editable_get_width_chars(m_pEditable);
+#else
+ return gtk_entry_get_width_chars(GTK_ENTRY(m_pDelegate));
+#endif
+ }
+
+ virtual void set_max_length(int nChars) override
+ {
+ disable_notify_events();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_text_set_max_length(GTK_TEXT(m_pDelegate), nChars);
+#else
+ gtk_entry_set_max_length(GTK_ENTRY(m_pDelegate), nChars);
+#endif
+ enable_notify_events();
+ }
+
+ virtual void select_region(int nStartPos, int nEndPos) override
+ {
+ disable_notify_events();
+ gtk_editable_select_region(m_pEditable, nStartPos, nEndPos);
+ enable_notify_events();
+ }
+
+ bool get_selection_bounds(int& rStartPos, int& rEndPos) override
+ {
+ return gtk_editable_get_selection_bounds(m_pEditable, &rStartPos, &rEndPos);
+ }
+
+ virtual void replace_selection(const OUString& rText) override
+ {
+ disable_notify_events();
+ gtk_editable_delete_selection(m_pEditable);
+ OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gint position = gtk_editable_get_position(m_pEditable);
+ gtk_editable_insert_text(m_pEditable, sText.getStr(), sText.getLength(),
+ &position);
+ enable_notify_events();
+ }
+
+ virtual void set_position(int nCursorPos) override
+ {
+ disable_notify_events();
+ gtk_editable_set_position(m_pEditable, nCursorPos);
+ enable_notify_events();
+ }
+
+ virtual int get_position() const override
+ {
+ return gtk_editable_get_position(m_pEditable);
+ }
+
+ virtual void set_editable(bool bEditable) override
+ {
+ gtk_editable_set_editable(m_pEditable, bEditable);
+ }
+
+ virtual bool get_editable() const override
+ {
+ return gtk_editable_get_editable(m_pEditable);
+ }
+
+ virtual void set_overwrite_mode(bool bOn) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_text_set_overwrite_mode(GTK_TEXT(m_pDelegate), bOn);
+#else
+ gtk_entry_set_overwrite_mode(GTK_ENTRY(m_pDelegate), bOn);
+#endif
+ }
+
+ virtual bool get_overwrite_mode() const override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_text_get_overwrite_mode(GTK_TEXT(m_pDelegate));
+#else
+ return gtk_entry_get_overwrite_mode(GTK_ENTRY(m_pDelegate));
+#endif
+ }
+
+ virtual void set_message_type(weld::EntryMessageType eType) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (!GTK_IS_ENTRY(m_pDelegate))
+ {
+ ::set_widget_css_message_type(m_pDelegate, eType);
+ return;
+ }
+#endif
+ ::set_entry_message_type(GTK_ENTRY(m_pDelegate), eType);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pDelegate, m_nActivateSignalId);
+ g_signal_handler_block(m_pEditable, m_nSelectionPosSignalId);
+ g_signal_handler_block(m_pEditable, m_nCursorPosSignalId);
+ g_signal_handler_block(m_pEditable, m_nInsertTextSignalId);
+ g_signal_handler_block(m_pEditable, m_nChangedSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pEditable, m_nChangedSignalId);
+ g_signal_handler_unblock(m_pEditable, m_nInsertTextSignalId);
+ g_signal_handler_unblock(m_pEditable, m_nCursorPosSignalId);
+ g_signal_handler_unblock(m_pEditable, m_nSelectionPosSignalId);
+ g_signal_handler_unblock(m_pDelegate, m_nActivateSignalId);
+ }
+
+ virtual vcl::Font get_font() override
+ {
+ if (const vcl::Font* pFont = m_aCustomFont.get_custom_font())
+ return *pFont;
+ return GtkInstanceWidget::get_font();
+ }
+
+ void set_font_color(const Color& rColor) override
+ {
+ PangoAttrList* pOrigList = get_attributes();
+ if (rColor == COL_AUTO && !pOrigList) // nothing to do
+ return;
+
+ PangoAttrType aFilterAttrs[] = {PANGO_ATTR_FOREGROUND, PANGO_ATTR_INVALID};
+
+ PangoAttrList* pAttrs = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
+ PangoAttrList* pRemovedAttrs = pOrigList ? pango_attr_list_filter(pAttrs, filter_pango_attrs, &aFilterAttrs) : nullptr;
+
+ if (rColor != COL_AUTO)
+ pango_attr_list_insert(pAttrs, pango_attr_foreground_new(rColor.GetRed()/255.0, rColor.GetGreen()/255.0, rColor.GetBlue()/255.0));
+
+ set_attributes(pAttrs);
+ pango_attr_list_unref(pAttrs);
+ pango_attr_list_unref(pRemovedAttrs);
+ }
+
+ void fire_signal_changed()
+ {
+ signal_changed();
+ }
+
+ virtual void cut_clipboard() override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_activate_action(m_pDelegate, "cut.clipboard", nullptr);
+#else
+ gtk_editable_cut_clipboard(m_pEditable);
+#endif
+ }
+
+ virtual void copy_clipboard() override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_activate_action(m_pDelegate, "copy.clipboard", nullptr);
+#else
+ gtk_editable_copy_clipboard(m_pEditable);
+#endif
+ }
+
+ virtual void paste_clipboard() override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_activate_action(m_pDelegate, "paste.clipboard", nullptr);
+#else
+ gtk_editable_paste_clipboard(m_pEditable);
+#endif
+ }
+
+ virtual void set_placeholder_text(const OUString& rText) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_text_set_placeholder_text(GTK_TEXT(m_pDelegate), rText.toUtf8().getStr());
+#else
+ gtk_entry_set_placeholder_text(GTK_ENTRY(m_pDelegate), rText.toUtf8().getStr());
+#endif
+ }
+
+ virtual void grab_focus() override
+ {
+ if (has_focus())
+ return;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_text_grab_focus_without_selecting(GTK_TEXT(m_pDelegate));
+#else
+ gtk_entry_grab_focus_without_selecting(GTK_ENTRY(m_pDelegate));
+#endif
+ }
+
+ virtual void set_alignment(TxtAlign eXAlign) override
+ {
+ gfloat xalign = 0;
+ switch (eXAlign)
+ {
+ case TxtAlign::Left:
+ xalign = 0.0;
+ break;
+ case TxtAlign::Center:
+ xalign = 0.5;
+ break;
+ case TxtAlign::Right:
+ xalign = 1.0;
+ break;
+ }
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_editable_set_alignment(m_pEditable, xalign);
+#else
+ gtk_entry_set_alignment(GTK_ENTRY(m_pDelegate), xalign);
+#endif
+ }
+
+ virtual ~GtkInstanceEditable() override
+ {
+ g_signal_handler_disconnect(m_pDelegate, m_nActivateSignalId);
+ g_signal_handler_disconnect(m_pEditable, m_nSelectionPosSignalId);
+ g_signal_handler_disconnect(m_pEditable, m_nCursorPosSignalId);
+ g_signal_handler_disconnect(m_pEditable, m_nInsertTextSignalId);
+ g_signal_handler_disconnect(m_pEditable, m_nChangedSignalId);
+ }
+};
+
+class GtkInstanceEntry : public GtkInstanceEditable
+{
+private:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkEntry* m_pEntry;
+ GtkOverlay* m_pPlaceHolderReplacement;
+ GtkLabel* m_pPlaceHolderLabel;
+ gulong m_nEntryFocusInSignalId;
+ gulong m_nEntryFocusOutSignalId;
+ gulong m_nEntryTextLengthSignalId;
+ gulong m_nEntryScrollOffsetSignalId;
+ guint m_nUpdatePlaceholderReplacementIdle;
+
+ static gboolean do_update_placeholder_replacement(gpointer widget)
+ {
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ pThis->update_placeholder_replacement();
+ return false;
+ }
+
+ void update_placeholder_replacement()
+ {
+ m_nUpdatePlaceholderReplacementIdle = 0;
+
+ const char* placeholder_text = gtk_entry_get_placeholder_text(m_pEntry);
+ const bool bShow = placeholder_text && !gtk_entry_get_text_length(m_pEntry) &&
+ gtk_widget_has_focus(GTK_WIDGET(m_pEntry));
+ if (bShow)
+ {
+ GdkRectangle text_area;
+ gtk_entry_get_text_area(m_pEntry, &text_area);
+ gint x;
+ gtk_entry_get_layout_offsets(m_pEntry, &x, nullptr);
+ gtk_widget_set_margin_start(GTK_WIDGET(m_pPlaceHolderLabel), x);
+ gtk_widget_set_margin_end(GTK_WIDGET(m_pPlaceHolderLabel), x);
+ gtk_label_set_text(m_pPlaceHolderLabel, placeholder_text);
+ gtk_widget_show(GTK_WIDGET(m_pPlaceHolderLabel));
+ }
+ else
+ gtk_widget_hide(GTK_WIDGET(m_pPlaceHolderLabel));
+ }
+
+ void launch_update_placeholder_replacement()
+ {
+ // do it in the next event cycle so the GtkEntry has done its layout
+ // and gtk_entry_get_layout_offsets returns the right results
+ if (m_nUpdatePlaceholderReplacementIdle)
+ return;
+ // G_PRIORITY_LOW so gtk's idles are run before this
+ m_nUpdatePlaceholderReplacementIdle = g_idle_add_full(G_PRIORITY_LOW, do_update_placeholder_replacement, this, nullptr);
+ }
+
+ static gboolean signalEntryFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ pThis->launch_update_placeholder_replacement();
+ return false;
+ }
+
+ static gboolean signalEntryFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ pThis->launch_update_placeholder_replacement();
+ return false;
+ }
+
+ static void signalEntryTextLength(void*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ pThis->launch_update_placeholder_replacement();
+ }
+
+ static void signalEntryScrollOffset(void*, GParamSpec*, gpointer widget)
+ {
+ // this property affects the x-position of the text area
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ pThis->launch_update_placeholder_replacement();
+ }
+
+#endif
+
+public:
+ GtkInstanceEntry(GtkEntry* pEntry, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceEditable(GTK_WIDGET(pEntry), pBuilder, bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_pEntry(pEntry)
+ , m_pPlaceHolderReplacement(nullptr)
+ , m_pPlaceHolderLabel(nullptr)
+ , m_nEntryFocusInSignalId(0)
+ , m_nEntryFocusOutSignalId(0)
+ , m_nEntryTextLengthSignalId(0)
+ , m_nEntryScrollOffsetSignalId(0)
+ , m_nUpdatePlaceholderReplacementIdle(0)
+#endif
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // tdf#150810 fake getting placeholders visible even when GtkEntry has focus in gtk3.
+ // In gtk4 this works out of the box, for gtk3 fake it by having a GtkLabel in an
+ // overlay and show that label if the placeholder would be shown if there was
+ // no focus
+ const char* pPlaceHolderText = gtk_entry_get_placeholder_text(m_pEntry);
+ if (pPlaceHolderText ? strlen(pPlaceHolderText) : 0)
+ {
+ m_pPlaceHolderReplacement = GTK_OVERLAY(gtk_overlay_new());
+ m_pPlaceHolderLabel = GTK_LABEL(gtk_label_new(nullptr));
+
+ GtkStyleContext *pStyleContext = gtk_widget_get_style_context(GTK_WIDGET(m_pEntry));
+ GdkRGBA fg = { 0.5, 0.5, 0.5, 0.0 };
+ gtk_style_context_lookup_color(pStyleContext, "placeholder_text_color", &fg);
+
+ auto red = std::clamp(fg.red * 65535 + 0.5, 0.0, 65535.0);
+ auto green = std::clamp(fg.green * 65535 + 0.5, 0.0, 65535.0);
+ auto blue = std::clamp(fg.blue * 65535 + 0.5, 0.0, 65535.0);
+
+ PangoAttribute *pAttr = pango_attr_foreground_new(red, green, blue);
+ pAttr->start_index = 0;
+ pAttr->end_index = G_MAXINT;
+ PangoAttrList* pAttrList = pango_attr_list_new();
+ pango_attr_list_insert(pAttrList, pAttr);
+ gtk_label_set_attributes(m_pPlaceHolderLabel, pAttrList);
+ pango_attr_list_unref(pAttrList);
+
+ // The GtkEntry will have the placeholder as the text to analyze here, assumes there is no initial text, just placeholder
+ const bool bRTL = PANGO_DIRECTION_RTL == pango_context_get_base_dir(pango_layout_get_context(gtk_entry_get_layout(m_pEntry)));
+ SAL_WARN_IF(gtk_entry_get_text_length(m_pEntry), "vcl.gtk", "don't have a placeholder set, but also initial text");
+ gtk_label_set_xalign(m_pPlaceHolderLabel, bRTL ? 1.0 : 0.0);
+
+ gtk_overlay_add_overlay(m_pPlaceHolderReplacement, GTK_WIDGET(m_pPlaceHolderLabel));
+ insertAsParent(GTK_WIDGET(m_pEntry), GTK_WIDGET(m_pPlaceHolderReplacement));
+ m_nEntryFocusInSignalId = g_signal_connect_after(m_pEntry, "focus-in-event", G_CALLBACK(signalEntryFocusIn), this);
+ m_nEntryFocusOutSignalId = g_signal_connect_after(m_pEntry, "focus-out-event", G_CALLBACK(signalEntryFocusOut), this);
+ m_nEntryTextLengthSignalId = g_signal_connect(m_pEntry, "notify::text-length", G_CALLBACK(signalEntryTextLength), this);
+ m_nEntryScrollOffsetSignalId = g_signal_connect(m_pEntry, "notify::scroll-offset", G_CALLBACK(signalEntryScrollOffset), this);
+ }
+#endif
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_aCustomFont.use_custom_font(&rFont, u"entry");
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+
+ virtual void show() override
+ {
+ GtkInstanceEditable::show();
+ if (m_pPlaceHolderReplacement)
+ gtk_widget_show(GTK_WIDGET(m_pPlaceHolderReplacement));
+ }
+
+ virtual void hide() override
+ {
+ if (m_pPlaceHolderReplacement)
+ gtk_widget_hide(GTK_WIDGET(m_pPlaceHolderReplacement));
+ GtkInstanceEditable::hide();
+ }
+
+ virtual ~GtkInstanceEntry() override
+ {
+ if (m_nUpdatePlaceholderReplacementIdle)
+ g_source_remove(m_nUpdatePlaceholderReplacementIdle);
+ if (m_nEntryFocusInSignalId)
+ g_signal_handler_disconnect(m_pEntry, m_nEntryFocusInSignalId);
+ if (m_nEntryFocusOutSignalId)
+ g_signal_handler_disconnect(m_pEntry, m_nEntryFocusOutSignalId);
+ if (m_nEntryTextLengthSignalId)
+ g_signal_handler_disconnect(m_pEntry, m_nEntryTextLengthSignalId);
+ if (m_nEntryScrollOffsetSignalId)
+ g_signal_handler_disconnect(m_pEntry, m_nEntryScrollOffsetSignalId);
+ }
+#endif
+};
+
+}
+
+namespace
+{
+
+ struct Search
+ {
+ OString str;
+ int index;
+ int col;
+ Search(std::u16string_view rText, int nCol)
+ : str(OUStringToOString(rText, RTL_TEXTENCODING_UTF8))
+ , index(-1)
+ , col(nCol)
+ {
+ }
+ };
+
+ gboolean foreach_find(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, gpointer data)
+ {
+ Search* search = static_cast<Search*>(data);
+ gchar *pStr = nullptr;
+ gtk_tree_model_get(model, iter, search->col, &pStr, -1);
+ bool found = strcmp(pStr, search->str.getStr()) == 0;
+ if (found)
+ {
+ gint depth;
+ gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
+ search->index = indices[depth-1];
+ }
+ g_free(pStr);
+ return found;
+ }
+
+ void insert_row(GtkListStore* pListStore, GtkTreeIter& iter, int pos, const OUString* pId, std::u16string_view rText, const OUString* pIconName, const VirtualDevice* pDevice)
+ {
+ if (!pIconName && !pDevice)
+ {
+ gtk_list_store_insert_with_values(pListStore, &iter, pos,
+ 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
+ 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
+ -1);
+ }
+ else
+ {
+ if (pIconName)
+ {
+ GdkPixbuf* pixbuf = getPixbuf(*pIconName);
+
+ gtk_list_store_insert_with_values(pListStore, &iter, pos,
+ 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
+ 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
+ 2, pixbuf,
+ -1);
+
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+ else
+ {
+ cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
+
+ Size aSize(pDevice->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);
+
+ gtk_list_store_insert_with_values(pListStore, &iter, pos,
+ 0, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
+ 1, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
+ 3, target,
+ -1);
+ cairo_surface_destroy(target);
+ }
+ }
+ }
+}
+
+namespace
+{
+ gint default_sort_func(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b, gpointer data)
+ {
+ comphelper::string::NaturalStringSorter* pSorter = static_cast<comphelper::string::NaturalStringSorter*>(data);
+ gchar* pName1;
+ gchar* pName2;
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(pModel);
+ gint sort_column_id(0);
+ gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr);
+ gtk_tree_model_get(pModel, a, sort_column_id, &pName1, -1);
+ gtk_tree_model_get(pModel, b, sort_column_id, &pName2, -1);
+ gint ret = pSorter->compare(OUString(pName1, pName1 ? strlen(pName1) : 0, RTL_TEXTENCODING_UTF8),
+ OUString(pName2, pName2 ? strlen(pName2) : 0, RTL_TEXTENCODING_UTF8));
+ g_free(pName1);
+ g_free(pName2);
+ return ret;
+ }
+
+ int starts_with(GtkTreeModel* pTreeModel, const OUString& rStr, int col, int nStartRow, bool bCaseSensitive)
+ {
+ GtkTreeIter iter;
+ if (!gtk_tree_model_iter_nth_child(pTreeModel, &iter, nullptr, nStartRow))
+ return -1;
+
+ const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
+ int nRet = nStartRow;
+ do
+ {
+ gchar* pStr;
+ gtk_tree_model_get(pTreeModel, &iter, col, &pStr, -1);
+ OUString aStr(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pStr);
+ const bool bMatch = !bCaseSensitive ? rI18nHelper.MatchString(rStr, aStr) : aStr.startsWith(rStr);
+ if (bMatch)
+ return nRet;
+ ++nRet;
+ } while (gtk_tree_model_iter_next(pTreeModel, &iter));
+
+ return -1;
+ }
+
+ struct GtkInstanceTreeIter : public weld::TreeIter
+ {
+ GtkInstanceTreeIter(const GtkInstanceTreeIter* pOrig)
+ {
+ if (pOrig)
+ iter = pOrig->iter;
+ else
+ memset(&iter, 0, sizeof(iter));
+ }
+ GtkInstanceTreeIter(const GtkTreeIter& rOrig)
+ {
+ memcpy(&iter, &rOrig, sizeof(iter));
+ }
+ virtual bool equal(const TreeIter& rOther) const override
+ {
+ return memcmp(&iter, &static_cast<const GtkInstanceTreeIter&>(rOther).iter, sizeof(GtkTreeIter)) == 0;
+ }
+ GtkTreeIter iter;
+ };
+
+ class GtkInstanceTreeView;
+
+}
+
+static GtkInstanceTreeView* g_DragSource;
+
+namespace {
+
+struct CompareGtkTreePath
+{
+ bool operator()(const GtkTreePath* lhs, const GtkTreePath* rhs) const
+ {
+ return gtk_tree_path_compare(lhs, rhs) < 0;
+ }
+};
+
+int get_height_row(GtkTreeView* pTreeView, GList* pColumns)
+{
+ gint nMaxRowHeight = 0;
+ for (GList* pEntry = g_list_first(pColumns); pEntry; pEntry = g_list_next(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ gint nRowHeight;
+ gtk_cell_renderer_get_preferred_height(pCellRenderer, GTK_WIDGET(pTreeView), nullptr, &nRowHeight);
+ nMaxRowHeight = std::max(nMaxRowHeight, nRowHeight);
+ }
+ g_list_free(pRenderers);
+ }
+ return nMaxRowHeight;
+}
+
+int get_height_row_separator(GtkTreeView* pTreeView)
+{
+ // gtk4: _TREE_VIEW_VERTICAL_SEPARATOR define in gtk/gtktreeview.c
+ gint nVerticalSeparator = 2;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_style_get(GTK_WIDGET(pTreeView), "vertical-separator", &nVerticalSeparator, nullptr);
+#else
+ (void)pTreeView;
+#endif
+ return nVerticalSeparator;
+}
+
+int get_height_rows(GtkTreeView* pTreeView, GList* pColumns, int nRows)
+{
+ gint nMaxRowHeight = get_height_row(pTreeView, pColumns);
+ gint nVerticalSeparator = get_height_row_separator(pTreeView);
+ return (nMaxRowHeight * nRows) + (nVerticalSeparator * nRows) / 2;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+int get_height_rows(int nRowHeight, int nSeparatorHeight, int nRows)
+{
+ return (nRowHeight * nRows) + (nSeparatorHeight * (nRows + 1));
+}
+#endif
+
+tools::Rectangle get_row_area(GtkTreeView* pTreeView, GList* pColumns, GtkTreePath* pPath)
+{
+ tools::Rectangle aRet;
+
+ GdkRectangle aRect;
+ for (GList* pEntry = g_list_last(pColumns); pEntry; pEntry = g_list_previous(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ gtk_tree_view_get_cell_area(pTreeView, pPath, pColumn, &aRect);
+ aRet.Union(tools::Rectangle(aRect.x, aRect.y, aRect.x + aRect.width, aRect.y + aRect.height));
+ }
+
+ return aRet;
+}
+
+struct GtkTreeRowReferenceDeleter
+{
+ void operator()(GtkTreeRowReference* p) const
+ {
+ gtk_tree_row_reference_free(p);
+ }
+};
+
+bool separator_function(const GtkTreePath* path, const std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>>& rSeparatorRows)
+{
+ bool bFound = false;
+ for (auto& a : rSeparatorRows)
+ {
+ GtkTreePath* seppath = gtk_tree_row_reference_get_path(a.get());
+ if (seppath)
+ {
+ bFound = gtk_tree_path_compare(path, seppath) == 0;
+ gtk_tree_path_free(seppath);
+ }
+ if (bFound)
+ break;
+ }
+ return bFound;
+}
+
+void tree_store_set(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, ...)
+{
+ va_list args;
+
+ va_start(args, pIter);
+ gtk_tree_store_set_valist(GTK_TREE_STORE(pTreeModel), pIter, args);
+ va_end(args);
+}
+
+void list_store_set(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, ...)
+{
+ va_list args;
+
+ va_start(args, pIter);
+ gtk_list_store_set_valist(GTK_LIST_STORE(pTreeModel), pIter, args);
+ va_end(args);
+}
+
+void tree_store_insert_with_values(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPos,
+ gint nTextCol, const gchar* pText,
+ gint nIdCol, const gchar* pId)
+{
+ gtk_tree_store_insert_with_values(GTK_TREE_STORE(pTreeModel), pIter, pParent, nPos,
+ nTextCol, pText, nIdCol, pId, -1);
+}
+
+void list_store_insert_with_values(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPos,
+ gint nTextCol, const gchar* pText,
+ gint nIdCol, const gchar* pId)
+{
+ assert(!pParent); (void)pParent;
+ gtk_list_store_insert_with_values(GTK_LIST_STORE(pTreeModel), pIter, nPos,
+ nTextCol, pText, nIdCol, pId, -1);
+}
+
+void tree_store_prepend(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent)
+{
+ gtk_tree_store_prepend(GTK_TREE_STORE(pTreeModel), pIter, pParent);
+}
+
+void list_store_prepend(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent)
+{
+ assert(!pParent); (void)pParent;
+ gtk_list_store_prepend(GTK_LIST_STORE(pTreeModel), pIter);
+}
+
+void tree_store_insert(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPosition)
+{
+ gtk_tree_store_insert(GTK_TREE_STORE(pTreeModel), pIter, pParent, nPosition);
+}
+
+void list_store_insert(GtkTreeModel* pTreeModel, GtkTreeIter *pIter, GtkTreeIter *pParent, gint nPosition)
+{
+ assert(!pParent); (void)pParent;
+ gtk_list_store_insert(GTK_LIST_STORE(pTreeModel), pIter, nPosition);
+}
+
+void tree_store_clear(GtkTreeModel* pTreeModel)
+{
+ gtk_tree_store_clear(GTK_TREE_STORE(pTreeModel));
+}
+
+void list_store_clear(GtkTreeModel* pTreeModel)
+{
+ gtk_list_store_clear(GTK_LIST_STORE(pTreeModel));
+}
+
+bool tree_store_remove(GtkTreeModel* pTreeModel, GtkTreeIter *pIter)
+{
+ return gtk_tree_store_remove(GTK_TREE_STORE(pTreeModel), pIter);
+}
+
+bool list_store_remove(GtkTreeModel* pTreeModel, GtkTreeIter *pIter)
+{
+ return gtk_list_store_remove(GTK_LIST_STORE(pTreeModel), pIter);
+}
+
+void tree_store_swap(GtkTreeModel* pTreeModel, GtkTreeIter* pIter1, GtkTreeIter* pIter2)
+{
+ gtk_tree_store_swap(GTK_TREE_STORE(pTreeModel), pIter1, pIter2);
+}
+
+void list_store_swap(GtkTreeModel* pTreeModel, GtkTreeIter* pIter1, GtkTreeIter* pIter2)
+{
+ gtk_list_store_swap(GTK_LIST_STORE(pTreeModel), pIter1, pIter2);
+}
+
+void tree_store_set_value(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gint nColumn, GValue* pValue)
+{
+ gtk_tree_store_set_value(GTK_TREE_STORE(pTreeModel), pIter, nColumn, pValue);
+}
+
+void list_store_set_value(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gint nColumn, GValue* pValue)
+{
+ gtk_list_store_set_value(GTK_LIST_STORE(pTreeModel), pIter, nColumn, pValue);
+}
+
+int promote_arg(bool bArg)
+{
+ return static_cast<int>(bArg);
+}
+
+class GtkInstanceTreeView : public GtkInstanceWidget, public virtual weld::TreeView
+{
+private:
+ GtkTreeView* m_pTreeView;
+ GtkTreeModel* m_pTreeModel;
+
+ typedef void(*setterFnc)(GtkTreeModel*, GtkTreeIter*, ...);
+ setterFnc m_Setter;
+
+ typedef void(*insertWithValuesFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*, gint, gint, const gchar*, gint, const gchar*);
+ insertWithValuesFnc m_InsertWithValues;
+
+ typedef void(*insertFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*, gint);
+ insertFnc m_Insert;
+
+ typedef void(*prependFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*);
+ prependFnc m_Prepend;
+
+ typedef void(*clearFnc)(GtkTreeModel*);
+ clearFnc m_Clear;
+
+ typedef bool(*removeFnc)(GtkTreeModel*, GtkTreeIter*);
+ removeFnc m_Remove;
+
+ typedef void(*swapFnc)(GtkTreeModel*, GtkTreeIter*, GtkTreeIter*);
+ swapFnc m_Swap;
+
+ typedef void(*setValueFnc)(GtkTreeModel*, GtkTreeIter*, gint, GValue*);
+ setValueFnc m_SetValue;
+
+ std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
+ GList *m_pColumns;
+ std::vector<gulong> m_aColumnSignalIds;
+ // map from toggle column to toggle visibility column
+ std::map<int, int> m_aToggleVisMap;
+ // map from toggle column to tristate column
+ std::map<int, int> m_aToggleTriStateMap;
+ // map from text column to text weight column
+ std::map<int, int> m_aWeightMap;
+ // map from text column to sensitive column
+ std::map<int, int> m_aSensitiveMap;
+ // map from text column to indent column
+ std::map<int, int> m_aIndentMap;
+ // map from text column to text align column
+ std::map<int, int> m_aAlignMap;
+ // currently expanding parent that logically, but not currently physically,
+ // contain placeholders
+ o3tl::sorted_vector<GtkTreePath*, CompareGtkTreePath> m_aExpandingPlaceHolderParents;
+ // which rows are separators (rare)
+ std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>> m_aSeparatorRows;
+ std::vector<GtkSortType> m_aSavedSortTypes;
+ std::vector<int> m_aSavedSortColumns;
+ bool m_bWorkAroundBadDragRegion;
+ bool m_bInDrag;
+ bool m_bChangedByMouse;
+ gint m_nTextCol;
+ gint m_nTextView;
+ gint m_nImageCol;
+ gint m_nExpanderToggleCol;
+ gint m_nExpanderImageCol;
+ gint m_nIdCol;
+ int m_nPendingVAdjustment;
+ gulong m_nChangedSignalId;
+ gulong m_nRowActivatedSignalId;
+ gulong m_nTestExpandRowSignalId;
+ gulong m_nTestCollapseRowSignalId;
+ gulong m_nVAdjustmentChangedSignalId;
+ gulong m_nRowDeletedSignalId;
+ gulong m_nRowInsertedSignalId;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nPopupMenuSignalId;
+ gulong m_nKeyPressSignalId;
+#endif
+ gulong m_nQueryTooltipSignalId;
+ GtkAdjustment* m_pVAdjustment;
+ ImplSVEvent* m_pChangeEvent;
+
+ DECL_LINK(async_signal_changed, void*, void);
+
+ void launch_signal_changed()
+ {
+ //tdf#117991 selection change is sent before the focus change, and focus change
+ //is what will cause a spinbutton that currently has the focus to set its contents
+ //as the spin button value. So any LibreOffice callbacks on
+ //signal-change would happen before the spinbutton value-change occurs.
+ //To avoid this, send the signal-change to LibreOffice to occur after focus-change
+ //has been processed
+ if (m_pChangeEvent)
+ Application::RemoveUserEvent(m_pChangeEvent);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkEvent *pEvent = gtk_get_current_event();
+ m_bChangedByMouse = pEvent && categorizeEvent(pEvent) == VclInputFlags::MOUSE;
+#else
+ //TODO maybe iterate over gtk_widget_observe_controllers looking for a motion controller
+#endif
+
+ m_pChangeEvent = Application::PostUserEvent(LINK(this, GtkInstanceTreeView, async_signal_changed));
+ }
+
+ static void signalChanged(GtkTreeView*, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ pThis->launch_signal_changed();
+ }
+
+ void handle_row_activated()
+ {
+ if (signal_row_activated())
+ return;
+ GtkInstanceTreeIter aIter(nullptr);
+ if (!get_cursor(&aIter))
+ return;
+ if (gtk_tree_model_iter_has_child(m_pTreeModel, &aIter.iter))
+ get_row_expanded(aIter) ? collapse_row(aIter) : expand_row(aIter);
+ }
+
+ static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->handle_row_activated();
+ }
+
+ virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
+ {
+ return m_aPopupMenuHdl.Call(rCEvt);
+ }
+
+ void insert_row(GtkTreeIter& iter, const GtkTreeIter* parent, int pos, const OUString* pId, const OUString* pText,
+ const OUString* pIconName, const VirtualDevice* pDevice)
+ {
+ m_InsertWithValues(m_pTreeModel, &iter, const_cast<GtkTreeIter*>(parent), pos,
+ m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
+ m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr());
+
+ if (pIconName)
+ {
+ GdkPixbuf* pixbuf = getPixbuf(*pIconName);
+ m_Setter(m_pTreeModel, &iter, m_nImageCol, pixbuf, -1);
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+ else if (pDevice)
+ {
+ cairo_surface_t* surface = get_underlying_cairo_surface(*pDevice);
+
+ Size aSize(pDevice->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);
+
+ m_Setter(m_pTreeModel, &iter, m_nImageCol, target, -1);
+ cairo_surface_destroy(target);
+ }
+ }
+
+ bool separator_function(const GtkTreePath* path)
+ {
+ return ::separator_function(path, m_aSeparatorRows);
+ }
+
+ static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter);
+ bool bRet = pThis->separator_function(path);
+ gtk_tree_path_free(path);
+ return bRet;
+ }
+
+ OUString get(const GtkTreeIter& iter, int col) const
+ {
+ gchar* pStr;
+ gtk_tree_model_get(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, &pStr, -1);
+ OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pStr);
+ return sRet;
+ }
+
+ OUString get(int pos, int col) const
+ {
+ OUString sRet;
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ sRet = get(iter, col);
+ return sRet;
+ }
+
+ gint get_int(const GtkTreeIter& iter, int col) const
+ {
+ gint nRet(-1);
+ gtk_tree_model_get(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, &nRet, -1);
+ return nRet;
+ }
+
+ gint get_int(int pos, int col) const
+ {
+ gint nRet(-1);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ nRet = get_int(iter, col);
+ gtk_tree_model_get(m_pTreeModel, &iter, col, &nRet, -1);
+ return nRet;
+ }
+
+ bool get_bool(const GtkTreeIter& iter, int col) const
+ {
+ gboolean bRet(false);
+ gtk_tree_model_get(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, &bRet, -1);
+ return bRet;
+ }
+
+ bool get_bool(int pos, int col) const
+ {
+ bool bRet(false);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ bRet = get_bool(iter, col);
+ return bRet;
+ }
+
+ void set_toggle(const GtkTreeIter& iter, TriState eState, int col)
+ {
+ if (col == -1)
+ col = m_nExpanderToggleCol;
+ else
+ col = to_internal_model(col);
+
+ if (eState == TRISTATE_INDET)
+ {
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter),
+ m_aToggleVisMap[col], promote_arg(true), // checkbuttons are invisible until toggled on or off
+ m_aToggleTriStateMap[col], promote_arg(true), // tristate on
+ -1);
+ }
+ else
+ {
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter),
+ m_aToggleVisMap[col], promote_arg(true), // checkbuttons are invisible until toggled on or off
+ m_aToggleTriStateMap[col], promote_arg(false), // tristate off
+ col, promote_arg(eState == TRISTATE_TRUE), // set toggle state
+ -1);
+ }
+ }
+
+ void set(const GtkTreeIter& iter, int col, std::u16string_view rText)
+ {
+ OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, aStr.getStr(), -1);
+ }
+
+ void set(int pos, int col, std::u16string_view rText)
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ set(iter, col, rText);
+ }
+
+ void set(const GtkTreeIter& iter, int col, bool bOn)
+ {
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, promote_arg(bOn), -1);
+ }
+
+ void set(int pos, int col, bool bOn)
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ set(iter, col, bOn);
+ }
+
+ void set(const GtkTreeIter& iter, int col, gint bInt)
+ {
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, bInt, -1);
+ }
+
+ void set(int pos, int col, gint bInt)
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ set(iter, col, bInt);
+ }
+
+ void set(const GtkTreeIter& iter, int col, double fValue)
+ {
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, fValue, -1);
+ }
+
+ void set(int pos, int col, double fValue)
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ set(iter, col, fValue);
+ }
+
+ static gboolean signalTestExpandRow(GtkTreeView*, GtkTreeIter* iter, GtkTreePath*, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ return !pThis->signal_test_expand_row(*iter);
+ }
+
+ static gboolean signalTestCollapseRow(GtkTreeView*, GtkTreeIter* iter, GtkTreePath*, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ return !pThis->signal_test_collapse_row(*iter);
+ }
+
+ bool child_is_placeholder(GtkInstanceTreeIter& rGtkIter) const
+ {
+ GtkTreePath* pPath = gtk_tree_model_get_path(m_pTreeModel, &rGtkIter.iter);
+ bool bExpanding = m_aExpandingPlaceHolderParents.count(pPath);
+ gtk_tree_path_free(pPath);
+ if (bExpanding)
+ return true;
+
+ bool bPlaceHolder = false;
+ GtkTreeIter tmp;
+ if (gtk_tree_model_iter_children(m_pTreeModel, &tmp, &rGtkIter.iter))
+ {
+ rGtkIter.iter = tmp;
+ if (get_text(rGtkIter, -1) == "<dummy>")
+ {
+ bPlaceHolder = true;
+ }
+ }
+ return bPlaceHolder;
+ }
+
+ bool signal_test_expand_row(GtkTreeIter& iter)
+ {
+ disable_notify_events();
+
+ // if there's a preexisting placeholder child, required to make this
+ // potentially expandable in the first place, now we remove it
+ GtkInstanceTreeIter aIter(iter);
+ GtkTreePath* pPlaceHolderPath = nullptr;
+ bool bPlaceHolder = child_is_placeholder(aIter);
+ if (bPlaceHolder)
+ {
+ m_Remove(m_pTreeModel, &aIter.iter);
+
+ pPlaceHolderPath = gtk_tree_model_get_path(m_pTreeModel, &iter);
+ m_aExpandingPlaceHolderParents.insert(pPlaceHolderPath);
+ }
+
+ aIter.iter = iter;
+ bool bRet = signal_expanding(aIter);
+
+ if (bPlaceHolder)
+ {
+ //expand disallowed, restore placeholder
+ if (!bRet)
+ {
+ GtkTreeIter subiter;
+ OUString sDummy("<dummy>");
+ insert_row(subiter, &iter, -1, nullptr, &sDummy, nullptr, nullptr);
+ }
+ m_aExpandingPlaceHolderParents.erase(pPlaceHolderPath);
+ gtk_tree_path_free(pPlaceHolderPath);
+ }
+
+ enable_notify_events();
+ return bRet;
+ }
+
+ bool signal_test_collapse_row(const GtkTreeIter& iter)
+ {
+ disable_notify_events();
+
+ GtkInstanceTreeIter aIter(iter);
+ bool bRet = signal_collapsing(aIter);
+
+ enable_notify_events();
+ return bRet;
+ }
+
+ static void signalCellToggled(GtkCellRendererToggle* pCell, const gchar *path, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ void* pData = g_object_get_data(G_OBJECT(pCell), "g-lo-CellIndex");
+ pThis->signal_cell_toggled(path, reinterpret_cast<sal_IntPtr>(pData));
+ }
+
+ void signal_cell_toggled(const gchar *path, int nCol)
+ {
+ GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
+
+ // additionally set the cursor into the row the toggled element is in
+ gtk_tree_view_set_cursor(m_pTreeView, tree_path, nullptr, false);
+
+ GtkTreeIter iter;
+ gtk_tree_model_get_iter(m_pTreeModel, &iter, tree_path);
+
+ gboolean bRet(false);
+ gtk_tree_model_get(m_pTreeModel, &iter, nCol, &bRet, -1);
+ bRet = !bRet;
+ m_Setter(m_pTreeModel, &iter, nCol, bRet, -1);
+
+ set(iter, m_aToggleTriStateMap[nCol], false);
+
+ signal_toggled(iter_col(GtkInstanceTreeIter(iter), to_external_model(nCol)));
+
+ gtk_tree_path_free(tree_path);
+ }
+
+ DECL_LINK(async_stop_cell_editing, void*, void);
+
+ static void signalCellEditingStarted(GtkCellRenderer*, GtkCellEditable*, const gchar *path, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ if (!pThis->signal_cell_editing_started(path))
+ Application::PostUserEvent(LINK(pThis, GtkInstanceTreeView, async_stop_cell_editing));
+ }
+
+ bool signal_cell_editing_started(const gchar *path)
+ {
+ GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
+
+ GtkInstanceTreeIter aGtkIter(nullptr);
+ gtk_tree_model_get_iter(m_pTreeModel, &aGtkIter.iter, tree_path);
+ gtk_tree_path_free(tree_path);
+
+ return signal_editing_started(aGtkIter);
+ }
+
+ static void signalCellEdited(GtkCellRendererText* pCell, const gchar *path, const gchar *pNewText, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ pThis->signal_cell_edited(pCell, path, pNewText);
+ }
+
+ static void restoreNonEditable(GObject* pCell)
+ {
+ if (g_object_get_data(pCell, "g-lo-RestoreNonEditable"))
+ {
+ g_object_set(pCell, "editable", false, "editable-set", false, nullptr);
+ g_object_set_data(pCell, "g-lo-RestoreNonEditable", reinterpret_cast<gpointer>(false));
+ }
+ }
+
+ void signal_cell_edited(GtkCellRendererText* pCell, const gchar *path, const gchar* pNewText)
+ {
+ GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
+
+ GtkInstanceTreeIter aGtkIter(nullptr);
+ gtk_tree_model_get_iter(m_pTreeModel, &aGtkIter.iter, tree_path);
+ gtk_tree_path_free(tree_path);
+
+ OUString sText(pNewText, pNewText ? strlen(pNewText) : 0, RTL_TEXTENCODING_UTF8);
+ if (signal_editing_done(iter_string(aGtkIter, sText)))
+ {
+ void* pData = g_object_get_data(G_OBJECT(pCell), "g-lo-CellIndex");
+ set(aGtkIter.iter, reinterpret_cast<sal_IntPtr>(pData), sText);
+ }
+
+ restoreNonEditable(G_OBJECT(pCell));
+ }
+
+ static void signalCellEditingCanceled(GtkCellRenderer* pCell, gpointer /*widget*/)
+ {
+ restoreNonEditable(G_OBJECT(pCell));
+ }
+
+ void signal_column_clicked(GtkTreeViewColumn* pClickedColumn)
+ {
+ int nIndex(0);
+ for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ if (pColumn == pClickedColumn)
+ {
+ TreeView::signal_column_clicked(nIndex);
+ break;
+ }
+ ++nIndex;
+ }
+ }
+
+ static void signalColumnClicked(GtkTreeViewColumn* pColumn, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ pThis->signal_column_clicked(pColumn);
+ }
+
+ static void signalVAdjustmentChanged(GtkAdjustment*, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ pThis->signal_visible_range_changed();
+ }
+
+ // The outside concept of a column maps to a gtk CellRenderer, rather than
+ // a TreeViewColumn. If the first TreeViewColumn has a leading Toggle Renderer
+ // and/or a leading Image Renderer, those are considered special expander
+ // columns and precede index 0 and can be accessed via outside index -1
+ int to_external_model(int modelcol) const
+ {
+ if (m_nExpanderToggleCol != -1)
+ --modelcol;
+ if (m_nExpanderImageCol != -1)
+ --modelcol;
+ return modelcol;
+ }
+
+ int to_internal_model(int modelcol) const
+ {
+ if (m_nExpanderToggleCol != -1)
+ ++modelcol;
+ if (m_nExpanderImageCol != -1)
+ ++modelcol;
+ return modelcol;
+ }
+
+ void set_column_editable(int nCol, bool bEditable)
+ {
+ nCol = to_internal_model(nCol);
+
+ for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
+ if (reinterpret_cast<sal_IntPtr>(pData) == nCol)
+ {
+ g_object_set(G_OBJECT(pCellRenderer), "editable", bEditable, "editable-set", true, nullptr);
+ break;
+ }
+ }
+ g_list_free(pRenderers);
+ }
+ }
+
+ static void signalRowDeleted(GtkTreeModel*, GtkTreePath*, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ pThis->signal_model_changed();
+ }
+
+ static void signalRowInserted(GtkTreeModel*, GtkTreePath*, GtkTreeIter*, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ pThis->signal_model_changed();
+ }
+
+ static gint sortFunc(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ return pThis->sort_func(pModel, a, b);
+ }
+
+ gint sort_func(GtkTreeModel* pModel, GtkTreeIter* a, GtkTreeIter* b)
+ {
+ if (m_aCustomSort)
+ return m_aCustomSort(GtkInstanceTreeIter(*a), GtkInstanceTreeIter(*b));
+ return default_sort_func(pModel, a, b, m_xSorter.get());
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool signal_key_press(GdkEventKey* pEvent)
+ {
+ if (pEvent->keyval != GDK_KEY_Left && pEvent->keyval != GDK_KEY_Right)
+ return false;
+
+ GtkInstanceTreeIter aIter(nullptr);
+ if (!get_cursor(&aIter))
+ return false;
+
+ bool bHasChild = gtk_tree_model_iter_has_child(m_pTreeModel, &aIter.iter);
+
+ if (pEvent->keyval == GDK_KEY_Right)
+ {
+ if (bHasChild && !get_row_expanded(aIter))
+ {
+ expand_row(aIter);
+ return true;
+ }
+ return false;
+ }
+
+ if (bHasChild && get_row_expanded(aIter))
+ {
+ collapse_row(aIter);
+ return true;
+ }
+
+ if (iter_parent(aIter))
+ {
+ unselect_all();
+ set_cursor(aIter);
+ select(aIter);
+ return true;
+ }
+
+ return false;
+ }
+
+ static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ return pThis->signal_key_press(pEvent);
+ }
+#endif
+
+ static gboolean signalQueryTooltip(GtkWidget* /*pGtkWidget*/, gint x, gint y,
+ gboolean keyboard_tip, GtkTooltip *tooltip,
+ gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ GtkTreeIter iter;
+ GtkTreeView *pTreeView = pThis->m_pTreeView;
+ GtkTreeModel *pModel = gtk_tree_view_get_model(pTreeView);
+ GtkTreePath *pPath = nullptr;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (!gtk_tree_view_get_tooltip_context(pTreeView, x, y, keyboard_tip, &pModel, &pPath, &iter))
+ return false;
+#else
+ if (!gtk_tree_view_get_tooltip_context(pTreeView, &x, &y, keyboard_tip, &pModel, &pPath, &iter))
+ return false;
+#endif
+ OUString aTooltip = pThis->signal_query_tooltip(GtkInstanceTreeIter(iter));
+ if (!aTooltip.isEmpty())
+ {
+ gtk_tooltip_set_text(tooltip, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
+ gtk_tree_view_set_tooltip_row(pTreeView, tooltip, pPath);
+ }
+ gtk_tree_path_free(pPath);
+ return !aTooltip.isEmpty();
+ }
+
+ void last_child(GtkTreeModel* pModel, GtkTreeIter* result, GtkTreeIter* pParent, int nChildren) const
+ {
+ gtk_tree_model_iter_nth_child(pModel, result, pParent, nChildren - 1);
+ nChildren = gtk_tree_model_iter_n_children(pModel, result);
+ if (nChildren)
+ {
+ GtkTreeIter newparent(*result);
+ last_child(pModel, result, &newparent, nChildren);
+ }
+ }
+
+ GtkTreePath* get_path_of_last_entry(GtkTreeModel *pModel)
+ {
+ GtkTreePath *lastpath;
+ // find the last entry in the model for comparison
+ int nChildren = gtk_tree_model_iter_n_children(pModel, nullptr);
+ if (!nChildren)
+ lastpath = gtk_tree_path_new_from_indices(0, -1);
+ else
+ {
+ GtkTreeIter iter;
+ last_child(pModel, &iter, nullptr, nChildren);
+ lastpath = gtk_tree_model_get_path(pModel, &iter);
+ }
+ return lastpath;
+ }
+
+ void set_font_color(const GtkTreeIter& iter, const Color& rColor)
+ {
+ if (rColor == COL_AUTO)
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), m_nIdCol + 1, nullptr, -1);
+ else
+ {
+ GdkRGBA aColor{rColor.GetRed()/255.0f, rColor.GetGreen()/255.0f, rColor.GetBlue()/255.0f, 0};
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), m_nIdCol + 1, &aColor, -1);
+ }
+ }
+
+ int get_expander_size() const
+ {
+ // gtk4: _TREE_VIEW_EXPANDER_SIZE define in gtk/gtktreeview.c
+ gint nExpanderSize = 16;
+ // gtk4: _TREE_VIEW_HORIZONTAL_SEPARATOR define in gtk/gtktreeview.c
+ gint nHorizontalSeparator = 4;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_style_get(GTK_WIDGET(m_pTreeView),
+ "expander-size", &nExpanderSize,
+ "horizontal-separator", &nHorizontalSeparator,
+ nullptr);
+#endif
+
+ return nExpanderSize + (nHorizontalSeparator/ 2);
+ }
+
+ void real_vadjustment_set_value(int value)
+ {
+ disable_notify_events();
+ gtk_adjustment_set_value(m_pVAdjustment, value);
+ enable_notify_events();
+ }
+
+ static gboolean setAdjustmentCallback(GtkWidget*, GdkFrameClock*, gpointer widget)
+ {
+ GtkInstanceTreeView* pThis = static_cast<GtkInstanceTreeView*>(widget);
+ if (pThis->m_nPendingVAdjustment != -1)
+ {
+ pThis->real_vadjustment_set_value(pThis->m_nPendingVAdjustment);
+ pThis->m_nPendingVAdjustment = -1;
+ }
+ return false;
+ }
+
+ bool iter_next(weld::TreeIter& rIter, bool bOnlyExpanded) const
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ GtkTreeIter tmp;
+ GtkTreeIter iter = rGtkIter.iter;
+
+ bool ret = gtk_tree_model_iter_children(m_pTreeModel, &tmp, &iter);
+ if (ret && bOnlyExpanded && !get_row_expanded(rGtkIter))
+ ret = false;
+ rGtkIter.iter = tmp;
+ if (ret)
+ {
+ //on-demand dummy entry doesn't count
+ if (get_text(rGtkIter, -1) == "<dummy>")
+ return iter_next(rGtkIter, bOnlyExpanded);
+ return true;
+ }
+
+ tmp = iter;
+ if (gtk_tree_model_iter_next(m_pTreeModel, &tmp))
+ {
+ rGtkIter.iter = tmp;
+ //on-demand dummy entry doesn't count
+ if (get_text(rGtkIter, -1) == "<dummy>")
+ return iter_next(rGtkIter, bOnlyExpanded);
+ return true;
+ }
+ // Move up level(s) until we find the level where the next node exists.
+ while (gtk_tree_model_iter_parent(m_pTreeModel, &tmp, &iter))
+ {
+ iter = tmp;
+ if (gtk_tree_model_iter_next(m_pTreeModel, &tmp))
+ {
+ rGtkIter.iter = tmp;
+ //on-demand dummy entry doesn't count
+ if (get_text(rGtkIter, -1) == "<dummy>")
+ return iter_next(rGtkIter, bOnlyExpanded);
+ return true;
+ }
+ }
+ return false;
+ }
+
+public:
+ GtkInstanceTreeView(GtkTreeView* pTreeView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pTreeView), pBuilder, bTakeOwnership)
+ , m_pTreeView(pTreeView)
+ , m_pTreeModel(gtk_tree_view_get_model(m_pTreeView))
+ , m_bWorkAroundBadDragRegion(false)
+ , m_bInDrag(false)
+ , m_bChangedByMouse(false)
+ , m_nTextCol(-1)
+ , m_nTextView(-1)
+ , m_nImageCol(-1)
+ , m_nExpanderToggleCol(-1)
+ , m_nExpanderImageCol(-1)
+ , m_nPendingVAdjustment(-1)
+ , m_nChangedSignalId(g_signal_connect(gtk_tree_view_get_selection(pTreeView), "changed",
+ G_CALLBACK(signalChanged), this))
+ , m_nRowActivatedSignalId(g_signal_connect(pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
+ , m_nTestExpandRowSignalId(g_signal_connect(pTreeView, "test-expand-row", G_CALLBACK(signalTestExpandRow), this))
+ , m_nTestCollapseRowSignalId(g_signal_connect(pTreeView, "test-collapse-row", G_CALLBACK(signalTestCollapseRow), this))
+ , m_nVAdjustmentChangedSignalId(0)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_nPopupMenuSignalId(g_signal_connect(pTreeView, "popup-menu", G_CALLBACK(signalPopupMenu), this))
+ , m_nKeyPressSignalId(g_signal_connect(pTreeView, "key-press-event", G_CALLBACK(signalKeyPress), this))
+#endif
+ , m_nQueryTooltipSignalId(0)
+ , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTreeView)))
+ , m_pChangeEvent(nullptr)
+ {
+ if (GTK_IS_TREE_STORE(m_pTreeModel))
+ {
+ m_Setter = tree_store_set;
+ m_InsertWithValues = tree_store_insert_with_values;
+ m_Insert = tree_store_insert;
+ m_Prepend = tree_store_prepend;
+ m_Remove = tree_store_remove;
+ m_Swap = tree_store_swap;
+ m_SetValue = tree_store_set_value;
+ m_Clear = tree_store_clear;
+ }
+ else
+ {
+ /*
+ tdf#136559 see: https://gitlab.gnome.org/GNOME/gtk/-/issues/2693
+ If we only need a list and not a tree we can get a performance boost from using a ListStore
+ */
+ assert(!gtk_tree_view_get_show_expanders(m_pTreeView) && "a liststore can only be used if no tree structure is needed");
+ m_Setter = list_store_set;
+ m_InsertWithValues = list_store_insert_with_values;
+ m_Insert = list_store_insert;
+ m_Prepend = list_store_prepend;
+ m_Remove = list_store_remove;
+ m_Swap = list_store_swap;
+ m_SetValue = list_store_set_value;
+ m_Clear = list_store_clear;
+ }
+
+ /* The outside concept of a column maps to a gtk CellRenderer, rather than
+ a TreeViewColumn. If the first TreeViewColumn has a leading Toggle Renderer
+ and/or a leading Image Renderer, those are considered special expander
+ columns and precede index 0 and can be accessed via outside index -1
+ */
+ m_pColumns = gtk_tree_view_get_columns(m_pTreeView);
+ int nIndex(0);
+ int nViewColumn(0);
+ for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ m_aColumnSignalIds.push_back(g_signal_connect(pColumn, "clicked", G_CALLBACK(signalColumnClicked), this));
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
+ {
+ if (m_nTextCol == -1)
+ {
+ m_nTextCol = nIndex;
+ m_nTextView = nViewColumn;
+ }
+ m_aWeightMap[nIndex] = -1;
+ m_aSensitiveMap[nIndex] = -1;
+ m_aIndentMap[nIndex] = -1;
+ m_aAlignMap[nIndex] = -1;
+ g_signal_connect(G_OBJECT(pCellRenderer), "editing-started", G_CALLBACK(signalCellEditingStarted), this);
+ g_signal_connect(G_OBJECT(pCellRenderer), "editing-canceled", G_CALLBACK(signalCellEditingCanceled), this);
+ g_signal_connect(G_OBJECT(pCellRenderer), "edited", G_CALLBACK(signalCellEdited), this);
+ }
+ else if (GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
+ {
+ const bool bExpander = nIndex == 0 || (nIndex == 1 && m_nExpanderImageCol == 0);
+ if (bExpander)
+ m_nExpanderToggleCol = nIndex;
+ g_signal_connect(G_OBJECT(pCellRenderer), "toggled", G_CALLBACK(signalCellToggled), this);
+ m_aToggleVisMap[nIndex] = -1;
+ m_aToggleTriStateMap[nIndex] = -1;
+ }
+ else if (GTK_IS_CELL_RENDERER_PIXBUF(pCellRenderer))
+ {
+ const bool bExpander = g_list_next(pRenderer) != nullptr;
+ if (bExpander && m_nExpanderImageCol == -1)
+ m_nExpanderImageCol = nIndex;
+ else if (m_nImageCol == -1)
+ m_nImageCol = nIndex;
+ }
+ g_object_set_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex", reinterpret_cast<gpointer>(nIndex));
+ ++nIndex;
+ }
+ g_list_free(pRenderers);
+ ++nViewColumn;
+ }
+
+ m_nIdCol = nIndex++;
+
+ for (auto& a : m_aToggleVisMap)
+ a.second = nIndex++;
+ for (auto& a : m_aToggleTriStateMap)
+ a.second = nIndex++;
+ for (auto& a : m_aWeightMap)
+ a.second = nIndex++;
+ for (auto& a : m_aSensitiveMap)
+ a.second = nIndex++;
+ for (auto& a : m_aIndentMap)
+ a.second = nIndex++;
+ for (auto& a : m_aAlignMap)
+ a.second = nIndex++;
+
+ ensure_drag_begin_end();
+
+ m_nRowDeletedSignalId = g_signal_connect(m_pTreeModel, "row-deleted", G_CALLBACK(signalRowDeleted), this);
+ m_nRowInsertedSignalId = g_signal_connect(m_pTreeModel, "row-inserted", G_CALLBACK(signalRowInserted), this);
+ }
+
+ virtual void connect_query_tooltip(const Link<const weld::TreeIter&, OUString>& rLink) override
+ {
+ weld::TreeView::connect_query_tooltip(rLink);
+ m_nQueryTooltipSignalId = g_signal_connect(m_pTreeView, "query-tooltip", G_CALLBACK(signalQueryTooltip), this);
+ }
+
+ virtual void columns_autosize() override
+ {
+ gtk_tree_view_columns_autosize(m_pTreeView);
+ }
+
+ virtual void set_column_fixed_widths(const std::vector<int>& rWidths) override
+ {
+ GList* pEntry = g_list_first(m_pColumns);
+ for (auto nWidth : rWidths)
+ {
+ assert(pEntry && "wrong count");
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ gtk_tree_view_column_set_fixed_width(pColumn, nWidth);
+ pEntry = g_list_next(pEntry);
+ }
+ }
+
+ virtual void set_column_editables(const std::vector<bool>& rEditables) override
+ {
+ size_t nTabCount = rEditables.size();
+ for (size_t i = 0 ; i < nTabCount; ++i)
+ set_column_editable(i, rEditables[i]);
+ }
+
+ virtual void set_centered_column(int nCol) override
+ {
+ for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
+ if (reinterpret_cast<sal_IntPtr>(pData) == nCol)
+ {
+ g_object_set(G_OBJECT(pCellRenderer), "xalign", 0.5, nullptr);
+ break;
+ }
+ }
+ g_list_free(pRenderers);
+ }
+ }
+
+ virtual int get_column_width(int nColumn) const override
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
+ assert(pColumn && "wrong count");
+ int nWidth = gtk_tree_view_column_get_width(pColumn);
+ // https://github.com/exaile/exaile/issues/580
+ // after setting fixed_width on a column and requesting width before
+ // gtk has a chance to do its layout of the column means that the width
+ // request hasn't come into effect
+ if (!nWidth)
+ nWidth = gtk_tree_view_column_get_fixed_width(pColumn);
+ return nWidth;
+ }
+
+ virtual OUString get_column_title(int nColumn) const override
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
+ assert(pColumn && "wrong count");
+ const gchar* pTitle = gtk_tree_view_column_get_title(pColumn);
+ OUString sRet(pTitle, pTitle ? strlen(pTitle) : 0, RTL_TEXTENCODING_UTF8);
+ return sRet;
+ }
+
+ virtual void set_column_title(int nColumn, const OUString& rTitle) override
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
+ assert(pColumn && "wrong count");
+ gtk_tree_view_column_set_title(pColumn, OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ virtual void set_column_custom_renderer(int nColumn, bool bEnable) override
+ {
+ assert(n_children() == 0 && "tree must be empty");
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, nColumn));
+ assert(pColumn && "wrong count");
+
+ GtkCellRenderer* pExpander = nullptr;
+ GtkCellRenderer* pToggle = nullptr;
+
+ // migrate existing editable setting to the new renderer
+ gboolean is_editable(false);
+ void* pEditCellData(nullptr);
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+
+ void* pData = g_object_get_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex");
+ auto nCellIndex = reinterpret_cast<sal_IntPtr>(pData);
+
+ if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
+ {
+ g_object_get(pCellRenderer, "editable", &is_editable, nullptr);
+ pEditCellData = pData;
+ break;
+ }
+ else if (GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
+ {
+ if (nCellIndex == m_nExpanderToggleCol)
+ {
+ pToggle = pCellRenderer;
+ g_object_ref(pToggle);
+ }
+ }
+ else if (GTK_IS_CELL_RENDERER_PIXBUF(pCellRenderer))
+ {
+ if (nCellIndex == m_nExpanderImageCol)
+ {
+ pExpander = pCellRenderer;
+ g_object_ref(pExpander);
+ }
+ }
+
+ }
+ g_list_free(pRenderers);
+
+ GtkCellRenderer* pRenderer;
+
+ gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn));
+ if (pExpander)
+ {
+ gtk_tree_view_column_pack_start(pColumn, pExpander, false);
+ gtk_tree_view_column_add_attribute(pColumn, pExpander, "pixbuf", m_nExpanderImageCol);
+ g_object_unref(pExpander);
+ }
+ if (pToggle)
+ {
+ gtk_tree_view_column_pack_start(pColumn, pToggle, false);
+ gtk_tree_view_column_add_attribute(pColumn, pToggle, "active", m_nExpanderToggleCol);
+ gtk_tree_view_column_add_attribute(pColumn, pToggle, "active", m_nExpanderToggleCol);
+ gtk_tree_view_column_add_attribute(pColumn, pToggle, "visible", m_aToggleTriStateMap[m_nExpanderToggleCol]);
+ g_object_unref(pToggle);
+ }
+
+ if (bEnable)
+ {
+ pRenderer = custom_cell_renderer_new();
+ GValue value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_POINTER);
+ g_value_set_pointer(&value, static_cast<gpointer>(this));
+ g_object_set_property(G_OBJECT(pRenderer), "instance", &value);
+ gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "id", m_nIdCol);
+ }
+ else
+ {
+ pRenderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
+ }
+
+ if (is_editable)
+ {
+ g_object_set(pRenderer, "editable", true, "editable-set", true, nullptr);
+ g_object_set_data(G_OBJECT(pRenderer), "g-lo-CellIndex", pEditCellData);
+ g_signal_connect(pRenderer, "editing-started", G_CALLBACK(signalCellEditingStarted), this);
+ g_signal_connect(pRenderer, "editing-canceled", G_CALLBACK(signalCellEditingCanceled), this);
+ g_signal_connect(pRenderer, "edited", G_CALLBACK(signalCellEdited), this);
+ }
+ }
+
+ virtual void queue_draw() override
+ {
+ gtk_widget_queue_draw(GTK_WIDGET(m_pTreeView));
+ }
+
+ virtual void insert(const weld::TreeIter* pParent, int pos, const OUString* pText, const OUString* pId, const OUString* pIconName,
+ VirtualDevice* pImageSurface,
+ bool bChildrenOnDemand, weld::TreeIter* pRet) override
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ const GtkInstanceTreeIter* pGtkIter = static_cast<const GtkInstanceTreeIter*>(pParent);
+ insert_row(iter, pGtkIter ? &pGtkIter->iter : nullptr, pos, pId, pText, pIconName, pImageSurface);
+ if (bChildrenOnDemand)
+ {
+ GtkTreeIter subiter;
+ OUString sDummy("<dummy>");
+ insert_row(subiter, &iter, -1, nullptr, &sDummy, nullptr, nullptr);
+ }
+ if (pRet)
+ {
+ GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
+ pGtkRetIter->iter = iter;
+ }
+ enable_notify_events();
+ }
+
+ virtual void insert_separator(int pos, const OUString& rId) override
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ if (!gtk_tree_view_get_row_separator_func(m_pTreeView))
+ gtk_tree_view_set_row_separator_func(m_pTreeView, separatorFunction, this, nullptr);
+ insert_row(iter, nullptr, pos, &rId, nullptr, nullptr, nullptr);
+ GtkTreePath* pPath = gtk_tree_model_get_path(m_pTreeModel, &iter);
+ m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(m_pTreeModel, pPath));
+ gtk_tree_path_free(pPath);
+ enable_notify_events();
+ }
+
+ virtual void set_font_color(int pos, const Color& rColor) override
+ {
+ GtkTreeIter iter;
+ gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
+ set_font_color(iter, rColor);
+ }
+
+ virtual void set_font_color(const weld::TreeIter& rIter, const Color& rColor) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ set_font_color(rGtkIter.iter, rColor);
+ }
+
+ virtual void remove(int pos) override
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
+ m_Remove(m_pTreeModel, &iter);
+ enable_notify_events();
+ }
+
+ virtual int find_text(const OUString& rText) const override
+ {
+ Search aSearch(rText, m_nTextCol);
+ gtk_tree_model_foreach(m_pTreeModel, foreach_find, &aSearch);
+ return aSearch.index;
+ }
+
+ virtual int find_id(const OUString& rId) const override
+ {
+ Search aSearch(rId, m_nIdCol);
+ gtk_tree_model_foreach(m_pTreeModel, foreach_find, &aSearch);
+ return aSearch.index;
+ }
+
+ virtual void bulk_insert_for_each(int nSourceCount, const std::function<void(weld::TreeIter&, int nSourceIndex)>& func,
+ const weld::TreeIter* pParent,
+ const std::vector<int>* pFixedWidths) override
+ {
+ GtkInstanceTreeIter* pGtkIter = const_cast<GtkInstanceTreeIter*>(static_cast<const GtkInstanceTreeIter*>(pParent));
+
+ freeze();
+ if (!pGtkIter)
+ clear();
+ else
+ {
+ GtkTreeIter restore(pGtkIter->iter);
+
+ if (iter_children(*pGtkIter))
+ while (m_Remove(m_pTreeModel, &pGtkIter->iter));
+
+ pGtkIter->iter = restore;
+ }
+ GtkInstanceTreeIter aGtkIter(nullptr);
+
+ if (pFixedWidths)
+ set_column_fixed_widths(*pFixedWidths);
+
+ while (nSourceCount)
+ {
+ // tdf#125241 inserting backwards is massively faster
+ m_Prepend(m_pTreeModel, &aGtkIter.iter, pGtkIter ? &pGtkIter->iter : nullptr);
+ func(aGtkIter, --nSourceCount);
+ }
+
+ thaw();
+ }
+
+ virtual void swap(int pos1, int pos2) override
+ {
+ disable_notify_events();
+
+ GtkTreeIter iter1;
+ gtk_tree_model_iter_nth_child(m_pTreeModel, &iter1, nullptr, pos1);
+
+ GtkTreeIter iter2;
+ gtk_tree_model_iter_nth_child(m_pTreeModel, &iter2, nullptr, pos2);
+
+ m_Swap(m_pTreeModel, &iter1, &iter2);
+
+ enable_notify_events();
+ }
+
+ virtual void clear() override
+ {
+ disable_notify_events();
+ gtk_tree_view_set_row_separator_func(m_pTreeView, nullptr, nullptr, nullptr);
+ m_aSeparatorRows.clear();
+ m_Clear(m_pTreeModel);
+ enable_notify_events();
+ }
+
+ virtual void make_sorted() override
+ {
+ // thaw wants to restore sort state of freeze
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
+ m_xSorter.reset(new comphelper::string::NaturalStringSorter(
+ ::comphelper::getProcessComponentContext(),
+ Application::GetSettings().GetUILanguageTag().getLocale()));
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, sortFunc, this, nullptr);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
+ }
+
+ virtual void make_unsorted() override
+ {
+ m_xSorter.reset();
+ int nSortColumn;
+ GtkSortType eSortType;
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
+ gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, eSortType);
+ }
+
+ virtual void set_sort_order(bool bAscending) override
+ {
+ GtkSortType eSortType = bAscending ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
+
+ gint sort_column_id(0);
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr);
+ gtk_tree_sortable_set_sort_column_id(pSortable, sort_column_id, eSortType);
+ }
+
+ virtual bool get_sort_order() const override
+ {
+ int nSortColumn;
+ GtkSortType eSortType;
+
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
+ return nSortColumn != GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID && eSortType == GTK_SORT_ASCENDING;
+ }
+
+ virtual void set_sort_indicator(TriState eState, int col) override
+ {
+ assert(col >= 0 && "cannot sort on expander column");
+
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
+ assert(pColumn && "wrong count");
+ if (eState == TRISTATE_INDET)
+ gtk_tree_view_column_set_sort_indicator(pColumn, false);
+ else
+ {
+ gtk_tree_view_column_set_sort_indicator(pColumn, true);
+ GtkSortType eSortType = eState == TRISTATE_TRUE ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
+ gtk_tree_view_column_set_sort_order(pColumn, eSortType);
+ }
+ }
+
+ virtual TriState get_sort_indicator(int col) const override
+ {
+ assert(col >= 0 && "cannot sort on expander column");
+
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
+ if (!gtk_tree_view_column_get_sort_indicator(pColumn))
+ return TRISTATE_INDET;
+ return gtk_tree_view_column_get_sort_order(pColumn) == GTK_SORT_ASCENDING ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+
+ virtual int get_sort_column() const override
+ {
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gint sort_column_id(0);
+ if (!gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr))
+ return -1;
+ return to_external_model(sort_column_id);
+ }
+
+ virtual void set_sort_column(int nColumn) override
+ {
+ if (nColumn == -1)
+ {
+ make_unsorted();
+ return;
+ }
+ GtkSortType eSortType;
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_get_sort_column_id(pSortable, nullptr, &eSortType);
+ int nSortCol = to_internal_model(nColumn);
+ gtk_tree_sortable_set_sort_func(pSortable, nSortCol, sortFunc, this, nullptr);
+ gtk_tree_sortable_set_sort_column_id(pSortable, nSortCol, eSortType);
+ }
+
+ virtual void set_sort_func(const std::function<int(const weld::TreeIter&, const weld::TreeIter&)>& func) override
+ {
+ weld::TreeView::set_sort_func(func);
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_sort_column_changed(pSortable);
+ }
+
+ virtual int n_children() const override
+ {
+ return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr);
+ }
+
+ virtual int iter_n_children(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ return gtk_tree_model_iter_n_children(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ }
+
+ virtual void select(int pos) override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
+ disable_notify_events();
+ if (pos == -1 || (pos == 0 && n_children() == 0))
+ {
+ gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(m_pTreeView));
+ }
+ else
+ {
+ GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
+ gtk_tree_selection_select_path(gtk_tree_view_get_selection(m_pTreeView), path);
+ gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
+ gtk_tree_path_free(path);
+ }
+ enable_notify_events();
+ }
+
+ virtual void set_cursor(int pos) override
+ {
+ disable_notify_events();
+ GtkTreePath* path;
+ if (pos != -1)
+ {
+ path = gtk_tree_path_new_from_indices(pos, -1);
+ gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
+ }
+ else
+ path = gtk_tree_path_new_from_indices(G_MAXINT, -1);
+ gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
+ gtk_tree_path_free(path);
+ enable_notify_events();
+ }
+
+ virtual void scroll_to_row(int pos) override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
+ disable_notify_events();
+ GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
+ gtk_tree_view_expand_to_path(m_pTreeView, path);
+ gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
+ gtk_tree_path_free(path);
+ enable_notify_events();
+ }
+
+ virtual bool is_selected(int pos) const override
+ {
+ GtkTreeIter iter;
+ gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
+ return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(m_pTreeView), &iter);
+ }
+
+ virtual void unselect(int pos) override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
+ disable_notify_events();
+ if (pos == -1 || (pos == 0 && n_children() == 0))
+ {
+ gtk_tree_selection_select_all(gtk_tree_view_get_selection(m_pTreeView));
+ }
+ else
+ {
+ GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
+ gtk_tree_selection_unselect_path(gtk_tree_view_get_selection(m_pTreeView), path);
+ gtk_tree_path_free(path);
+ }
+ enable_notify_events();
+ }
+
+ virtual std::vector<int> get_selected_rows() const override
+ {
+ std::vector<int> aRows;
+
+ GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), nullptr);
+ for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
+ {
+ GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
+
+ gint depth;
+ gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
+ int nRow = indices[depth-1];
+
+ aRows.push_back(nRow);
+ }
+ g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
+
+ return aRows;
+ }
+
+ virtual void all_foreach(const std::function<bool(weld::TreeIter&)>& func) override
+ {
+ g_object_freeze_notify(G_OBJECT(m_pTreeModel));
+
+ GtkInstanceTreeIter aGtkIter(nullptr);
+ if (get_iter_first(aGtkIter))
+ {
+ do
+ {
+ if (func(aGtkIter))
+ break;
+ } while (iter_next(aGtkIter));
+ }
+
+ g_object_thaw_notify(G_OBJECT(m_pTreeModel));
+ }
+
+ virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
+ {
+ g_object_freeze_notify(G_OBJECT(m_pTreeModel));
+
+ GtkInstanceTreeIter aGtkIter(nullptr);
+
+ GtkTreeModel* pModel;
+ GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
+ for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
+ {
+ GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
+ gtk_tree_model_get_iter(pModel, &aGtkIter.iter, path);
+ if (func(aGtkIter))
+ break;
+ }
+ g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
+
+ g_object_thaw_notify(G_OBJECT(m_pTreeModel));
+ }
+
+ virtual void visible_foreach(const std::function<bool(weld::TreeIter&)>& func) override
+ {
+ g_object_freeze_notify(G_OBJECT(m_pTreeModel));
+
+ GtkTreePath* start_path;
+ GtkTreePath* end_path;
+
+ if (!gtk_tree_view_get_visible_range(m_pTreeView, &start_path, &end_path))
+ return;
+
+ GtkInstanceTreeIter aGtkIter(nullptr);
+ gtk_tree_model_get_iter(m_pTreeModel, &aGtkIter.iter, start_path);
+
+ do
+ {
+ if (func(aGtkIter))
+ break;
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, &aGtkIter.iter);
+ bool bContinue = gtk_tree_path_compare(path, end_path) != 0;
+ gtk_tree_path_free(path);
+ if (!bContinue)
+ break;
+ if (!iter_next(aGtkIter))
+ break;
+ } while(true);
+
+ gtk_tree_path_free(start_path);
+ gtk_tree_path_free(end_path);
+
+ g_object_thaw_notify(G_OBJECT(m_pTreeModel));
+ }
+
+ virtual void connect_visible_range_changed(const Link<weld::TreeView&, void>& rLink) override
+ {
+ weld::TreeView::connect_visible_range_changed(rLink);
+ if (!m_nVAdjustmentChangedSignalId)
+ {
+ GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
+ m_nVAdjustmentChangedSignalId = g_signal_connect(pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustmentChanged), this);
+ }
+ }
+
+ virtual bool is_selected(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ return gtk_tree_selection_iter_is_selected(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ }
+
+ virtual OUString get_text(int pos, int col) const override
+ {
+ if (col == -1)
+ col = m_nTextCol;
+ else
+ col = to_internal_model(col);
+ return get(pos, col);
+ }
+
+ virtual void set_text(int pos, const OUString& rText, int col) override
+ {
+ if (col == -1)
+ col = m_nTextCol;
+ else
+ col = to_internal_model(col);
+ set(pos, col, rText);
+ }
+
+ virtual TriState get_toggle(int pos, int col) const override
+ {
+ if (col == -1)
+ col = m_nExpanderToggleCol;
+ else
+ col = to_internal_model(col);
+
+ if (get_bool(pos, m_aToggleTriStateMap.find(col)->second))
+ return TRISTATE_INDET;
+ return get_bool(pos, col) ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+
+ virtual TriState get_toggle(const weld::TreeIter& rIter, int col) const override
+ {
+ if (col == -1)
+ col = m_nExpanderToggleCol;
+ else
+ col = to_internal_model(col);
+
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ if (get_bool(rGtkIter.iter, m_aToggleTriStateMap.find(col)->second))
+ return TRISTATE_INDET;
+ return get_bool(rGtkIter.iter, col) ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+
+ virtual void set_toggle(const weld::TreeIter& rIter, TriState eState, int col) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ set_toggle(rGtkIter.iter, eState, col);
+ }
+
+ virtual void set_toggle(int pos, TriState eState, int col) override
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ set_toggle(iter, eState, col);
+ }
+
+ virtual void enable_toggle_buttons(weld::ColumnToggleType eType) override
+ {
+ for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ if (!GTK_IS_CELL_RENDERER_TOGGLE(pCellRenderer))
+ continue;
+ GtkCellRendererToggle* pToggle = GTK_CELL_RENDERER_TOGGLE(pCellRenderer);
+ gtk_cell_renderer_toggle_set_radio(pToggle, eType == weld::ColumnToggleType::Radio);
+ }
+ g_list_free(pRenderers);
+ }
+ }
+
+ virtual void set_clicks_to_toggle(int /*nToggleBehavior*/) override
+ {
+ }
+
+ virtual void set_extra_row_indent(const weld::TreeIter& rIter, int nIndentLevel) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ set(rGtkIter.iter, m_aIndentMap[m_nTextCol], nIndentLevel * get_expander_size());
+ }
+
+ virtual void set_text_emphasis(const weld::TreeIter& rIter, bool bOn, int col) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ col = to_internal_model(col);
+ set(rGtkIter.iter, m_aWeightMap[col], bOn ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
+ }
+
+ virtual void set_text_emphasis(int pos, bool bOn, int col) override
+ {
+ col = to_internal_model(col);
+ set(pos, m_aWeightMap[col], bOn ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
+ }
+
+ virtual bool get_text_emphasis(const weld::TreeIter& rIter, int col) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ col = to_internal_model(col);
+ return get_int(rGtkIter.iter, m_aWeightMap.find(col)->second) == PANGO_WEIGHT_BOLD;
+ }
+
+ virtual bool get_text_emphasis(int pos, int col) const override
+ {
+ col = to_internal_model(col);
+ return get_int(pos, m_aWeightMap.find(col)->second) == PANGO_WEIGHT_BOLD;
+ }
+
+ virtual void set_text_align(const weld::TreeIter& rIter, double fAlign, int col) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ col = to_internal_model(col);
+ set(rGtkIter.iter, m_aAlignMap[col], fAlign);
+ }
+
+ virtual void set_text_align(int pos, double fAlign, int col) override
+ {
+ col = to_internal_model(col);
+ set(pos, m_aAlignMap[col], fAlign);
+ }
+
+ using GtkInstanceWidget::set_sensitive;
+
+ virtual void set_sensitive(int pos, bool bSensitive, int col) override
+ {
+ if (col == -1)
+ col = m_nTextCol;
+ else
+ col = to_internal_model(col);
+ set(pos, m_aSensitiveMap[col], bSensitive);
+ }
+
+ virtual void set_sensitive(const weld::TreeIter& rIter, bool bSensitive, int col) override
+ {
+ if (col == -1)
+ col = m_nTextCol;
+ else
+ col = to_internal_model(col);
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ set(rGtkIter.iter, m_aSensitiveMap[col], bSensitive);
+ }
+
+ void set_image(const GtkTreeIter& iter, int col, GdkPixbuf* pixbuf)
+ {
+ if (col == -1)
+ col = m_nExpanderImageCol;
+ else
+ col = to_internal_model(col);
+ m_Setter(m_pTreeModel, const_cast<GtkTreeIter*>(&iter), col, pixbuf, -1);
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+
+ void set_image(int pos, GdkPixbuf* pixbuf, int col)
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ {
+ set_image(iter, col, pixbuf);
+ }
+ }
+
+ virtual void set_image(int pos, const css::uno::Reference<css::graphic::XGraphic>& rImage, int col) override
+ {
+ set_image(pos, getPixbuf(rImage), col);
+ }
+
+ virtual void set_image(int pos, const OUString& rImage, int col) override
+ {
+ set_image(pos, getPixbuf(rImage), col);
+ }
+
+ virtual void set_image(int pos, VirtualDevice& rImage, int col) override
+ {
+ set_image(pos, getPixbuf(rImage), col);
+ }
+
+ virtual void set_image(const weld::TreeIter& rIter, const css::uno::Reference<css::graphic::XGraphic>& rImage, int col) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ set_image(rGtkIter.iter, col, getPixbuf(rImage));
+ }
+
+ virtual void set_image(const weld::TreeIter& rIter, const OUString& rImage, int col) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ set_image(rGtkIter.iter, col, getPixbuf(rImage));
+ }
+
+ virtual void set_image(const weld::TreeIter& rIter, VirtualDevice& rImage, int col) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ set_image(rGtkIter.iter, col, getPixbuf(rImage));
+ }
+
+ virtual OUString get_id(int pos) const override
+ {
+ return get(pos, m_nIdCol);
+ }
+
+ virtual void set_id(int pos, const OUString& rId) override
+ {
+ return set(pos, m_nIdCol, rId);
+ }
+
+ virtual int get_iter_index_in_parent(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+
+ gint depth;
+ gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
+ int nRet = indices[depth-1];
+
+ gtk_tree_path_free(path);
+
+ return nRet;
+ }
+
+ virtual int iter_compare(const weld::TreeIter& a, const weld::TreeIter& b) const override
+ {
+ const GtkInstanceTreeIter& rGtkIterA = static_cast<const GtkInstanceTreeIter&>(a);
+ const GtkInstanceTreeIter& rGtkIterB = static_cast<const GtkInstanceTreeIter&>(b);
+
+ GtkTreePath* pathA = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIterA.iter));
+ GtkTreePath* pathB = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIterB.iter));
+
+ int nRet = gtk_tree_path_compare(pathA, pathB);
+
+ gtk_tree_path_free(pathB);
+ gtk_tree_path_free(pathA);
+
+ return nRet;
+ }
+
+ // by copy and delete of old copy
+ void move_subtree(GtkTreeIter& rFromIter, GtkTreeIter* pGtkParentIter, int nIndexInNewParent)
+ {
+ int nCols = gtk_tree_model_get_n_columns(m_pTreeModel);
+ GValue value;
+
+ GtkTreeIter toiter;
+ m_Insert(m_pTreeModel, &toiter, pGtkParentIter, nIndexInNewParent);
+
+ for (int i = 0; i < nCols; ++i)
+ {
+ memset(&value, 0, sizeof(GValue));
+ gtk_tree_model_get_value(m_pTreeModel, &rFromIter, i, &value);
+ m_SetValue(m_pTreeModel, &toiter, i, &value);
+ g_value_unset(&value);
+ }
+
+ GtkTreeIter tmpfromiter;
+ if (gtk_tree_model_iter_children(m_pTreeModel, &tmpfromiter, &rFromIter))
+ {
+ int j = 0;
+ do
+ {
+ move_subtree(tmpfromiter, &toiter, j++);
+ } while (gtk_tree_model_iter_next(m_pTreeModel, &tmpfromiter));
+ }
+
+ m_Remove(m_pTreeModel, &rFromIter);
+ }
+
+ virtual void move_subtree(weld::TreeIter& rNode, const weld::TreeIter* pNewParent, int nIndexInNewParent) override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rNode);
+ const GtkInstanceTreeIter* pGtkParentIter = static_cast<const GtkInstanceTreeIter*>(pNewParent);
+ move_subtree(rGtkIter.iter, pGtkParentIter ? const_cast<GtkTreeIter*>(&pGtkParentIter->iter) : nullptr, nIndexInNewParent);
+ }
+
+ virtual int get_selected_index() const override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
+ int nRet = -1;
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(m_pTreeView);
+ if (gtk_tree_selection_get_mode(selection) != GTK_SELECTION_MULTIPLE)
+ {
+ GtkTreeIter iter;
+ GtkTreeModel* pModel;
+ if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(m_pTreeView), &pModel, &iter))
+ {
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, &iter);
+
+ gint depth;
+ gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
+ nRet = indices[depth-1];
+
+ gtk_tree_path_free(path);
+ }
+ }
+ else
+ {
+ auto vec = get_selected_rows();
+ return vec.empty() ? -1 : vec[0];
+ }
+ return nRet;
+ }
+
+ bool get_selected_iterator(GtkTreeIter* pIter) const
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
+ bool bRet = false;
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(m_pTreeView);
+ if (gtk_tree_selection_get_mode(selection) != GTK_SELECTION_MULTIPLE)
+ bRet = gtk_tree_selection_get_selected(gtk_tree_view_get_selection(m_pTreeView), nullptr, pIter);
+ else
+ {
+ GtkTreeModel* pModel;
+ GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
+ for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
+ {
+ if (pIter)
+ {
+ GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
+ gtk_tree_model_get_iter(pModel, pIter, path);
+ }
+ bRet = true;
+ break;
+ }
+ g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
+ }
+ return bRet;
+ }
+
+ virtual OUString get_selected_text() const override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
+ GtkTreeIter iter;
+ if (get_selected_iterator(&iter))
+ return get(iter, m_nTextCol);
+ return OUString();
+ }
+
+ virtual OUString get_selected_id() const override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't request selection when frozen");
+ GtkTreeIter iter;
+ if (get_selected_iterator(&iter))
+ return get(iter, m_nIdCol);
+ return OUString();
+ }
+
+ virtual std::unique_ptr<weld::TreeIter> make_iterator(const weld::TreeIter* pOrig) const override
+ {
+ return std::unique_ptr<weld::TreeIter>(new GtkInstanceTreeIter(static_cast<const GtkInstanceTreeIter*>(pOrig)));
+ }
+
+ virtual void copy_iterator(const weld::TreeIter& rSource, weld::TreeIter& rDest) const override
+ {
+ const GtkInstanceTreeIter& rGtkSource(static_cast<const GtkInstanceTreeIter&>(rSource));
+ GtkInstanceTreeIter& rGtkDest(static_cast<GtkInstanceTreeIter&>(rDest));
+ rGtkDest.iter = rGtkSource.iter;
+ }
+
+ virtual bool get_selected(weld::TreeIter* pIter) const override
+ {
+ GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
+ return get_selected_iterator(pGtkIter ? &pGtkIter->iter : nullptr);
+ }
+
+ virtual bool get_cursor(weld::TreeIter* pIter) const override
+ {
+ GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
+ GtkTreePath* path;
+ gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
+ if (pGtkIter && path)
+ {
+ gtk_tree_model_get_iter(m_pTreeModel, &pGtkIter->iter, path);
+ }
+ if (!path)
+ return false;
+ gtk_tree_path_free(path);
+ return true;
+ }
+
+ virtual int get_cursor_index() const override
+ {
+ int nRet = -1;
+
+ GtkTreePath* path;
+ gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
+ if (path)
+ {
+ gint depth;
+ gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
+ nRet = indices[depth-1];
+ gtk_tree_path_free(path);
+ }
+
+ return nRet;
+ }
+
+ virtual void set_cursor(const weld::TreeIter& rIter) override
+ {
+ disable_notify_events();
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreeIter Iter;
+ if (gtk_tree_model_iter_parent(m_pTreeModel, &Iter, const_cast<GtkTreeIter*>(&rGtkIter.iter)))
+ {
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, &Iter);
+ if (!gtk_tree_view_row_expanded(m_pTreeView, path))
+ gtk_tree_view_expand_to_path(m_pTreeView, path);
+ gtk_tree_path_free(path);
+ }
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
+ gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
+ gtk_tree_path_free(path);
+ enable_notify_events();
+ }
+
+ virtual bool get_iter_first(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ return gtk_tree_model_get_iter_first(m_pTreeModel, &rGtkIter.iter);
+ }
+
+ virtual bool iter_next_sibling(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ return gtk_tree_model_iter_next(m_pTreeModel, &rGtkIter.iter);
+ }
+
+ virtual bool iter_previous_sibling(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ return gtk_tree_model_iter_previous(m_pTreeModel, &rGtkIter.iter);
+ }
+
+ virtual bool iter_next(weld::TreeIter& rIter) const override
+ {
+ return iter_next(rIter, false);
+ }
+
+ virtual bool iter_previous(weld::TreeIter& rIter) const override
+ {
+ bool ret = false;
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ GtkTreeIter iter = rGtkIter.iter;
+ GtkTreeIter tmp = iter;
+ if (gtk_tree_model_iter_previous(m_pTreeModel, &tmp))
+ {
+ // Move down level(s) until we find the level where the last node exists.
+ int nChildren = gtk_tree_model_iter_n_children(m_pTreeModel, &tmp);
+ if (!nChildren)
+ rGtkIter.iter = tmp;
+ else
+ last_child(m_pTreeModel, &rGtkIter.iter, &tmp, nChildren);
+ ret = true;
+ }
+ else
+ {
+ // Move up level
+ if (gtk_tree_model_iter_parent(m_pTreeModel, &tmp, &iter))
+ {
+ rGtkIter.iter = tmp;
+ ret = true;
+ }
+ }
+
+ if (ret)
+ {
+ //on-demand dummy entry doesn't count
+ if (get_text(rGtkIter, -1) == "<dummy>")
+ return iter_previous(rGtkIter);
+ return true;
+ }
+
+ return false;
+ }
+
+ virtual bool iter_children(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ GtkTreeIter tmp;
+ bool ret = gtk_tree_model_iter_children(m_pTreeModel, &tmp, &rGtkIter.iter);
+ rGtkIter.iter = tmp;
+ if (ret)
+ {
+ //on-demand dummy entry doesn't count
+ return get_text(rGtkIter, -1) != "<dummy>";
+ }
+ return ret;
+ }
+
+ virtual bool iter_parent(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ GtkTreeIter tmp;
+ bool ret = gtk_tree_model_iter_parent(m_pTreeModel, &tmp, &rGtkIter.iter);
+ rGtkIter.iter = tmp;
+ return ret;
+ }
+
+ virtual void remove(const weld::TreeIter& rIter) override
+ {
+ disable_notify_events();
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ m_Remove(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ enable_notify_events();
+ }
+
+ virtual void remove_selection() override
+ {
+ disable_notify_events();
+
+ std::vector<GtkTreeIter> aIters;
+ GtkTreeModel* pModel;
+ GList* pList = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m_pTreeView), &pModel);
+ for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
+ {
+ GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
+ aIters.emplace_back();
+ gtk_tree_model_get_iter(pModel, &aIters.back(), path);
+ }
+ g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
+
+ for (auto& iter : aIters)
+ m_Remove(m_pTreeModel, &iter);
+
+ enable_notify_events();
+ }
+
+ virtual void select(const weld::TreeIter& rIter) override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
+ disable_notify_events();
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ gtk_tree_selection_select_iter(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ enable_notify_events();
+ }
+
+ virtual void scroll_to_row(const weld::TreeIter& rIter) override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
+ disable_notify_events();
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ gtk_tree_view_expand_to_path(m_pTreeView, path);
+ gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
+ gtk_tree_path_free(path);
+ enable_notify_events();
+ }
+
+ virtual void unselect(const weld::TreeIter& rIter) override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
+ disable_notify_events();
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ gtk_tree_selection_unselect_iter(gtk_tree_view_get_selection(m_pTreeView), const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ enable_notify_events();
+ }
+
+ virtual int get_iter_depth(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ int ret = gtk_tree_path_get_depth(path) - 1;
+ gtk_tree_path_free(path);
+ return ret;
+ }
+
+ virtual bool iter_has_child(const weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter aTempCopy(static_cast<const GtkInstanceTreeIter*>(&rIter));
+ return iter_children(aTempCopy);
+ }
+
+ virtual bool get_row_expanded(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ bool ret = gtk_tree_view_row_expanded(m_pTreeView, path);
+ gtk_tree_path_free(path);
+ return ret;
+ }
+
+ virtual bool get_children_on_demand(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkInstanceTreeIter aIter(&rGtkIter);
+ return child_is_placeholder(aIter);
+ }
+
+ virtual void set_children_on_demand(const weld::TreeIter& rIter, bool bChildrenOnDemand) override
+ {
+ disable_notify_events();
+
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkInstanceTreeIter aPlaceHolderIter(&rGtkIter);
+
+ bool bPlaceHolder = child_is_placeholder(aPlaceHolderIter);
+
+ if (bChildrenOnDemand && !bPlaceHolder)
+ {
+ GtkTreeIter subiter;
+ OUString sDummy("<dummy>");
+ insert_row(subiter, &rGtkIter.iter, -1, nullptr, &sDummy, nullptr, nullptr);
+ }
+ else if (!bChildrenOnDemand && bPlaceHolder)
+ remove(aPlaceHolderIter);
+
+ enable_notify_events();
+ }
+
+ virtual void expand_row(const weld::TreeIter& rIter) override
+ {
+ assert(gtk_tree_view_get_model(m_pTreeView) && "don't expand when frozen");
+
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ if (!gtk_tree_view_row_expanded(m_pTreeView, path))
+ gtk_tree_view_expand_to_path(m_pTreeView, path);
+ gtk_tree_path_free(path);
+ }
+
+ virtual void collapse_row(const weld::TreeIter& rIter) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ if (gtk_tree_view_row_expanded(m_pTreeView, path))
+ gtk_tree_view_collapse_row(m_pTreeView, path);
+ gtk_tree_path_free(path);
+ }
+
+ virtual OUString get_text(const weld::TreeIter& rIter, int col) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ if (col == -1)
+ col = m_nTextCol;
+ else
+ col = to_internal_model(col);
+ return get(rGtkIter.iter, col);
+ }
+
+ virtual void set_text(const weld::TreeIter& rIter, const OUString& rText, int col) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ if (col == -1)
+ col = m_nTextCol;
+ else
+ col = to_internal_model(col);
+ set(rGtkIter.iter, col, rText);
+ }
+
+ virtual OUString get_id(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ return get(rGtkIter.iter, m_nIdCol);
+ }
+
+ virtual void set_id(const weld::TreeIter& rIter, const OUString& rId) override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ set(rGtkIter.iter, m_nIdCol, rId);
+ }
+
+ virtual void freeze() override
+ {
+ disable_notify_events();
+ bool bIsFirstFreeze = IsFirstFreeze();
+ GtkInstanceWidget::freeze();
+ if (bIsFirstFreeze)
+ {
+ g_object_ref(m_pTreeModel);
+ gtk_tree_view_set_model(m_pTreeView, nullptr);
+ g_object_freeze_notify(G_OBJECT(m_pTreeModel));
+ if (m_xSorter)
+ {
+ int nSortColumn;
+ GtkSortType eSortType;
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_get_sort_column_id(pSortable, &nSortColumn, &eSortType);
+ gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, eSortType);
+
+ m_aSavedSortColumns.push_back(nSortColumn);
+ m_aSavedSortTypes.push_back(eSortType);
+ }
+ }
+ enable_notify_events();
+ }
+
+ virtual void thaw() override
+ {
+ disable_notify_events();
+ if (IsLastThaw())
+ {
+ if (m_xSorter)
+ {
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_aSavedSortColumns.back(), m_aSavedSortTypes.back());
+ m_aSavedSortTypes.pop_back();
+ m_aSavedSortColumns.pop_back();
+ }
+ g_object_thaw_notify(G_OBJECT(m_pTreeModel));
+ gtk_tree_view_set_model(m_pTreeView, GTK_TREE_MODEL(m_pTreeModel));
+ g_object_unref(m_pTreeModel);
+ }
+ GtkInstanceWidget::thaw();
+ enable_notify_events();
+ }
+
+ virtual int get_height_rows(int nRows) const override
+ {
+ return ::get_height_rows(m_pTreeView, m_pColumns, nRows);
+ }
+
+ virtual Size get_size_request() const override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ {
+ return Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
+ gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
+ }
+ int nWidth, nHeight;
+ gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
+ return Size(nWidth, nHeight);
+ }
+
+ virtual Size get_preferred_size() const override
+ {
+ Size aRet(-1, -1);
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ {
+ aRet = Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
+ gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
+ }
+ GtkRequisition size;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // sometimes gtk gives a bad outcome for gtk_widget_get_preferred_size if GtkTreeView's
+ // do_validate_rows hasn't been run before querying the preferred size, if we call
+ // gtk_widget_get_preferred_width first, we can guarantee do_validate_rows gets called
+ gtk_widget_get_preferred_width(m_pWidget, nullptr, &size.width);
+#endif
+ gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
+ if (aRet.Width() == -1)
+ aRet.setWidth(size.width);
+ if (aRet.Height() == -1)
+ aRet.setHeight(size.height);
+ return aRet;
+ }
+
+ virtual void show() override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ gtk_widget_show(pParent);
+ gtk_widget_show(m_pWidget);
+ }
+
+ virtual void hide() override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ gtk_widget_hide(pParent);
+ gtk_widget_hide(m_pWidget);
+ }
+
+ virtual void enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants) override
+ {
+ do_enable_drag_source(rHelper, eDNDConstants);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ virtual void drag_source_set(const std::vector<GtkTargetEntry>& rGtkTargets, GdkDragAction eDragAction) override
+ {
+ if (rGtkTargets.empty() && !eDragAction)
+ gtk_tree_view_unset_rows_drag_source(m_pTreeView);
+ else
+ gtk_tree_view_enable_model_drag_source(m_pTreeView, GDK_BUTTON1_MASK, rGtkTargets.data(), rGtkTargets.size(), eDragAction);
+ }
+#endif
+
+ virtual void set_selection_mode(SelectionMode eMode) override
+ {
+ disable_notify_events();
+ gtk_tree_selection_set_mode(gtk_tree_view_get_selection(m_pTreeView), VclToGtk(eMode));
+ enable_notify_events();
+ }
+
+ virtual int count_selected_rows() const override
+ {
+ return gtk_tree_selection_count_selected_rows(gtk_tree_view_get_selection(m_pTreeView));
+ }
+
+ int starts_with(const OUString& rStr, int nStartRow, bool bCaseSensitive)
+ {
+ return ::starts_with(m_pTreeModel, rStr, m_nTextCol, nStartRow, bCaseSensitive);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
+ g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
+
+ g_signal_handler_block(m_pTreeModel, m_nRowDeletedSignalId);
+ g_signal_handler_block(m_pTreeModel, m_nRowInsertedSignalId);
+
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+
+ g_signal_handler_unblock(m_pTreeModel, m_nRowDeletedSignalId);
+ g_signal_handler_unblock(m_pTreeModel, m_nRowInsertedSignalId);
+
+ g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
+ g_signal_handler_unblock(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
+ }
+
+ virtual void connect_popup_menu(const Link<const CommandEvent&, bool>& rLink) override
+ {
+ ensureButtonPressSignal();
+ weld::TreeView::connect_popup_menu(rLink);
+ }
+
+ virtual bool get_dest_row_at_pos(const Point &rPos, weld::TreeIter* pResult, bool bDnDMode) override
+ {
+ if (rPos.X() < 0 || rPos.Y() < 0)
+ {
+ // short-circuit to avoid "gtk_tree_view_get_dest_row_at_pos: assertion 'drag_x >= 0'" g_assert
+ return false;
+ }
+
+ const bool bAsTree = gtk_tree_view_get_enable_tree_lines(m_pTreeView);
+
+ // to keep it simple we'll default to always drop before the current row
+ // except for the special edge cases
+ GtkTreeViewDropPosition pos = bAsTree ? GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : GTK_TREE_VIEW_DROP_BEFORE;
+
+ // unhighlight current highlighted row
+ gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, pos);
+
+ if (m_bWorkAroundBadDragRegion)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_unset_state_flags(GTK_WIDGET(m_pTreeView), GTK_STATE_FLAG_DROP_ACTIVE);
+#else
+ gtk_drag_unhighlight(GTK_WIDGET(m_pTreeView));
+#endif
+ }
+
+ GtkTreePath *path = nullptr;
+ GtkTreeViewDropPosition gtkpos = bAsTree ? GTK_TREE_VIEW_DROP_INTO_OR_BEFORE : GTK_TREE_VIEW_DROP_BEFORE;
+ bool ret = gtk_tree_view_get_dest_row_at_pos(m_pTreeView, rPos.X(), rPos.Y(),
+ &path, &gtkpos);
+
+ // find the last entry in the model for comparison
+ GtkTreePath *lastpath = get_path_of_last_entry(m_pTreeModel);
+
+ if (!ret)
+ {
+ // empty space, draw an indicator at the last entry
+ assert(!path);
+ path = gtk_tree_path_copy(lastpath);
+ pos = GTK_TREE_VIEW_DROP_AFTER;
+ }
+ else if (bDnDMode && gtk_tree_path_compare(path, lastpath) == 0)
+ {
+ // if we're on the last entry, see if gtk thinks
+ // the drop should be before or after it, and if
+ // its after, treat it like a drop into empty
+ // space, i.e. append it
+ if (gtkpos == GTK_TREE_VIEW_DROP_AFTER ||
+ gtkpos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
+ {
+ ret = false;
+ pos = bAsTree ? gtkpos : GTK_TREE_VIEW_DROP_AFTER;
+ }
+ }
+
+ if (ret && pResult)
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(*pResult);
+ gtk_tree_model_get_iter(m_pTreeModel, &rGtkIter.iter, path);
+ }
+
+ if (m_bInDrag && bDnDMode)
+ {
+ // highlight the row
+ gtk_tree_view_set_drag_dest_row(m_pTreeView, path, pos);
+ }
+
+ assert(path);
+ gtk_tree_path_free(path);
+ gtk_tree_path_free(lastpath);
+
+ // auto scroll if we're close to the edges
+ GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
+ double fStep = gtk_adjustment_get_step_increment(pVAdjustment);
+ if (rPos.Y() < fStep)
+ {
+ double fValue = gtk_adjustment_get_value(pVAdjustment) - fStep;
+ if (fValue < 0)
+ fValue = 0.0;
+ gtk_adjustment_set_value(pVAdjustment, fValue);
+ }
+ else
+ {
+ GdkRectangle aRect;
+ gtk_tree_view_get_visible_rect(m_pTreeView, &aRect);
+ if (rPos.Y() > aRect.height - fStep)
+ {
+ double fValue = gtk_adjustment_get_value(pVAdjustment) + fStep;
+ double fMax = gtk_adjustment_get_upper(pVAdjustment);
+ if (fValue > fMax)
+ fValue = fMax;
+ gtk_adjustment_set_value(pVAdjustment, fValue);
+ }
+ }
+
+ return ret;
+ }
+
+ virtual void unset_drag_dest_row() override
+ {
+ gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, GTK_TREE_VIEW_DROP_BEFORE);
+ }
+
+ virtual tools::Rectangle get_row_area(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreePath* pPath = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ tools::Rectangle aRet = ::get_row_area(m_pTreeView, m_pColumns, pPath);
+ gtk_tree_path_free(pPath);
+ return aRet;
+ }
+
+ virtual void start_editing(const weld::TreeIter& rIter) override
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, m_nTextView));
+ assert(pColumn && "wrong column");
+
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreePath* path = gtk_tree_model_get_path(m_pTreeModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+
+ // allow editing of cells which are not usually editable, so we can have double click
+ // do its usual row-activate but if we explicitly want to edit (remote files dialog)
+ // we can still do that
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
+ {
+ gboolean is_editable(false);
+ g_object_get(pCellRenderer, "editable", &is_editable, nullptr);
+ if (!is_editable)
+ {
+ g_object_set(pCellRenderer, "editable", true, "editable-set", true, nullptr);
+ g_object_set_data(G_OBJECT(pCellRenderer), "g-lo-RestoreNonEditable", reinterpret_cast<gpointer>(true));
+ break;
+ }
+ }
+ }
+ g_list_free(pRenderers);
+
+ gtk_tree_view_scroll_to_cell(m_pTreeView, path, pColumn, false, 0, 0);
+ gtk_tree_view_set_cursor(m_pTreeView, path, pColumn, true);
+
+ gtk_tree_path_free(path);
+ }
+
+ virtual void end_editing() override
+ {
+ GtkTreeViewColumn *focus_column = nullptr;
+ gtk_tree_view_get_cursor(m_pTreeView, nullptr, &focus_column);
+ if (focus_column)
+ gtk_cell_area_stop_editing(gtk_cell_layout_get_area(GTK_CELL_LAYOUT(focus_column)), true);
+ }
+
+ virtual TreeView* get_drag_source() const override
+ {
+ return g_DragSource;
+ }
+
+ virtual bool do_signal_drag_begin(bool& rUnsetDragIcon) override
+ {
+ if (m_aDragBeginHdl.Call(rUnsetDragIcon))
+ return true;
+ g_DragSource = this;
+ return false;
+ }
+
+ virtual void do_signal_drag_end() override
+ {
+ g_DragSource = nullptr;
+ }
+
+ // Under gtk 3.24.8 dragging into the TreeView is not highlighting
+ // entire TreeView widget, just the rectangle which has no entries
+ // in it, so as a workaround highlight the parent container
+ // on drag start, and undo it on drag end, and trigger removal
+ // of the treeview's highlight effort
+ virtual void drag_started() override
+ {
+ m_bInDrag = true;
+ GtkWidget* pWidget = GTK_WIDGET(m_pTreeView);
+ GtkWidget* pParent = gtk_widget_get_parent(pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_unset_state_flags(pWidget, GTK_STATE_FLAG_DROP_ACTIVE);
+ gtk_widget_set_state_flags(pParent, GTK_STATE_FLAG_DROP_ACTIVE, false);
+#else
+ gtk_drag_unhighlight(pWidget);
+ gtk_drag_highlight(pParent);
+#endif
+ m_bWorkAroundBadDragRegion = true;
+ }
+ }
+
+ virtual void drag_ended() override
+ {
+ m_bInDrag = false;
+ if (m_bWorkAroundBadDragRegion)
+ {
+ GtkWidget* pWidget = GTK_WIDGET(m_pTreeView);
+ GtkWidget* pParent = gtk_widget_get_parent(pWidget);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_unset_state_flags(pParent, GTK_STATE_FLAG_DROP_ACTIVE);
+#else
+ gtk_drag_unhighlight(pParent);
+#endif
+ m_bWorkAroundBadDragRegion = false;
+ }
+ // unhighlight the row
+ gtk_tree_view_set_drag_dest_row(m_pTreeView, nullptr, GTK_TREE_VIEW_DROP_BEFORE);
+ }
+
+ virtual int vadjustment_get_value() const override
+ {
+ if (m_nPendingVAdjustment != -1)
+ return m_nPendingVAdjustment;
+ return gtk_adjustment_get_value(m_pVAdjustment);
+ }
+
+ virtual void vadjustment_set_value(int value) override
+ {
+ disable_notify_events();
+
+ /* This rube goldberg device is to remove flicker from setting the
+ scroll position of a GtkTreeView directly after clearing it and
+ filling it. As a specific example the writer navigator with ~100
+ tables, scroll to the end, right click on an entry near the end
+ and rename it, the tree is cleared and refilled and an attempt
+ made to set the scroll position of the freshly refilled tree to
+ the same point as before the clear.
+ */
+
+ // This forces the tree to recalculate now its preferred size
+ // after being cleared
+ GtkRequisition size;
+ gtk_widget_get_preferred_size(GTK_WIDGET(m_pTreeView), nullptr, &size);
+
+ m_nPendingVAdjustment = value;
+
+ // The value set here just has to be different to the final value
+ // set later so that isn't a no-op
+ gtk_adjustment_set_value(m_pVAdjustment, value - 0.0001);
+
+ // This will set the desired m_nPendingVAdjustment value right
+ // before the tree gets drawn
+ gtk_widget_add_tick_callback(GTK_WIDGET(m_pTreeView), setAdjustmentCallback, this, nullptr);
+
+ enable_notify_events();
+ }
+
+ void call_signal_custom_render(VirtualDevice& rOutput, const tools::Rectangle& rRect, bool bSelected, const OUString& rId)
+ {
+ signal_custom_render(rOutput, rRect, bSelected, rId);
+ }
+
+ Size call_signal_custom_get_size(VirtualDevice& rOutput, const OUString& rId)
+ {
+ return signal_custom_get_size(rOutput, rId);
+ }
+
+ virtual void set_show_expanders(bool bShow) override
+ {
+ gtk_tree_view_set_show_expanders(m_pTreeView, bShow);
+ }
+
+ virtual bool changed_by_hover() const override
+ {
+ return m_bChangedByMouse;
+ }
+
+ virtual ~GtkInstanceTreeView() override
+ {
+ if (m_pChangeEvent)
+ Application::RemoveUserEvent(m_pChangeEvent);
+ if (m_nQueryTooltipSignalId)
+ g_signal_handler_disconnect(m_pTreeView, m_nQueryTooltipSignalId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pTreeView, m_nKeyPressSignalId);
+ g_signal_handler_disconnect(m_pTreeView, m_nPopupMenuSignalId);
+#endif
+ g_signal_handler_disconnect(m_pTreeModel, m_nRowDeletedSignalId);
+ g_signal_handler_disconnect(m_pTreeModel, m_nRowInsertedSignalId);
+
+ if (m_nVAdjustmentChangedSignalId)
+ {
+ GtkAdjustment* pVAdjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m_pTreeView));
+ g_signal_handler_disconnect(pVAdjustment, m_nVAdjustmentChangedSignalId);
+ }
+
+ g_signal_handler_disconnect(m_pTreeView, m_nTestCollapseRowSignalId);
+ g_signal_handler_disconnect(m_pTreeView, m_nTestExpandRowSignalId);
+ g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
+ g_signal_handler_disconnect(gtk_tree_view_get_selection(m_pTreeView), m_nChangedSignalId);
+
+ GValue value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_POINTER);
+ g_value_set_pointer(&value, static_cast<gpointer>(nullptr));
+
+ for (GList* pEntry = g_list_last(m_pColumns); pEntry; pEntry = g_list_previous(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ g_signal_handler_disconnect(pColumn, m_aColumnSignalIds.back());
+ m_aColumnSignalIds.pop_back();
+
+ // unset "instance" to avoid dangling "instance" points in any CustomCellRenderers
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ if (!CUSTOM_IS_CELL_RENDERER(pCellRenderer))
+ continue;
+ g_object_set_property(G_OBJECT(pCellRenderer), "instance", &value);
+ }
+ g_list_free(pRenderers);
+ }
+ g_list_free(m_pColumns);
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(GtkInstanceTreeView, async_signal_changed, void*, void)
+{
+ m_pChangeEvent = nullptr;
+ signal_changed();
+ m_bChangedByMouse = false;
+}
+
+IMPL_LINK_NOARG(GtkInstanceTreeView, async_stop_cell_editing, void*, void)
+{
+ end_editing();
+}
+
+namespace {
+
+class GtkInstanceIconView : public GtkInstanceWidget, public virtual weld::IconView
+{
+private:
+ GtkIconView* m_pIconView;
+ GtkTreeStore* m_pTreeStore;
+ gint m_nTextCol;
+ gint m_nImageCol;
+ gint m_nIdCol;
+ gulong m_nSelectionChangedSignalId;
+ gulong m_nItemActivatedSignalId;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nPopupMenu;
+#endif
+ gulong m_nQueryTooltipSignalId = 0;
+ ImplSVEvent* m_pSelectionChangeEvent;
+
+ DECL_LINK(async_signal_selection_changed, void*, void);
+
+ bool signal_command(const CommandEvent& rCEvt)
+ {
+ return m_aCommandHdl.Call(rCEvt);
+ }
+
+ virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
+ {
+ return signal_command(rCEvt);
+ }
+
+ void launch_signal_selection_changed()
+ {
+ //tdf#117991 selection change is sent before the focus change, and focus change
+ //is what will cause a spinbutton that currently has the focus to set its contents
+ //as the spin button value. So any LibreOffice callbacks on
+ //signal-change would happen before the spinbutton value-change occurs.
+ //To avoid this, send the signal-change to LibreOffice to occur after focus-change
+ //has been processed
+ if (m_pSelectionChangeEvent)
+ Application::RemoveUserEvent(m_pSelectionChangeEvent);
+ m_pSelectionChangeEvent = Application::PostUserEvent(LINK(this, GtkInstanceIconView, async_signal_selection_changed));
+ }
+
+ static void signalSelectionChanged(GtkIconView*, gpointer widget)
+ {
+ GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
+ pThis->launch_signal_selection_changed();
+ }
+
+ void handle_item_activated()
+ {
+ if (signal_item_activated())
+ return;
+ }
+
+ static void signalItemActivated(GtkIconView*, GtkTreePath*, gpointer widget)
+ {
+ GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->handle_item_activated();
+ }
+
+ static gboolean signalQueryTooltip(GtkWidget* /*pGtkWidget*/, gint x, gint y,
+ gboolean keyboard_tip, GtkTooltip* tooltip,
+ gpointer widget)
+ {
+ GtkInstanceIconView* pThis = static_cast<GtkInstanceIconView*>(widget);
+ GtkTreeIter iter;
+ GtkIconView* pIconView = pThis->m_pIconView;
+ GtkTreeModel* pModel = gtk_icon_view_get_model(pIconView);
+ GtkTreePath* pPath = nullptr;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (!gtk_icon_view_get_tooltip_context(pIconView, x, y, keyboard_tip, &pModel, &pPath, &iter))
+ return false;
+#else
+ if (!gtk_icon_view_get_tooltip_context(pIconView, &x, &y, keyboard_tip, &pModel, &pPath, &iter))
+ return false;
+#endif
+ OUString aTooltip = pThis->signal_query_tooltip(GtkInstanceTreeIter(iter));
+ if (!aTooltip.isEmpty())
+ {
+ gtk_tooltip_set_text(tooltip, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
+ gtk_icon_view_set_tooltip_item(pIconView, tooltip, pPath);
+ }
+ gtk_tree_path_free(pPath);
+ return !aTooltip.isEmpty();
+ }
+
+ void insert_item(GtkTreeIter& iter, int pos, const OUString* pId, const OUString* pText, const OUString* pIconName)
+ {
+ // m_nTextCol may be -1, so pass it last, to not terminate the sequence before the Id value
+ gtk_tree_store_insert_with_values(m_pTreeStore, &iter, nullptr, pos,
+ m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
+ m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
+ -1);
+ if (pIconName)
+ {
+ GdkPixbuf* pixbuf = getPixbuf(*pIconName);
+ gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, pixbuf, -1);
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+ }
+
+ void insert_item(GtkTreeIter& iter, int pos, const OUString* pId, const OUString* pText, const VirtualDevice* pIcon)
+ {
+ // m_nTextCol may be -1, so pass it last, to not terminate the sequence before the Id value
+ gtk_tree_store_insert_with_values(m_pTreeStore, &iter, nullptr, pos,
+ m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, RTL_TEXTENCODING_UTF8).getStr(),
+ m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
+ -1);
+ if (pIcon)
+ {
+ GdkPixbuf* pixbuf = getPixbuf(*pIcon);
+ gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, pixbuf, -1);
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+ }
+
+ OUString get(const GtkTreeIter& iter, int col) const
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ gchar* pStr;
+ gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &pStr, -1);
+ OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pStr);
+ return sRet;
+ }
+
+ bool get_selected_iterator(GtkTreeIter* pIter) const
+ {
+ assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
+ bool bRet = false;
+ {
+ GtkTreeModel* pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
+ for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
+ {
+ if (pIter)
+ {
+ GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
+ gtk_tree_model_get_iter(pModel, pIter, path);
+ }
+ bRet = true;
+ break;
+ }
+ g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
+ }
+ return bRet;
+ }
+
+public:
+ GtkInstanceIconView(GtkIconView* pIconView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pIconView), pBuilder, bTakeOwnership)
+ , m_pIconView(pIconView)
+ , m_pTreeStore(GTK_TREE_STORE(gtk_icon_view_get_model(m_pIconView)))
+ , m_nTextCol(gtk_icon_view_get_text_column(m_pIconView)) // May be -1
+ , m_nImageCol(gtk_icon_view_get_pixbuf_column(m_pIconView))
+ , m_nSelectionChangedSignalId(g_signal_connect(pIconView, "selection-changed",
+ G_CALLBACK(signalSelectionChanged), this))
+ , m_nItemActivatedSignalId(g_signal_connect(pIconView, "item-activated", G_CALLBACK(signalItemActivated), this))
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_nPopupMenu(g_signal_connect(pIconView, "popup-menu", G_CALLBACK(signalPopupMenu), this))
+#endif
+ , m_pSelectionChangeEvent(nullptr)
+ {
+ m_nIdCol = std::max(m_nTextCol, m_nImageCol) + 1;
+ }
+
+ virtual int get_item_width() const override
+ {
+ return gtk_icon_view_get_item_width(m_pIconView);
+ }
+
+ virtual void set_item_width(int width) override
+ {
+ gtk_icon_view_set_item_width(m_pIconView, width);
+ }
+
+ virtual void insert(int pos, const OUString* pText, const OUString* pId, const OUString* pIconName, weld::TreeIter* pRet) override
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ insert_item(iter, pos, pId, pText, pIconName);
+ if (pRet)
+ {
+ GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
+ pGtkRetIter->iter = iter;
+ }
+ enable_notify_events();
+ }
+
+ virtual void insert(int pos, const OUString* pText, const OUString* pId, const VirtualDevice* pIcon, weld::TreeIter* pRet) override
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ insert_item(iter, pos, pId, pText, pIcon);
+ if (pRet)
+ {
+ GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
+ pGtkRetIter->iter = iter;
+ }
+ enable_notify_events();
+ }
+
+ virtual void insert_separator(int /* pos */, const OUString* /* pId */) override
+ {
+ // TODO: can't just copy from GtkInstanceTreeView, since there's
+ // no IconView analog for gtk_tree_view_get_row_separator_func
+ }
+
+ virtual void connect_query_tooltip(const Link<const weld::TreeIter&, OUString>& rLink) override
+ {
+ weld::IconView::connect_query_tooltip(rLink);
+ m_nQueryTooltipSignalId = g_signal_connect(m_pIconView, "query-tooltip", G_CALLBACK(signalQueryTooltip), this);
+ gtk_widget_set_has_tooltip(GTK_WIDGET(m_pIconView), true);
+ }
+
+ virtual OUString get_selected_id() const override
+ {
+ assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
+ GtkTreeIter iter;
+ if (get_selected_iterator(&iter))
+ return get(iter, m_nIdCol);
+ return OUString();
+ }
+
+ virtual void clear() override
+ {
+ disable_notify_events();
+ gtk_tree_store_clear(m_pTreeStore);
+ enable_notify_events();
+ }
+
+ virtual void freeze() override
+ {
+ disable_notify_events();
+ bool bIsFirstFreeze = IsFirstFreeze();
+ GtkInstanceWidget::freeze();
+ if (bIsFirstFreeze)
+ {
+ g_object_ref(m_pTreeStore);
+ gtk_icon_view_set_model(m_pIconView, nullptr);
+ g_object_freeze_notify(G_OBJECT(m_pTreeStore));
+ }
+ enable_notify_events();
+ }
+
+ virtual void thaw() override
+ {
+ disable_notify_events();
+ if (IsLastThaw())
+ {
+ g_object_thaw_notify(G_OBJECT(m_pTreeStore));
+ gtk_icon_view_set_model(m_pIconView, GTK_TREE_MODEL(m_pTreeStore));
+ g_object_unref(m_pTreeStore);
+ }
+ GtkInstanceWidget::thaw();
+ enable_notify_events();
+ }
+
+ virtual Size get_size_request() const override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ {
+ return Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
+ gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
+ }
+ int nWidth, nHeight;
+ gtk_widget_get_size_request(m_pWidget, &nWidth, &nHeight);
+ return Size(nWidth, nHeight);
+ }
+
+ virtual Size get_preferred_size() const override
+ {
+ Size aRet(-1, -1);
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ {
+ aRet = Size(gtk_scrolled_window_get_min_content_width(GTK_SCROLLED_WINDOW(pParent)),
+ gtk_scrolled_window_get_min_content_height(GTK_SCROLLED_WINDOW(pParent)));
+ }
+ GtkRequisition size;
+ gtk_widget_get_preferred_size(m_pWidget, nullptr, &size);
+ if (aRet.Width() == -1)
+ aRet.setWidth(size.width);
+ if (aRet.Height() == -1)
+ aRet.setHeight(size.height);
+ return aRet;
+ }
+
+ virtual void show() override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ gtk_widget_show(pParent);
+ gtk_widget_show(m_pWidget);
+ }
+
+ virtual void hide() override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ gtk_widget_hide(pParent);
+ gtk_widget_hide(m_pWidget);
+ }
+
+ virtual OUString get_selected_text() const override
+ {
+ assert(gtk_icon_view_get_model(m_pIconView) && "don't request selection when frozen");
+ GtkTreeIter iter;
+ if (get_selected_iterator(&iter))
+ return get(iter, m_nTextCol);
+ return OUString();
+ }
+
+ virtual int count_selected_items() const override
+ {
+ GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
+ int nRet = g_list_length(pList);
+ g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
+ return nRet;
+ }
+
+ virtual void select(int pos) override
+ {
+ assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
+ disable_notify_events();
+ if (pos == -1 || (pos == 0 && n_children() == 0))
+ {
+ gtk_icon_view_unselect_all(m_pIconView);
+ }
+ else
+ {
+ GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
+ gtk_icon_view_select_path(m_pIconView, path);
+ gtk_icon_view_scroll_to_path(m_pIconView, path, false, 0, 0);
+ gtk_tree_path_free(path);
+ }
+ enable_notify_events();
+ }
+
+ virtual void unselect(int pos) override
+ {
+ assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
+ disable_notify_events();
+ if (pos == -1 || (pos == 0 && n_children() == 0))
+ {
+ gtk_icon_view_select_all(m_pIconView);
+ }
+ else
+ {
+ GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
+ gtk_icon_view_select_path(m_pIconView, path);
+ gtk_tree_path_free(path);
+ }
+ enable_notify_events();
+ }
+
+ virtual bool get_selected(weld::TreeIter* pIter) const override
+ {
+ GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
+ return get_selected_iterator(pGtkIter ? &pGtkIter->iter : nullptr);
+ }
+
+ virtual bool get_cursor(weld::TreeIter* pIter) const override
+ {
+ GtkInstanceTreeIter* pGtkIter = static_cast<GtkInstanceTreeIter*>(pIter);
+ GtkTreePath* path;
+ gtk_icon_view_get_cursor(m_pIconView, &path, nullptr);
+ if (pGtkIter && path)
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ gtk_tree_model_get_iter(pModel, &pGtkIter->iter, path);
+ }
+ return path != nullptr;
+ }
+
+ virtual void set_cursor(const weld::TreeIter& rIter) override
+ {
+ disable_notify_events();
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ gtk_icon_view_set_cursor(m_pIconView, path, nullptr, false);
+ gtk_tree_path_free(path);
+ enable_notify_events();
+ }
+
+ virtual bool get_iter_first(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ return gtk_tree_model_get_iter_first(pModel, &rGtkIter.iter);
+ }
+
+ virtual void scroll_to_item(const weld::TreeIter& rIter) override
+ {
+ assert(gtk_icon_view_get_model(m_pIconView) && "don't select when frozen, select after thaw. Note selection doesn't survive a freeze");
+ disable_notify_events();
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter));
+ gtk_icon_view_scroll_to_path(m_pIconView, path, false, 0, 0);
+ gtk_tree_path_free(path);
+ enable_notify_events();
+ }
+
+ virtual std::unique_ptr<weld::TreeIter> make_iterator(const weld::TreeIter* pOrig) const override
+ {
+ return std::unique_ptr<weld::TreeIter>(new GtkInstanceTreeIter(static_cast<const GtkInstanceTreeIter*>(pOrig)));
+ }
+
+ virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
+ {
+ GtkInstanceTreeIter aGtkIter(nullptr);
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GList* pList = gtk_icon_view_get_selected_items(m_pIconView);
+ for (GList* pItem = g_list_first(pList); pItem; pItem = g_list_next(pItem))
+ {
+ GtkTreePath* path = static_cast<GtkTreePath*>(pItem->data);
+ gtk_tree_model_get_iter(pModel, &aGtkIter.iter, path);
+ if (func(aGtkIter))
+ break;
+ }
+ g_list_free_full(pList, reinterpret_cast<GDestroyNotify>(gtk_tree_path_free));
+ }
+
+ virtual int n_children() const override
+ {
+ return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_pTreeStore), nullptr);
+ }
+
+ virtual OUString get_id(const weld::TreeIter& rIter) const override
+ {
+ const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter);
+ return get(rGtkIter.iter, m_nIdCol);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pIconView, m_nSelectionChangedSignalId);
+ g_signal_handler_block(m_pIconView, m_nItemActivatedSignalId);
+
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+
+ g_signal_handler_unblock(m_pIconView, m_nItemActivatedSignalId);
+ g_signal_handler_unblock(m_pIconView, m_nSelectionChangedSignalId);
+ }
+
+ virtual ~GtkInstanceIconView() override
+ {
+ if (m_pSelectionChangeEvent)
+ Application::RemoveUserEvent(m_pSelectionChangeEvent);
+
+ if (m_nQueryTooltipSignalId)
+ g_signal_handler_disconnect(m_pIconView, m_nQueryTooltipSignalId);
+
+ g_signal_handler_disconnect(m_pIconView, m_nItemActivatedSignalId);
+ g_signal_handler_disconnect(m_pIconView, m_nSelectionChangedSignalId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pIconView, m_nPopupMenu);
+#endif
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(GtkInstanceIconView, async_signal_selection_changed, void*, void)
+{
+ m_pSelectionChangeEvent = nullptr;
+ signal_selection_changed();
+}
+
+namespace {
+
+class GtkInstanceSpinButton : public GtkInstanceEditable, public virtual weld::SpinButton
+{
+private:
+ GtkSpinButton* m_pButton;
+ gulong m_nValueChangedSignalId;
+ gulong m_nOutputSignalId;
+ gulong m_nInputSignalId;
+ bool m_bFormatting;
+ bool m_bBlockOutput;
+ bool m_bBlank;
+
+ static void signalValueChanged(GtkSpinButton*, gpointer widget)
+ {
+ GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->m_bBlank = false;
+ pThis->signal_value_changed();
+ }
+
+ bool guarded_signal_output()
+ {
+ if (m_bBlockOutput)
+ return true;
+ m_bFormatting = true;
+ bool bRet = signal_output();
+ m_bFormatting = false;
+ return bRet;
+ }
+
+ static gboolean signalOutput(GtkSpinButton*, gpointer widget)
+ {
+ GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->guarded_signal_output();
+ }
+
+ static gint signalInput(GtkSpinButton*, gdouble* new_value, gpointer widget)
+ {
+ GtkInstanceSpinButton* pThis = static_cast<GtkInstanceSpinButton*>(widget);
+ SolarMutexGuard aGuard;
+ int result;
+ TriState eHandled = pThis->signal_input(&result);
+ if (eHandled == TRISTATE_INDET)
+ return 0;
+ if (eHandled == TRISTATE_TRUE)
+ {
+ *new_value = pThis->toGtk(result);
+ return 1;
+ }
+ return GTK_INPUT_ERROR;
+ }
+
+ virtual void signal_activate() override
+ {
+ gtk_spin_button_update(m_pButton);
+ GtkInstanceEditable::signal_activate();
+ }
+
+ double toGtk(sal_Int64 nValue) const
+ {
+ return static_cast<double>(nValue) / Power10(get_digits());
+ }
+
+ sal_Int64 fromGtk(double fValue) const
+ {
+ return FRound(fValue * Power10(get_digits()));
+ }
+
+public:
+ GtkInstanceSpinButton(GtkSpinButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceEditable(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
+ , m_pButton(pButton)
+ , m_nValueChangedSignalId(g_signal_connect(pButton, "value-changed", G_CALLBACK(signalValueChanged), this))
+ , m_nOutputSignalId(g_signal_connect(pButton, "output", G_CALLBACK(signalOutput), this))
+ , m_nInputSignalId(g_signal_connect(pButton, "input", G_CALLBACK(signalInput), this))
+ , m_bFormatting(false)
+ , m_bBlockOutput(false)
+ , m_bBlank(false)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_text_set_activates_default(GTK_TEXT(m_pDelegate), true);
+#endif
+ }
+
+ virtual sal_Int64 get_value() const override
+ {
+ return fromGtk(gtk_spin_button_get_value(m_pButton));
+ }
+
+ virtual void set_value(sal_Int64 value) override
+ {
+ disable_notify_events();
+ m_bBlank = false;
+ gtk_spin_button_set_value(m_pButton, toGtk(value));
+ enable_notify_events();
+ }
+
+ virtual void set_text(const OUString& rText) override
+ {
+ disable_notify_events();
+ // tdf#122786 if we're just formatting a value, then we're done,
+ // however if set_text has been called directly we want to update our
+ // value from this new text, but don't want to reformat with that value
+ if (!m_bFormatting)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+#else
+ gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+#endif
+
+ m_bBlockOutput = true;
+ gtk_spin_button_update(m_pButton);
+ m_bBlank = rText.isEmpty();
+ m_bBlockOutput = false;
+ }
+ else
+ {
+ bool bKeepBlank = m_bBlank && get_value() == 0;
+ if (!bKeepBlank)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+#else
+ gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+#endif
+ m_bBlank = false;
+ }
+ }
+ enable_notify_events();
+ }
+
+ virtual void set_range(sal_Int64 min, sal_Int64 max) override
+ {
+ disable_notify_events();
+ gtk_spin_button_set_range(m_pButton, toGtk(min), toGtk(max));
+ enable_notify_events();
+ }
+
+ virtual void get_range(sal_Int64& min, sal_Int64& max) const override
+ {
+ double gtkmin, gtkmax;
+ gtk_spin_button_get_range(m_pButton, &gtkmin, &gtkmax);
+ min = fromGtk(gtkmin);
+ max = fromGtk(gtkmax);
+ }
+
+ virtual void set_increments(int step, int page) override
+ {
+ disable_notify_events();
+ gtk_spin_button_set_increments(m_pButton, toGtk(step), toGtk(page));
+ enable_notify_events();
+ }
+
+ virtual void get_increments(int& step, int& page) const override
+ {
+ double gtkstep, gtkpage;
+ gtk_spin_button_get_increments(m_pButton, &gtkstep, &gtkpage);
+ step = fromGtk(gtkstep);
+ page = fromGtk(gtkpage);
+ }
+
+ virtual void set_digits(unsigned int digits) override
+ {
+ disable_notify_events();
+ gtk_spin_button_set_digits(m_pButton, digits);
+ enable_notify_events();
+ }
+
+ virtual unsigned int get_digits() const override
+ {
+ return gtk_spin_button_get_digits(m_pButton);
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_aCustomFont.use_custom_font(&rFont, u"spinbutton");
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pButton, m_nValueChangedSignalId);
+ GtkInstanceEditable::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceEditable::enable_notify_events();
+ g_signal_handler_unblock(m_pButton, m_nValueChangedSignalId);
+ }
+
+ virtual ~GtkInstanceSpinButton() override
+ {
+ g_signal_handler_disconnect(m_pButton, m_nInputSignalId);
+ g_signal_handler_disconnect(m_pButton, m_nOutputSignalId);
+ g_signal_handler_disconnect(m_pButton, m_nValueChangedSignalId);
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstanceFormattedSpinButton : public GtkInstanceEditable, public virtual weld::FormattedSpinButton
+{
+private:
+ GtkSpinButton* m_pButton;
+ std::unique_ptr<weld::EntryFormatter> m_xOwnFormatter;
+ weld::EntryFormatter* m_pFormatter;
+ gulong m_nValueChangedSignalId;
+ gulong m_nOutputSignalId;
+ gulong m_nInputSignalId;
+ bool m_bEmptyField;
+ bool m_bSyncingValue;
+ double m_dValueWhenEmpty;
+
+ bool signal_output()
+ {
+ double fValue = gtk_spin_button_get_value(m_pButton);
+ m_bEmptyField &= fValue == m_dValueWhenEmpty;
+ if (!m_bEmptyField)
+ GetFormatter().SetValue(fValue);
+ return true;
+ }
+
+ static gboolean signalOutput(GtkSpinButton*, gpointer widget)
+ {
+ GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_output();
+ }
+
+ gint signal_input(double* value)
+ {
+ Formatter& rFormatter = GetFormatter();
+ rFormatter.Modify();
+ // if the blank-mode is enabled then if the input is empty don't parse
+ // the input but keep the value as it is. store what the value the
+ // blank is associated with and until the value is changed, or the text
+ // is updated from the outside, don't output that value
+ m_bEmptyField = rFormatter.IsEmptyFieldEnabled() && get_text().isEmpty();
+ if (m_bEmptyField)
+ {
+ m_dValueWhenEmpty = gtk_spin_button_get_value(m_pButton);
+ *value = m_dValueWhenEmpty;
+ }
+ else
+ *value = rFormatter.GetValue();
+ return 1;
+ }
+
+ static gint signalInput(GtkSpinButton*, gdouble* new_value, gpointer widget)
+ {
+ GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_input(new_value);
+ }
+
+ static void signalValueChanged(GtkSpinButton*, gpointer widget)
+ {
+ GtkInstanceFormattedSpinButton* pThis = static_cast<GtkInstanceFormattedSpinButton*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_value_changed();
+ }
+
+public:
+ GtkInstanceFormattedSpinButton(GtkSpinButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceEditable(GTK_WIDGET(pButton), pBuilder, bTakeOwnership)
+ , m_pButton(pButton)
+ , m_pFormatter(nullptr)
+ , m_nValueChangedSignalId(g_signal_connect(pButton, "value-changed", G_CALLBACK(signalValueChanged), this))
+ , m_nOutputSignalId(g_signal_connect(pButton, "output", G_CALLBACK(signalOutput), this))
+ , m_nInputSignalId(g_signal_connect(pButton, "input", G_CALLBACK(signalInput), this))
+ , m_bEmptyField(false)
+ , m_bSyncingValue(false)
+ , m_dValueWhenEmpty(0.0)
+ {
+ }
+
+ virtual void set_text(const OUString& rText) override
+ {
+ GtkInstanceEditable::set_text(rText);
+ Formatter& rFormatter = GetFormatter();
+ m_bEmptyField = rFormatter.IsEmptyFieldEnabled() && rText.isEmpty();
+ if (m_bEmptyField)
+ m_dValueWhenEmpty = gtk_spin_button_get_value(m_pButton);
+ }
+
+ virtual void connect_changed(const Link<weld::Entry&, void>& rLink) override
+ {
+ if (!m_pFormatter) // once a formatter is set, it takes over "changed"
+ {
+ GtkInstanceEditable::connect_changed(rLink);
+ return;
+ }
+ m_pFormatter->connect_changed(rLink);
+ }
+
+ virtual void connect_focus_out(const Link<weld::Widget&, void>& rLink) override
+ {
+ if (!m_pFormatter) // once a formatter is set, it takes over "focus-out"
+ {
+ GtkInstanceEditable::connect_focus_out(rLink);
+ return;
+ }
+ m_pFormatter->connect_focus_out(rLink);
+ }
+
+ virtual void SetFormatter(weld::EntryFormatter* pFormatter) override
+ {
+ m_xOwnFormatter.reset();
+ m_pFormatter = pFormatter;
+ sync_range_from_formatter();
+ sync_value_from_formatter();
+ sync_increments_from_formatter();
+ }
+
+ virtual weld::EntryFormatter& GetFormatter() override
+ {
+ if (!m_pFormatter)
+ {
+ auto aFocusOutHdl = m_aFocusOutHdl;
+ m_aFocusOutHdl = Link<weld::Widget&, void>();
+ auto aChangeHdl = m_aChangeHdl;
+ m_aChangeHdl = Link<weld::Entry&, void>();
+
+ double fValue = gtk_spin_button_get_value(m_pButton);
+ double fMin, fMax;
+ gtk_spin_button_get_range(m_pButton, &fMin, &fMax);
+ double fStep;
+ gtk_spin_button_get_increments(m_pButton, &fStep, nullptr);
+ m_xOwnFormatter.reset(new weld::EntryFormatter(*this));
+ m_xOwnFormatter->SetMinValue(fMin);
+ m_xOwnFormatter->SetMaxValue(fMax);
+ m_xOwnFormatter->SetSpinSize(fStep);
+ m_xOwnFormatter->SetValue(fValue);
+
+ m_xOwnFormatter->connect_focus_out(aFocusOutHdl);
+ m_xOwnFormatter->connect_changed(aChangeHdl);
+
+ m_pFormatter = m_xOwnFormatter.get();
+ }
+ return *m_pFormatter;
+ }
+
+ virtual void sync_value_from_formatter() override
+ {
+ if (!m_pFormatter)
+ return;
+ // tdf#135317 avoid reenterence
+ if (m_bSyncingValue)
+ return;
+ m_bSyncingValue = true;
+ disable_notify_events();
+ // tdf#138519 use gtk_adjustment_set_value instead of gtk_spin_button_set_value because the
+ // latter doesn't change the value if the new value is less than an EPSILON diff of 1e-10
+ // from the old value
+ gtk_adjustment_set_value(gtk_spin_button_get_adjustment(m_pButton), m_pFormatter->GetValue());
+ enable_notify_events();
+ m_bSyncingValue = false;
+ }
+
+ virtual void sync_range_from_formatter() override
+ {
+ if (!m_pFormatter)
+ return;
+ disable_notify_events();
+ double fMin = m_pFormatter->HasMinValue() ? m_pFormatter->GetMinValue() : std::numeric_limits<double>::lowest();
+ double fMax = m_pFormatter->HasMaxValue() ? m_pFormatter->GetMaxValue() : std::numeric_limits<double>::max();
+ gtk_spin_button_set_range(m_pButton, fMin, fMax);
+ enable_notify_events();
+ }
+
+ virtual void sync_increments_from_formatter() override
+ {
+ if (!m_pFormatter)
+ return;
+ disable_notify_events();
+ double fSpinSize = m_pFormatter->GetSpinSize();
+ gtk_spin_button_set_increments(m_pButton, fSpinSize, fSpinSize * 10);
+ enable_notify_events();
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_aCustomFont.use_custom_font(&rFont, u"spinbutton");
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pButton, m_nValueChangedSignalId);
+ GtkInstanceEditable::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceEditable::enable_notify_events();
+ g_signal_handler_unblock(m_pButton, m_nValueChangedSignalId);
+ }
+
+ virtual ~GtkInstanceFormattedSpinButton() override
+ {
+ g_signal_handler_disconnect(m_pButton, m_nInputSignalId);
+ g_signal_handler_disconnect(m_pButton, m_nOutputSignalId);
+ g_signal_handler_disconnect(m_pButton, m_nValueChangedSignalId);
+
+ m_pFormatter = nullptr;
+ m_xOwnFormatter.reset();
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstanceLabel : public GtkInstanceWidget, public virtual weld::Label
+{
+private:
+ GtkLabel* m_pLabel;
+
+ void set_text_background_color(const Color& rColor)
+ {
+ guint16 nRed = rColor.GetRed() << 8;
+ guint16 nGreen = rColor.GetGreen() << 8;
+ guint16 nBlue = rColor.GetBlue() << 8;
+
+ PangoAttrType aFilterAttrs[] = {PANGO_ATTR_BACKGROUND, PANGO_ATTR_INVALID};
+
+ PangoAttrList* pOrigList = gtk_label_get_attributes(m_pLabel);
+ PangoAttrList* pAttrs = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
+ PangoAttrList* pRemovedAttrs = pOrigList ? pango_attr_list_filter(pAttrs, filter_pango_attrs, &aFilterAttrs) : nullptr;
+ pango_attr_list_insert(pAttrs, pango_attr_background_new(nRed, nGreen, nBlue));
+ gtk_label_set_attributes(m_pLabel, pAttrs);
+ pango_attr_list_unref(pAttrs);
+ pango_attr_list_unref(pRemovedAttrs);
+ }
+
+ void set_text_foreground_color(const Color& rColor, bool bSetBold)
+ {
+ guint16 nRed = rColor.GetRed() << 8;
+ guint16 nGreen = rColor.GetGreen() << 8;
+ guint16 nBlue = rColor.GetBlue() << 8;
+
+ PangoAttrType aFilterAttrs[] = {PANGO_ATTR_FOREGROUND, PANGO_ATTR_WEIGHT, PANGO_ATTR_INVALID};
+
+ if (!bSetBold)
+ aFilterAttrs[1] = PANGO_ATTR_INVALID;
+
+ PangoAttrList* pOrigList = gtk_label_get_attributes(m_pLabel);
+ PangoAttrList* pAttrs = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
+ PangoAttrList* pRemovedAttrs = pOrigList ? pango_attr_list_filter(pAttrs, filter_pango_attrs, &aFilterAttrs) : nullptr;
+ if (rColor != COL_AUTO)
+ pango_attr_list_insert(pAttrs, pango_attr_foreground_new(nRed, nGreen, nBlue));
+ if (bSetBold)
+ pango_attr_list_insert(pAttrs, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
+ gtk_label_set_attributes(m_pLabel, pAttrs);
+ pango_attr_list_unref(pAttrs);
+ pango_attr_list_unref(pRemovedAttrs);
+ }
+
+public:
+ GtkInstanceLabel(GtkLabel* pLabel, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pLabel), pBuilder, bTakeOwnership)
+ , m_pLabel(pLabel)
+ {
+ }
+
+ virtual void set_label(const OUString& rText) override
+ {
+ ::set_label(m_pLabel, rText);
+ }
+
+ virtual OUString get_label() const override
+ {
+ return ::get_label(m_pLabel);
+ }
+
+ virtual void set_mnemonic_widget(Widget* pTarget) override
+ {
+ assert(!gtk_label_get_selectable(m_pLabel) && "don't use set_mnemonic_widget on selectable labels, for consistency with gen backend");
+ GtkInstanceWidget* pTargetWidget = dynamic_cast<GtkInstanceWidget*>(pTarget);
+ gtk_label_set_mnemonic_widget(m_pLabel, pTargetWidget ? pTargetWidget->getWidget() : nullptr);
+ }
+
+ virtual void set_label_type(weld::LabelType eType) override
+ {
+ switch (eType)
+ {
+ case weld::LabelType::Normal:
+ gtk_label_set_attributes(m_pLabel, nullptr);
+ break;
+ case weld::LabelType::Warning:
+ set_text_background_color(Application::GetSettings().GetStyleSettings().GetWarningColor());
+ break;
+ case weld::LabelType::Error:
+ set_text_background_color(Application::GetSettings().GetStyleSettings().GetHighlightColor());
+ break;
+ case weld::LabelType::Title:
+ set_text_foreground_color(Application::GetSettings().GetStyleSettings().GetLightColor(), true);
+ break;
+ }
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ ::set_font(m_pLabel, rFont);
+ }
+
+ virtual void set_font_color(const Color& rColor) override
+ {
+ set_text_foreground_color(rColor, false);
+ }
+};
+
+}
+
+std::unique_ptr<weld::Label> GtkInstanceFrame::weld_label_widget() const
+{
+ GtkWidget* pLabel = gtk_frame_get_label_widget(m_pFrame);
+ if (!pLabel || !GTK_IS_LABEL(pLabel))
+ return nullptr;
+ return std::make_unique<GtkInstanceLabel>(GTK_LABEL(pLabel), m_pBuilder, false);
+}
+
+namespace {
+
+GdkClipboard* widget_get_clipboard(GtkWidget* pWidget)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return gtk_widget_get_clipboard(pWidget);
+#else
+ return gtk_widget_get_clipboard(pWidget, GDK_SELECTION_CLIPBOARD);
+#endif
+}
+
+class GtkInstanceTextView : public GtkInstanceWidget, public virtual weld::TextView
+{
+private:
+ GtkTextView* m_pTextView;
+ GtkTextBuffer* m_pTextBuffer;
+ GtkAdjustment* m_pVAdjustment;
+ GtkCssProvider* m_pFgCssProvider;
+ WidgetFont m_aCustomFont;
+ int m_nMaxTextLength;
+ gulong m_nChangedSignalId; // we don't disable/enable this one, it's to implement max-length
+ gulong m_nInsertTextSignalId;
+ gulong m_nCursorPosSignalId;
+ gulong m_nHasSelectionSignalId; // we don't disable/enable this one, it's to implement
+ // auto-scroll to cursor on losing selection
+ gulong m_nVAdjustChangedSignalId;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nButtonPressEvent; // we don't disable/enable this one, it's to block mouse
+ // click down from getting to (potential) toplevel
+ // GtkSalFrame parent, which grabs focus away
+
+ static gboolean signalButtonPressEvent(GtkWidget*, GdkEventButton*, gpointer)
+ {
+ // e.g. on clicking on the help TextView in OTableDesignHelpBar the currently displayed text shouldn't disappear
+ return true;
+ }
+#endif
+
+ static void signalChanged(GtkTextBuffer*, gpointer widget)
+ {
+ GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_changed();
+ }
+
+ static void signalInserText(GtkTextBuffer *pBuffer, GtkTextIter *pLocation, gchar* /*pText*/, gint /*nLen*/, gpointer widget)
+ {
+ GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
+ pThis->insert_text(pBuffer, pLocation);
+ }
+
+ void insert_text(GtkTextBuffer *pBuffer, GtkTextIter *pLocation)
+ {
+ if (m_nMaxTextLength)
+ {
+ gint nCount = gtk_text_buffer_get_char_count(pBuffer);
+ if (nCount > m_nMaxTextLength)
+ {
+ GtkTextIter nStart, nEnd;
+ gtk_text_buffer_get_iter_at_offset(m_pTextBuffer, &nStart, m_nMaxTextLength);
+ gtk_text_buffer_get_end_iter(m_pTextBuffer, &nEnd);
+ gtk_text_buffer_delete(m_pTextBuffer, &nStart, &nEnd);
+ gtk_text_iter_assign(pLocation, &nStart);
+ }
+ }
+ }
+
+ static void signalCursorPosition(GtkTextBuffer*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
+ pThis->signal_cursor_position();
+ }
+
+ static void signalHasSelection(GtkTextBuffer*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
+ pThis->signal_has_selection();
+ }
+
+ void signal_has_selection()
+ {
+ /*
+ in the data browser (Data Sources, shift+ctrl+f4), entering a
+ multiline cell selects all, on cursoring to the right, the selection
+ is lost and the cursor is at the end but gtk doesn't auto-scroll to
+ the cursor so if the text needs scrolling to see the cursor it is off
+ screen, another cursor makes gtk auto-scroll as wanted. So on losing
+ selection help gtk out and do the initial scroll ourselves here
+ */
+ if (!gtk_text_buffer_get_has_selection(m_pTextBuffer))
+ {
+ GtkTextMark* pMark = gtk_text_buffer_get_insert(m_pTextBuffer);
+ gtk_text_view_scroll_mark_onscreen(m_pTextView, pMark);
+ }
+ }
+
+ static void signalVAdjustValueChanged(GtkAdjustment*, gpointer widget)
+ {
+ GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_vadjustment_changed();
+ }
+
+public:
+ GtkInstanceTextView(GtkTextView* pTextView, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pTextView), pBuilder, bTakeOwnership)
+ , m_pTextView(pTextView)
+ , m_pTextBuffer(gtk_text_view_get_buffer(pTextView))
+ , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTextView)))
+ , m_pFgCssProvider(nullptr)
+ , m_aCustomFont(m_pWidget)
+ , m_nMaxTextLength(0)
+ , m_nChangedSignalId(g_signal_connect(m_pTextBuffer, "changed", G_CALLBACK(signalChanged), this))
+ , m_nInsertTextSignalId(g_signal_connect_after(m_pTextBuffer, "insert-text", G_CALLBACK(signalInserText), this))
+ , m_nCursorPosSignalId(g_signal_connect(m_pTextBuffer, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
+ , m_nHasSelectionSignalId(g_signal_connect(m_pTextBuffer, "notify::has-selection", G_CALLBACK(signalHasSelection), this))
+ , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustValueChanged), this))
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_nButtonPressEvent(g_signal_connect_after(m_pTextView, "button-press-event", G_CALLBACK(signalButtonPressEvent), this))
+#endif
+ {
+ }
+
+ virtual void set_size_request(int nWidth, int nHeight) override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ 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);
+ return;
+ }
+ gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
+ }
+
+ virtual void set_text(const OUString& rText) override
+ {
+ disable_notify_events();
+ OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gtk_text_buffer_set_text(m_pTextBuffer, sText.getStr(), sText.getLength());
+ enable_notify_events();
+ }
+
+ virtual OUString get_text() const override
+ {
+ GtkTextIter start, end;
+ gtk_text_buffer_get_bounds(m_pTextBuffer, &start, &end);
+ char* pStr = gtk_text_buffer_get_text(m_pTextBuffer, &start, &end, true);
+ OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pStr);
+ return sRet;
+ }
+
+ virtual void replace_selection(const OUString& rText) override
+ {
+ disable_notify_events();
+ gtk_text_buffer_delete_selection(m_pTextBuffer, false, gtk_text_view_get_editable(m_pTextView));
+ OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gtk_text_buffer_insert_at_cursor(m_pTextBuffer, sText.getStr(), sText.getLength());
+ enable_notify_events();
+ }
+
+ virtual bool get_selection_bounds(int& rStartPos, int& rEndPos) override
+ {
+ GtkTextIter start, end;
+ gtk_text_buffer_get_selection_bounds(m_pTextBuffer, &start, &end);
+ rStartPos = gtk_text_iter_get_offset(&start);
+ rEndPos = gtk_text_iter_get_offset(&end);
+ return rStartPos != rEndPos;
+ }
+
+ virtual void select_region(int nStartPos, int nEndPos) override
+ {
+ disable_notify_events();
+ GtkTextIter start, end;
+ gtk_text_buffer_get_iter_at_offset(m_pTextBuffer, &start, nStartPos);
+ gtk_text_buffer_get_iter_at_offset(m_pTextBuffer, &end, nEndPos);
+ gtk_text_buffer_select_range(m_pTextBuffer, &start, &end);
+ GtkTextMark* mark = gtk_text_buffer_create_mark(m_pTextBuffer, "scroll", &end, true);
+ gtk_text_view_scroll_mark_onscreen(m_pTextView, mark);
+ enable_notify_events();
+ }
+
+ virtual void set_editable(bool bEditable) override
+ {
+ gtk_text_view_set_editable(m_pTextView, bEditable);
+ }
+
+ virtual bool get_editable() const override
+ {
+ return gtk_text_view_get_editable(m_pTextView);
+ }
+
+ virtual void set_max_length(int nChars) override
+ {
+ m_nMaxTextLength = nChars;
+ }
+
+ virtual void set_monospace(bool bMonospace) override
+ {
+ gtk_text_view_set_monospace(m_pTextView, bMonospace);
+ }
+
+ virtual void set_font_color(const Color& rColor) override
+ {
+ const bool bRemoveColor = rColor == COL_AUTO;
+ if (bRemoveColor && !m_pFgCssProvider)
+ return;
+ GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(GTK_WIDGET(m_pTextView));
+ if (m_pFgCssProvider)
+ {
+ gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFgCssProvider));
+ m_pFgCssProvider = nullptr;
+ }
+ if (bRemoveColor)
+ return;
+ OUString sColor = rColor.AsRGBHexString();
+ m_pFgCssProvider = gtk_css_provider_new();
+ OUString aBuffer = "textview text { color: #" + sColor + "; }";
+ OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
+ css_provider_load_from_data(m_pFgCssProvider, aResult.getStr(), aResult.getLength());
+ gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pFgCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_aCustomFont.use_custom_font(&rFont, u"textview");
+ }
+
+ virtual vcl::Font get_font() override
+ {
+ if (const vcl::Font* pFont = m_aCustomFont.get_custom_font())
+ return *pFont;
+ return GtkInstanceWidget::get_font();
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pVAdjustment, m_nVAdjustChangedSignalId);
+ g_signal_handler_block(m_pTextBuffer, m_nCursorPosSignalId);
+ g_signal_handler_block(m_pTextBuffer, m_nChangedSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pTextBuffer, m_nChangedSignalId);
+ g_signal_handler_unblock(m_pTextBuffer, m_nCursorPosSignalId);
+ g_signal_handler_unblock(m_pVAdjustment, m_nVAdjustChangedSignalId);
+ }
+
+ // in gtk, 'up' when on the first line, will jump to the start of the line
+ // if not there already
+ virtual bool can_move_cursor_with_up() const override
+ {
+ GtkTextIter start, end;
+ gtk_text_buffer_get_selection_bounds(m_pTextBuffer, &start, &end);
+ return !gtk_text_iter_equal(&start, &end) || !gtk_text_iter_is_start(&start);
+ }
+
+ // in gtk, 'down' when on the first line, will jump to the end of the line
+ // if not there already
+ virtual bool can_move_cursor_with_down() const override
+ {
+ GtkTextIter start, end;
+ gtk_text_buffer_get_selection_bounds(m_pTextBuffer, &start, &end);
+ return !gtk_text_iter_equal(&start, &end) || !gtk_text_iter_is_end(&end);
+ }
+
+ virtual void cut_clipboard() override
+ {
+ GdkClipboard *pClipboard = widget_get_clipboard(GTK_WIDGET(m_pTextView));
+ gtk_text_buffer_cut_clipboard(m_pTextBuffer, pClipboard, get_editable());
+ }
+
+ virtual void copy_clipboard() override
+ {
+ GdkClipboard *pClipboard = widget_get_clipboard(GTK_WIDGET(m_pTextView));
+ gtk_text_buffer_copy_clipboard(m_pTextBuffer, pClipboard);
+ }
+
+ virtual void paste_clipboard() override
+ {
+ GdkClipboard *pClipboard = widget_get_clipboard(GTK_WIDGET(m_pTextView));
+ gtk_text_buffer_paste_clipboard(m_pTextBuffer, pClipboard, nullptr, get_editable());
+ }
+
+ virtual void set_alignment(TxtAlign eXAlign) override
+ {
+ GtkJustification eJust = GTK_JUSTIFY_LEFT;
+ switch (eXAlign)
+ {
+ case TxtAlign::Left:
+ eJust = GTK_JUSTIFY_LEFT;
+ break;
+ case TxtAlign::Center:
+ eJust = GTK_JUSTIFY_CENTER;
+ break;
+ case TxtAlign::Right:
+ eJust = GTK_JUSTIFY_RIGHT;
+ break;
+ }
+ gtk_text_view_set_justification(m_pTextView, eJust);
+ }
+
+ virtual int vadjustment_get_value() const override
+ {
+ return gtk_adjustment_get_value(m_pVAdjustment);
+ }
+
+ virtual void vadjustment_set_value(int value) override
+ {
+ disable_notify_events();
+ gtk_adjustment_set_value(m_pVAdjustment, value);
+ enable_notify_events();
+ }
+
+ virtual int vadjustment_get_upper() const override
+ {
+ return gtk_adjustment_get_upper(m_pVAdjustment);
+ }
+
+ virtual int vadjustment_get_lower() const override
+ {
+ return gtk_adjustment_get_lower(m_pVAdjustment);
+ }
+
+ virtual int vadjustment_get_page_size() const override
+ {
+ return gtk_adjustment_get_page_size(m_pVAdjustment);
+ }
+
+ virtual void show() override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ gtk_widget_show(pParent);
+ gtk_widget_show(m_pWidget);
+ }
+
+ virtual void hide() override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (GTK_IS_SCROLLED_WINDOW(pParent))
+ gtk_widget_hide(pParent);
+ gtk_widget_hide(m_pWidget);
+ }
+
+ virtual ~GtkInstanceTextView() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pTextView, m_nButtonPressEvent);
+#endif
+ g_signal_handler_disconnect(m_pVAdjustment, m_nVAdjustChangedSignalId);
+ g_signal_handler_disconnect(m_pTextBuffer, m_nInsertTextSignalId);
+ g_signal_handler_disconnect(m_pTextBuffer, m_nChangedSignalId);
+ g_signal_handler_disconnect(m_pTextBuffer, m_nCursorPosSignalId);
+ g_signal_handler_disconnect(m_pTextBuffer, m_nHasSelectionSignalId);
+ }
+};
+
+}
+
+namespace {
+
+// IMHandler
+class IMHandler;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+AtkObject* (*default_drawing_area_get_accessible)(GtkWidget *widget);
+#endif
+
+class GtkInstanceDrawingArea : public GtkInstanceWidget, public virtual weld::DrawingArea
+{
+private:
+ GtkDrawingArea* m_pDrawingArea;
+ a11yref m_xAccessible;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ AtkObject *m_pAccessible;
+#endif
+ ScopedVclPtrInstance<VirtualDevice> m_xDevice;
+ std::unique_ptr<IMHandler> m_xIMHandler;
+ cairo_surface_t* m_pSurface;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nDrawSignalId;
+#endif
+ gulong m_nQueryTooltip;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nPopupMenu;
+ gulong m_nScrollEvent;
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalDraw(GtkDrawingArea*, cairo_t *cr, int /*width*/, int /*height*/, gpointer widget)
+#else
+ static gboolean signalDraw(GtkWidget*, cairo_t* cr, gpointer widget)
+#endif
+ {
+ GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_draw(cr);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return false;
+#endif
+ }
+ void signal_draw(cairo_t* cr)
+ {
+ if (!m_pSurface)
+ return;
+
+ GdkRectangle rect;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ double clip_x1, clip_x2, clip_y1, clip_y2;
+ cairo_clip_extents(cr, &clip_x1, &clip_y1, &clip_x2, &clip_y2);
+ rect.x = clip_x1;
+ rect.y = clip_y1;
+ rect.width = clip_x2 - clip_x1;
+ rect.height = clip_y2 - clip_y1;
+ if (rect.width <= 0 || rect.height <= 0)
+ return;
+#else
+ if (!gdk_cairo_get_clip_rectangle(cr, &rect))
+ return;
+#endif
+
+ tools::Rectangle aRect(Point(rect.x, rect.y), Size(rect.width, rect.height));
+ aRect = m_xDevice->PixelToLogic(aRect);
+ m_xDevice->Erase(aRect);
+ m_aDrawHdl.Call(std::pair<vcl::RenderContext&, const tools::Rectangle&>(*m_xDevice, aRect));
+ cairo_surface_mark_dirty(m_pSurface);
+
+ cairo_set_source_surface(cr, m_pSurface, 0, 0);
+ cairo_paint(cr);
+
+ tools::Rectangle aFocusRect(m_aGetFocusRectHdl.Call(*this));
+ if (!aFocusRect.IsEmpty())
+ {
+ gtk_render_focus(gtk_widget_get_style_context(GTK_WIDGET(m_pDrawingArea)), cr,
+ aFocusRect.Left(), aFocusRect.Top(), aFocusRect.GetWidth(), aFocusRect.GetHeight());
+ }
+ }
+ virtual void signal_size_allocate(guint nWidth, guint nHeight) override
+ {
+ Size aNewSize(nWidth, nHeight);
+ if (m_pSurface && aNewSize == m_xDevice->GetOutputSizePixel())
+ {
+ // unchanged
+ return;
+ }
+ m_xDevice->SetOutputSizePixel(Size(nWidth, nHeight));
+ m_pSurface = get_underlying_cairo_surface(*m_xDevice);
+ GtkInstanceWidget::signal_size_allocate(nWidth, nHeight);
+ }
+ void signal_style_updated()
+ {
+ m_aStyleUpdatedHdl.Call(*this);
+ }
+ static gboolean signalQueryTooltip(GtkWidget* pGtkWidget, gint x, gint y,
+ gboolean /*keyboard_mode*/, GtkTooltip *tooltip,
+ gpointer widget)
+ {
+ GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
+ tools::Rectangle aHelpArea(x, y);
+ OUString aTooltip = pThis->signal_query_tooltip(aHelpArea);
+ if (aTooltip.isEmpty())
+ return false;
+ gtk_tooltip_set_text(tooltip, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
+ GdkRectangle aGdkHelpArea;
+ aGdkHelpArea.x = aHelpArea.Left();
+ aGdkHelpArea.y = aHelpArea.Top();
+ aGdkHelpArea.width = aHelpArea.GetWidth();
+ aGdkHelpArea.height = aHelpArea.GetHeight();
+ if (pThis->SwapForRTL())
+ aGdkHelpArea.x = gtk_widget_get_allocated_width(pGtkWidget) - aGdkHelpArea.width - 1 - aGdkHelpArea.x;
+ gtk_tooltip_set_tip_area(tooltip, &aGdkHelpArea);
+ return true;
+ }
+ virtual bool signal_popup_menu(const CommandEvent& rCEvt) override
+ {
+ return signal_command(rCEvt);
+ }
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool signal_scroll(const GdkEventScroll* pEvent)
+ {
+ SalWheelMouseEvent aEvt(GtkSalFrame::GetWheelEvent(*pEvent));
+
+ if (SwapForRTL())
+ aEvt.mnX = gtk_widget_get_allocated_width(m_pWidget) - 1 - aEvt.mnX;
+
+ CommandWheelMode nMode;
+ sal_uInt16 nCode = aEvt.mnCode;
+ bool bHorz = aEvt.mbHorz;
+ if (nCode & KEY_MOD1)
+ nMode = CommandWheelMode::ZOOM;
+ else if (nCode & KEY_MOD2)
+ nMode = CommandWheelMode::DATAZOOM;
+ else
+ {
+ nMode = CommandWheelMode::SCROLL;
+ // #i85450# interpret shift-wheel as horizontal wheel action
+ if( (nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)) == KEY_SHIFT )
+ bHorz = true;
+ }
+
+ CommandWheelData aWheelData(aEvt.mnDelta, aEvt.mnNotchDelta, aEvt.mnScrollLines,
+ nMode, nCode, bHorz, aEvt.mbDeltaIsPixel);
+ CommandEvent aCEvt(Point(aEvt.mnX, aEvt.mnY), CommandEventId::Wheel, true, &aWheelData);
+ return m_aCommandHdl.Call(aCEvt);
+ }
+ static gboolean signalScroll(GtkWidget*, GdkEventScroll* pEvent, gpointer widget)
+ {
+ GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
+ return pThis->signal_scroll(pEvent);
+ }
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalResize(GtkDrawingArea*, int nWidth, int nHeight, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_size_allocate(nWidth, nHeight);
+ }
+#endif
+
+ DECL_LINK(SettingsChangedHdl, VclWindowEvent&, void);
+public:
+ GtkInstanceDrawingArea(GtkDrawingArea* pDrawingArea, GtkInstanceBuilder* pBuilder, const a11yref& rA11y, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pDrawingArea), pBuilder, bTakeOwnership)
+ , m_pDrawingArea(pDrawingArea)
+ , m_xAccessible(rA11y)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_pAccessible(nullptr)
+#endif
+ , m_xDevice(DeviceFormat::DEFAULT)
+ , m_pSurface(nullptr)
+ , m_nQueryTooltip(g_signal_connect(m_pDrawingArea, "query-tooltip", G_CALLBACK(signalQueryTooltip), this))
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_nPopupMenu(g_signal_connect(m_pDrawingArea, "popup-menu", G_CALLBACK(signalPopupMenu), this))
+ , m_nScrollEvent(g_signal_connect(m_pDrawingArea, "scroll-event", G_CALLBACK(signalScroll), this))
+#endif
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_drawing_area_set_draw_func(m_pDrawingArea, signalDraw, this, nullptr);
+#else
+ m_nDrawSignalId = g_signal_connect(m_pDrawingArea, "draw", G_CALLBACK(signalDraw), this);
+#endif
+ gtk_widget_set_has_tooltip(m_pWidget, true);
+ g_object_set_data(G_OBJECT(m_pDrawingArea), "g-lo-GtkInstanceDrawingArea", this);
+ m_xDevice->EnableRTL(get_direction());
+
+ ImplGetDefaultWindow()->AddEventListener(LINK(this, GtkInstanceDrawingArea, SettingsChangedHdl));
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ AtkObject* GetAtkObject(AtkObject* pDefaultAccessible)
+ {
+ if (!m_pAccessible && m_xAccessible.is())
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ m_pAccessible = atk_object_wrapper_new(m_xAccessible, gtk_widget_get_accessible(pParent), pDefaultAccessible);
+ if (m_pAccessible)
+ g_object_ref(m_pAccessible);
+ }
+ return m_pAccessible;
+ }
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ virtual void connect_size_allocate(const Link<const Size&, void>& rLink) override
+ {
+ m_nSizeAllocateSignalId = g_signal_connect(m_pWidget, "resize", G_CALLBACK(signalResize), this);
+ weld::Widget::connect_size_allocate(rLink);
+ }
+#endif
+
+ virtual void connect_mouse_press(const Link<const MouseEvent&, bool>& rLink) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!(gtk_widget_get_events(m_pWidget) & GDK_BUTTON_PRESS_MASK))
+ gtk_widget_add_events(m_pWidget, GDK_BUTTON_PRESS_MASK);
+#endif
+ GtkInstanceWidget::connect_mouse_press(rLink);
+ }
+
+ virtual void connect_mouse_release(const Link<const MouseEvent&, bool>& rLink) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (!(gtk_widget_get_events(m_pWidget) & GDK_BUTTON_RELEASE_MASK))
+ gtk_widget_add_events(m_pWidget, GDK_BUTTON_RELEASE_MASK);
+#endif
+ GtkInstanceWidget::connect_mouse_release(rLink);
+ }
+
+ virtual void set_direction(bool bRTL) override
+ {
+ GtkInstanceWidget::set_direction(bRTL);
+ m_xDevice->EnableRTL(bRTL);
+ }
+
+ virtual void set_cursor(PointerStyle ePointerStyle) override
+ {
+ GdkCursor *pCursor = GtkSalFrame::getDisplay()->getCursor(ePointerStyle);
+ if (!gtk_widget_get_realized(GTK_WIDGET(m_pDrawingArea)))
+ gtk_widget_realize(GTK_WIDGET(m_pDrawingArea));
+ widget_set_cursor(GTK_WIDGET(m_pDrawingArea), pCursor);
+ }
+
+ virtual Point get_pointer_position() const override
+ {
+ GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
+ GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
+ GdkDevice* pPointer = gdk_seat_get_pointer(pSeat);
+ double x(-1), y(-1);
+ GdkSurface* pWin = widget_get_surface(m_pWidget);
+ surface_get_device_position(pWin, pPointer, x, y, nullptr);
+ return Point(x, y);
+ }
+
+ virtual void set_input_context(const InputContext& rInputContext) override;
+
+ virtual void im_context_set_cursor_location(const tools::Rectangle& rCursorRect, int nExtTextInputWidth) override;
+
+ int im_context_get_surrounding(OUString& rSurroundingText)
+ {
+ return signal_im_context_get_surrounding(rSurroundingText);
+ }
+
+ bool im_context_delete_surrounding(const Selection& rRange)
+ {
+ return signal_im_context_delete_surrounding(rRange);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ virtual bool do_signal_key_press(const GdkEventKey* pEvent) override;
+ virtual bool do_signal_key_release(const GdkEventKey* pEvent) override;
+#endif
+
+ virtual void queue_draw() override
+ {
+ gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
+ }
+
+ virtual void queue_draw_area(int x, int y, int width, int height) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ tools::Rectangle aRect(Point(x, y), Size(width, height));
+ aRect = m_xDevice->LogicToPixel(aRect);
+ gtk_widget_queue_draw_area(GTK_WIDGET(m_pDrawingArea), aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight());
+#else
+ (void)x; (void)y; (void)width; (void)height;
+ queue_draw();
+#endif
+ }
+
+ virtual a11yref get_accessible_parent() override
+ {
+ //get_accessible_parent should only be needed for the vcl implementation,
+ //in the gtk impl the native AtkObject parent set via
+ //atk_object_wrapper_new(m_xAccessible, gtk_widget_get_accessible(pParent));
+ //should negate the need.
+ assert(false && "get_accessible_parent should only be called on a vcl impl");
+ return uno::Reference<css::accessibility::XAccessible>();
+ }
+
+ virtual a11yrelationset get_accessible_relation_set() override
+ {
+ //get_accessible_relation_set should only be needed for the vcl implementation,
+ //in the gtk impl the native equivalent should negate the need.
+ assert(false && "get_accessible_relation_set should only be called on a vcl impl");
+ return uno::Reference<css::accessibility::XAccessibleRelationSet>();
+ }
+
+ virtual Point get_accessible_location_on_screen() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
+#endif
+ gint x(0), y(0);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (pAtkObject && ATK_IS_COMPONENT(pAtkObject))
+ atk_component_get_extents(ATK_COMPONENT(pAtkObject), &x, &y, nullptr, nullptr, ATK_XY_SCREEN);
+#endif
+ return Point(x, y);
+ }
+
+ virtual void set_accessible_name(const OUString& rName) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
+ if (!pAtkObject)
+ return;
+ atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
+#else
+ (void)rName;
+#endif
+ }
+
+ virtual OUString get_accessible_name() const override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ AtkObject* pAtkObject = default_drawing_area_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
+ return OUString();
+#endif
+ }
+
+ virtual OUString get_accessible_description() const override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ AtkObject* pAtkObject = default_drawing_area_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
+ return OUString();
+#endif
+ }
+
+ virtual void enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants) override
+ {
+ do_enable_drag_source(rHelper, eDNDConstants);
+ }
+
+ virtual bool do_signal_drag_begin(bool& rUnsetDragIcon) override
+ {
+ rUnsetDragIcon = false;
+ if (m_aDragBeginHdl.Call(*this))
+ return true;
+ return false;
+ }
+
+ virtual ~GtkInstanceDrawingArea() override
+ {
+ ImplGetDefaultWindow()->RemoveEventListener(LINK(this, GtkInstanceDrawingArea, SettingsChangedHdl));
+
+ g_object_steal_data(G_OBJECT(m_pDrawingArea), "g-lo-GtkInstanceDrawingArea");
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (m_pAccessible)
+ g_object_unref(m_pAccessible);
+#endif
+ css::uno::Reference<css::lang::XComponent> xComp(m_xAccessible, css::uno::UNO_QUERY);
+ if (xComp.is())
+ xComp->dispose();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pDrawingArea, m_nScrollEvent);
+#endif
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pDrawingArea, m_nPopupMenu);
+#endif
+ g_signal_handler_disconnect(m_pDrawingArea, m_nQueryTooltip);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_drawing_area_set_draw_func(m_pDrawingArea, nullptr, nullptr, nullptr);
+#else
+ g_signal_handler_disconnect(m_pDrawingArea, m_nDrawSignalId);
+#endif
+ }
+
+ virtual OutputDevice& get_ref_device() override
+ {
+ return *m_xDevice;
+ }
+
+ bool signal_command(const CommandEvent& rCEvt)
+ {
+ return m_aCommandHdl.Call(rCEvt);
+ }
+
+ virtual void click(const Point& rPos) override
+ {
+ MouseEvent aEvent(rPos);
+ m_aMousePressHdl.Call(aEvent);
+ m_aMouseReleaseHdl.Call(aEvent);
+ }
+};
+
+IMPL_LINK(GtkInstanceDrawingArea, 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();
+}
+
+class IMHandler
+{
+private:
+ GtkInstanceDrawingArea* m_pArea;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkEventController* m_pFocusController;
+#endif
+ GtkIMContext* m_pIMContext;
+ OUString m_sPreeditText;
+ gulong m_nFocusInSignalId;
+ gulong m_nFocusOutSignalId;
+ bool m_bExtTextInput;
+
+public:
+ IMHandler(GtkInstanceDrawingArea* pArea)
+ : m_pArea(pArea)
+ , m_pIMContext(gtk_im_multicontext_new())
+ , m_bExtTextInput(false)
+ {
+ GtkWidget* pWidget = m_pArea->getWidget();
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_pFocusController = gtk_event_controller_focus_new();
+ gtk_widget_add_controller(pWidget, m_pFocusController);
+
+ m_nFocusInSignalId = g_signal_connect(m_pFocusController, "enter", G_CALLBACK(signalFocusIn), this);
+ m_nFocusOutSignalId = g_signal_connect(m_pFocusController, "leave", G_CALLBACK(signalFocusOut), this);
+#else
+ m_nFocusInSignalId = g_signal_connect(pWidget, "focus-in-event", G_CALLBACK(signalFocusIn), this);
+ m_nFocusOutSignalId = g_signal_connect(pWidget, "focus-out-event", G_CALLBACK(signalFocusOut), this);
+#endif
+
+ g_signal_connect(m_pIMContext, "preedit-start", G_CALLBACK(signalIMPreeditStart), this);
+ g_signal_connect(m_pIMContext, "preedit-end", G_CALLBACK(signalIMPreeditEnd), this);
+ g_signal_connect(m_pIMContext, "commit", G_CALLBACK(signalIMCommit), this);
+ g_signal_connect(m_pIMContext, "preedit-changed", G_CALLBACK(signalIMPreeditChanged), this);
+ g_signal_connect(m_pIMContext, "retrieve-surrounding", G_CALLBACK(signalIMRetrieveSurrounding), this);
+ g_signal_connect(m_pIMContext, "delete-surrounding", G_CALLBACK(signalIMDeleteSurrounding), this);
+
+ if (!gtk_widget_get_realized(pWidget))
+ gtk_widget_realize(pWidget);
+ im_context_set_client_widget(m_pIMContext, pWidget);
+ if (gtk_widget_has_focus(m_pArea->getWidget()))
+ gtk_im_context_focus_in(m_pIMContext);
+ }
+
+ void signalFocus(bool bIn)
+ {
+ if (bIn)
+ gtk_im_context_focus_in(m_pIMContext);
+ else
+ gtk_im_context_focus_out(m_pIMContext);
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalFocusIn(GtkEventControllerFocus*, gpointer im_handler)
+#else
+ static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer im_handler)
+#endif
+ {
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+ pThis->signalFocus(true);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return false;
+#endif
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalFocusOut(GtkEventControllerFocus*, gpointer im_handler)
+#else
+ static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer im_handler)
+#endif
+ {
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+ pThis->signalFocus(false);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return false;
+#endif
+ }
+
+ ~IMHandler()
+ {
+ EndExtTextInput();
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pFocusController, m_nFocusOutSignalId);
+ g_signal_handler_disconnect(m_pFocusController, m_nFocusInSignalId);
+#else
+ g_signal_handler_disconnect(m_pArea->getWidget(), m_nFocusOutSignalId);
+ g_signal_handler_disconnect(m_pArea->getWidget(), m_nFocusInSignalId);
+#endif
+
+ if (gtk_widget_has_focus(m_pArea->getWidget()))
+ gtk_im_context_focus_out(m_pIMContext);
+
+ // first give IC a chance to deinitialize
+ im_context_set_client_widget(m_pIMContext, nullptr);
+ // destroy old IC
+ g_object_unref(m_pIMContext);
+ }
+
+ void updateIMSpotLocation()
+ {
+ CommandEvent aCEvt(Point(), CommandEventId::CursorPos);
+ // we expect set_cursor_location to get triggered by this
+ m_pArea->signal_command(aCEvt);
+ }
+
+ void set_cursor_location(const tools::Rectangle& rRect)
+ {
+ GdkRectangle aArea{static_cast<int>(rRect.Left()), static_cast<int>(rRect.Top()),
+ static_cast<int>(rRect.GetWidth()), static_cast<int>(rRect.GetHeight())};
+ gtk_im_context_set_cursor_location(m_pIMContext, &aArea);
+ }
+
+ static void signalIMCommit(GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler)
+ {
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+
+ SolarMutexGuard aGuard;
+
+ // at least editeng expects to have seen a start before accepting a commit
+ pThis->StartExtTextInput();
+
+ OUString sText(pText, strlen(pText), RTL_TEXTENCODING_UTF8);
+ CommandExtTextInputData aData(sText, nullptr, sText.getLength(), 0, false);
+ CommandEvent aCEvt(Point(), CommandEventId::ExtTextInput, false, &aData);
+ pThis->m_pArea->signal_command(aCEvt);
+
+ pThis->updateIMSpotLocation();
+
+ pThis->EndExtTextInput();
+
+ pThis->m_sPreeditText.clear();
+ }
+
+ static void signalIMPreeditChanged(GtkIMContext* pIMContext, gpointer im_handler)
+ {
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+
+ SolarMutexGuard aGuard;
+
+ sal_Int32 nCursorPos(0);
+ sal_uInt8 nCursorFlags(0);
+ std::vector<ExtTextInputAttr> aInputFlags;
+ OUString sText = GtkSalFrame::GetPreeditDetails(pIMContext, aInputFlags, nCursorPos, nCursorFlags);
+
+ // change from nothing to nothing -> do not start preedit e.g. this
+ // will activate input into a calc cell without user input
+ if (sText.isEmpty() && pThis->m_sPreeditText.isEmpty())
+ return;
+
+ pThis->m_sPreeditText = sText;
+
+ CommandExtTextInputData aData(sText, aInputFlags.data(), nCursorPos, nCursorFlags, false);
+ CommandEvent aCEvt(Point(), CommandEventId::ExtTextInput, false, &aData);
+ pThis->m_pArea->signal_command(aCEvt);
+
+ pThis->updateIMSpotLocation();
+ }
+
+ static gboolean signalIMRetrieveSurrounding(GtkIMContext* pContext, gpointer im_handler)
+ {
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+
+ SolarMutexGuard aGuard;
+
+ OUString sSurroundingText;
+ int nCursorIndex = pThis->m_pArea->im_context_get_surrounding(sSurroundingText);
+
+ if (nCursorIndex != -1)
+ {
+ OString sUTF = OUStringToOString(sSurroundingText, RTL_TEXTENCODING_UTF8);
+ std::u16string_view sCursorText(sSurroundingText.subView(0, nCursorIndex));
+ gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
+ OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
+ }
+
+ return true;
+ }
+
+ static gboolean signalIMDeleteSurrounding(GtkIMContext*, gint nOffset, gint nChars,
+ gpointer im_handler)
+ {
+ bool bRet = false;
+
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+
+ SolarMutexGuard aGuard;
+
+ OUString sSurroundingText;
+ sal_Int32 nCursorIndex = pThis->m_pArea->im_context_get_surrounding(sSurroundingText);
+
+ Selection aSelection = SalFrame::CalcDeleteSurroundingSelection(sSurroundingText, nCursorIndex, nOffset, nChars);
+ if (aSelection != Selection(SAL_MAX_UINT32, SAL_MAX_UINT32))
+ bRet = pThis->m_pArea->im_context_delete_surrounding(aSelection);
+ return bRet;
+ }
+
+ void StartExtTextInput()
+ {
+ if (m_bExtTextInput)
+ return;
+ CommandEvent aCEvt(Point(), CommandEventId::StartExtTextInput);
+ m_pArea->signal_command(aCEvt);
+ m_bExtTextInput = true;
+ }
+
+ static void signalIMPreeditStart(GtkIMContext*, gpointer im_handler)
+ {
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+ SolarMutexGuard aGuard;
+ pThis->StartExtTextInput();
+ pThis->updateIMSpotLocation();
+ }
+
+ void EndExtTextInput()
+ {
+ if (!m_bExtTextInput)
+ return;
+ CommandEvent aCEvt(Point(), CommandEventId::EndExtTextInput);
+ m_pArea->signal_command(aCEvt);
+ m_bExtTextInput = false;
+ }
+
+ static void signalIMPreeditEnd(GtkIMContext*, gpointer im_handler)
+ {
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+ SolarMutexGuard aGuard;
+ pThis->updateIMSpotLocation();
+ pThis->EndExtTextInput();
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool im_context_filter_keypress(const GdkEventKey* pEvent)
+ {
+ return gtk_im_context_filter_keypress(m_pIMContext, const_cast<GdkEventKey*>(pEvent));
+ }
+#endif
+};
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+bool GtkInstanceDrawingArea::do_signal_key_press(const GdkEventKey* pEvent)
+{
+ if (m_xIMHandler && m_xIMHandler->im_context_filter_keypress(pEvent))
+ return true;
+ return GtkInstanceWidget::do_signal_key_press(pEvent);
+}
+
+bool GtkInstanceDrawingArea::do_signal_key_release(const GdkEventKey* pEvent)
+{
+ if (m_xIMHandler && m_xIMHandler->im_context_filter_keypress(pEvent))
+ return true;
+ return GtkInstanceWidget::do_signal_key_release(pEvent);
+}
+#endif
+
+void GtkInstanceDrawingArea::set_input_context(const InputContext& rInputContext)
+{
+ bool bUseIm(rInputContext.GetOptions() & InputContextFlags::Text);
+ if (!bUseIm)
+ {
+ m_xIMHandler.reset();
+ return;
+ }
+ // create a new im context
+ if (!m_xIMHandler)
+ m_xIMHandler.reset(new IMHandler(this));
+}
+
+void GtkInstanceDrawingArea::im_context_set_cursor_location(const tools::Rectangle& rCursorRect, int /*nExtTextInputWidth*/)
+{
+ if (!m_xIMHandler)
+ return;
+ m_xIMHandler->set_cursor_location(rCursorRect);
+}
+
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+static gboolean signalEntryInsertSpecialCharKeyPress(GtkEntry* pEntry, GdkEventKey* pEvent, gpointer)
+{
+ if ((pEvent->keyval == GDK_KEY_S || pEvent->keyval == GDK_KEY_s) &&
+ (pEvent->state & GDK_MODIFIER_MASK) == static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK))
+ {
+ if (auto pImplFncGetSpecialChars = vcl::GetGetSpecialCharsFunction())
+ {
+ weld::Window* pDialogParent = nullptr;
+
+ GtkWidget* pTopLevel = widget_get_toplevel(GTK_WIDGET(pEntry));
+ if (GtkSalFrame* pFrame = pTopLevel ? GtkSalFrame::getFromWindow(pTopLevel) : nullptr)
+ pDialogParent = pFrame->GetFrameWeld();
+
+ std::unique_ptr<GtkInstanceWindow> xFrameWeld;
+ if (!pDialogParent && pTopLevel)
+ {
+ xFrameWeld.reset(new GtkInstanceWindow(GTK_WINDOW(pTopLevel), nullptr, false));
+ pDialogParent = xFrameWeld.get();
+ }
+
+ OUString aChars = pImplFncGetSpecialChars(pDialogParent, ::get_font(GTK_WIDGET(pEntry)));
+ if (!aChars.isEmpty())
+ {
+ gtk_editable_delete_selection(GTK_EDITABLE(pEntry));
+ gint position = gtk_editable_get_position(GTK_EDITABLE(pEntry));
+ OString sText(OUStringToOString(aChars, RTL_TEXTENCODING_UTF8));
+ gtk_editable_insert_text(GTK_EDITABLE(pEntry), sText.getStr(), sText.getLength(),
+ &position);
+ gtk_editable_set_position(GTK_EDITABLE(pEntry), position);
+ }
+ }
+ return true;
+ }
+ return false;
+}
+#endif
+
+namespace {
+
+GtkBuilder* makeMenuToggleButtonBuilder()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ OUString aUri(AllSettings::GetUIRootDir() + "vcl/ui/menutogglebutton3.ui");
+#else
+ OUString aUri(AllSettings::GetUIRootDir() + "vcl/ui/menutogglebutton4.ui");
+#endif
+ OUString aPath;
+ osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
+ return gtk_builder_new_from_file(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr());
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+
+GtkBuilder* makeComboBoxBuilder()
+{
+ OUString aUri(AllSettings::GetUIRootDir() + "vcl/ui/combobox.ui");
+ OUString aPath;
+ osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
+ return gtk_builder_new_from_file(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr());
+}
+
+// pop down the toplevel combobox menu when something is activated from a custom
+// submenu, i.e. wysiwyg style menu
+class CustomRenderMenuButtonHelper : public MenuHelper
+{
+private:
+ GtkToggleButton* m_pComboBox;
+public:
+ CustomRenderMenuButtonHelper(GtkMenu* pMenu, GtkToggleButton* pComboBox)
+ : MenuHelper(pMenu, false)
+ , m_pComboBox(pComboBox)
+ {
+ }
+ virtual void signal_item_activate(const OString& /*rIdent*/) override
+ {
+ gtk_toggle_button_set_active(m_pComboBox, false);
+ }
+};
+
+#endif
+
+gboolean signalTooltipQuery(GtkWidget* pWidget, gint /*x*/, gint /*y*/,
+ gboolean /*keyboard_mode*/, GtkTooltip *tooltip)
+{
+ const ImplSVHelpData& aHelpData = ImplGetSVHelpData();
+ if (aHelpData.mbBalloonHelp) // extended tips
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // by default use accessible description
+ AtkObject* pAtkObject = gtk_widget_get_accessible(pWidget);
+ const char* pDesc = pAtkObject ? atk_object_get_description(pAtkObject) : nullptr;
+ if (pDesc && pDesc[0])
+ {
+ gtk_tooltip_set_text(tooltip, pDesc);
+ return true;
+ }
+#endif
+
+ // fallback to the mechanism which needs help installed
+ OString sHelpId = ::get_help_id(pWidget);
+ Help* pHelp = !sHelpId.isEmpty() ? Application::GetHelp() : nullptr;
+ if (pHelp)
+ {
+ OUString sHelpText = pHelp->GetHelpText(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), static_cast<weld::Widget*>(nullptr));
+ if (!sHelpText.isEmpty())
+ {
+ gtk_tooltip_set_text(tooltip, OUStringToOString(sHelpText, RTL_TEXTENCODING_UTF8).getStr());
+ return true;
+ }
+ }
+ }
+
+ const char* pDesc = gtk_widget_get_tooltip_text(pWidget);
+ if (pDesc && pDesc[0])
+ {
+ gtk_tooltip_set_text(tooltip, pDesc);
+ return true;
+ }
+
+ return false;
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+
+class GtkInstanceComboBox : public GtkInstanceWidget, public vcl::ISearchableStringList, public virtual weld::ComboBox
+{
+private:
+ GtkComboBox* m_pComboBox;
+// GtkOverlay* m_pOverlay;
+// GtkTreeView* m_pTreeView;
+// GtkMenuButton* m_pOverlayButton; // button that the StyleDropdown uses on an active row
+ GtkWidget* m_pMenuWindow;
+ GtkTreeModel* m_pTreeModel;
+ GtkCellRenderer* m_pButtonTextRenderer;
+ GtkWidget* m_pEntry;
+ GtkEditable* m_pEditable;
+// GtkCellView* m_pCellView;
+ GtkEventController* m_pKeyController;
+ GtkEventController* m_pEntryKeyController;
+ GtkEventController* m_pMenuKeyController;
+ GtkEventController* m_pEntryFocusController;
+// std::unique_ptr<CustomRenderMenuButtonHelper> m_xCustomMenuButtonHelper;
+ WidgetFont m_aCustomFont;
+ std::optional<vcl::Font> m_xEntryFont;
+ std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
+ vcl::QuickSelectionEngine m_aQuickSelectionEngine;
+ std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>> m_aSeparatorRows;
+#if 0
+ OUString m_sMenuButtonRow;
+#endif
+// bool m_bHoverSelection;
+// bool m_bMouseInOverlayButton;
+ bool m_bPopupActive;
+ bool m_bAutoComplete;
+ bool m_bAutoCompleteCaseSensitive;
+ bool m_bChangedByMenu;
+ bool m_bCustomRenderer;
+ bool m_bUserSelectEntry;
+ gint m_nTextCol;
+ gint m_nIdCol;
+// gulong m_nToggleFocusInSignalId;
+// gulong m_nToggleFocusOutSignalId;
+// gulong m_nRowActivatedSignalId;
+ gulong m_nChangedSignalId;
+ gulong m_nPopupShownSignalId;
+ gulong m_nKeyPressEventSignalId;
+ gulong m_nEntryInsertTextSignalId;
+ gulong m_nEntryActivateSignalId;
+ gulong m_nEntryFocusInSignalId;
+ gulong m_nEntryFocusOutSignalId;
+ gulong m_nEntryKeyPressEventSignalId;
+ guint m_nAutoCompleteIdleId;
+// gint m_nNonCustomLineHeight;
+ gint m_nPrePopupCursorPos;
+ int m_nMRUCount;
+ int m_nMaxMRUCount;
+
+ static gboolean idleAutoComplete(gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->auto_complete();
+ return false;
+ }
+
+ void auto_complete()
+ {
+ m_nAutoCompleteIdleId = 0;
+ OUString aStartText = get_active_text();
+ int nStartPos, nEndPos;
+ get_entry_selection_bounds(nStartPos, nEndPos);
+ int nMaxSelection = std::max(nStartPos, nEndPos);
+ if (nMaxSelection != aStartText.getLength())
+ return;
+
+ disable_notify_events();
+ int nActive = get_active();
+ int nStart = nActive;
+
+ if (nStart == -1)
+ nStart = 0;
+
+ int nPos = -1;
+
+ int nZeroRow = 0;
+ if (m_nMRUCount)
+ nZeroRow += (m_nMRUCount + 1);
+
+ if (!m_bAutoCompleteCaseSensitive)
+ {
+ // Try match case insensitive from current position
+ nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, false);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case insensitive, but from start
+ nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, false);
+ }
+ }
+
+ if (nPos == -1)
+ {
+ // Try match case sensitive from current position
+ nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, true);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case sensitive, but from start
+ nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, true);
+ }
+ }
+
+ if (nPos != -1)
+ {
+ OUString aText = get_text_including_mru(nPos);
+ if (aText != aStartText)
+ {
+ SolarMutexGuard aGuard;
+ set_active_including_mru(nPos, true);
+ }
+ select_entry_region(aText.getLength(), aStartText.getLength());
+ }
+ enable_notify_events();
+ }
+
+ static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
+ gint* position, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
+ }
+
+ void signal_entry_insert_text(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength, gint* position)
+ {
+ if (m_bPopupActive) // not entered by the user
+ return;
+
+ // first filter inserted text
+ if (m_aEntryInsertTextHdl.IsSet())
+ {
+ OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
+ const bool bContinue = m_aEntryInsertTextHdl.Call(sText);
+ if (bContinue && !sText.isEmpty())
+ {
+ OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
+ g_signal_handlers_block_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
+ gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position);
+ g_signal_handlers_unblock_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
+ }
+ g_signal_stop_emission_by_name(pEntry, "insert-text");
+ }
+
+ if (m_bAutoComplete)
+ {
+ // now check for autocompletes
+ if (m_nAutoCompleteIdleId)
+ g_source_remove(m_nAutoCompleteIdleId);
+ m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
+ }
+ }
+
+ static void signalChanged(GtkComboBox*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->fire_signal_changed();
+ }
+
+ void fire_signal_changed()
+ {
+ m_bUserSelectEntry = true;
+ m_bChangedByMenu = m_bPopupActive;
+ signal_changed();
+ m_bChangedByMenu = false;
+ }
+
+ static void signalPopupToggled(GObject*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_popup_toggled();
+ }
+
+#if 0
+ int get_popup_height(gint& rPopupWidth)
+ {
+ const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
+
+ int nMaxRows = rSettings.GetListBoxMaximumLineCount();
+ bool bAddScrollWidth = false;
+ int nRows = get_count_including_mru();
+ if (nMaxRows < nRows)
+ {
+ nRows = nMaxRows;
+ bAddScrollWidth = true;
+ }
+
+ GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
+ gint nRowHeight = get_height_row(m_pTreeView, pColumns);
+ g_list_free(pColumns);
+
+ gint nSeparatorHeight = get_height_row_separator(m_pTreeView);
+ gint nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nRows);
+
+ // if we're using a custom renderer, limit the height to the height nMaxRows would be
+ // for a normal renderer, and then round down to how many custom rows fit in that
+ // space
+ if (m_nNonCustomLineHeight != -1 && nRowHeight)
+ {
+ gint nNormalHeight = get_height_rows(m_nNonCustomLineHeight, nSeparatorHeight, nMaxRows);
+ if (nHeight > nNormalHeight)
+ {
+ gint nRowsOnly = nNormalHeight - get_height_rows(0, nSeparatorHeight, nMaxRows);
+ gint nCustomRows = (nRowsOnly + (nRowHeight - 1)) / nRowHeight;
+ nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nCustomRows);
+ }
+ }
+
+ if (bAddScrollWidth)
+ rPopupWidth += rSettings.GetScrollBarSize();
+
+ return nHeight;
+ }
+#endif
+
+ bool toggle_button_get_active()
+ {
+ GValue value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_BOOLEAN);
+ g_object_get_property(G_OBJECT(m_pComboBox), "popup-shown", &value);
+ return g_value_get_boolean(&value);
+ }
+
+ void menu_toggled()
+ {
+ if (!m_bPopupActive)
+ {
+#if 0
+ if (m_bHoverSelection)
+ {
+ // turn hover selection back off until mouse is moved again
+ // *after* menu is shown again
+ gtk_tree_view_set_hover_selection(m_pTreeView, false);
+ m_bHoverSelection = false;
+ }
+#endif
+
+ if (!m_bUserSelectEntry)
+ set_active_including_mru(m_nPrePopupCursorPos, true);
+
+#if 0
+ // undo show_menu tooltip blocking
+ GtkWidget* pParent = widget_get_toplevel(m_pToggleButton);
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
+ if (pFrame)
+ pFrame->UnblockTooltip();
+#endif
+ }
+ else
+ {
+ m_nPrePopupCursorPos = get_active();
+
+ m_bUserSelectEntry = false;
+
+ // if we are in mru mode always start with the cursor at the top of the menu
+ if (m_nMaxMRUCount)
+ set_active_including_mru(0, true);
+ }
+ }
+
+ virtual void signal_popup_toggled() override
+ {
+ m_aQuickSelectionEngine.Reset();
+
+ bool bOldPopupActive = m_bPopupActive;
+ m_bPopupActive = toggle_button_get_active();
+
+ menu_toggled();
+
+ if (bOldPopupActive != m_bPopupActive)
+ {
+ ComboBox::signal_popup_toggled();
+ // restore focus to the GtkEntry when the popup is gone, which
+ // is what the vcl case does, to ease the transition a little,
+ // but don't do it if the focus was moved out of togglebutton
+ // by something else already (e.g. font combobox in toolbar
+ // on a "direct pick" from the menu which moves focus into
+ // the main document
+ if (!m_bPopupActive && m_pEntry && has_child_focus())
+ {
+ disable_notify_events();
+ gtk_widget_grab_focus(m_pEntry);
+ enable_notify_events();
+ }
+ }
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalEntryFocusIn(GtkEventControllerFocus*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_entry_focus_in();
+ }
+#else
+ static gboolean signalEntryFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_entry_focus_in();
+ return false;
+ }
+#endif
+
+ void signal_entry_focus_in()
+ {
+ signal_focus_in();
+ }
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ static void signalEntryFocusOut(GtkEventControllerFocus*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_entry_focus_out();
+ }
+#else
+ static gboolean signalEntryFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_entry_focus_out();
+ return false;
+ }
+#endif
+
+ void signal_entry_focus_out()
+ {
+ // if we have an untidy selection on losing focus remove the selection
+ int nStartPos, nEndPos;
+ if (get_entry_selection_bounds(nStartPos, nEndPos))
+ {
+ int nMin = std::min(nStartPos, nEndPos);
+ int nMax = std::max(nStartPos, nEndPos);
+ if (nMin != 0 || nMax != get_active_text().getLength())
+ select_entry_region(0, 0);
+ }
+ signal_focus_out();
+ }
+
+ static void signalEntryActivate(GtkEntry*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_entry_activate();
+ }
+
+ void signal_entry_activate()
+ {
+ if (m_aEntryActivateHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ if (m_aEntryActivateHdl.Call(*this))
+ g_signal_stop_emission_by_name(m_pEntry, "activate");
+ }
+ update_mru();
+ }
+
+ OUString get(int pos, int col) const
+ {
+ OUString sRet;
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ {
+ gchar* pStr;
+ gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
+ sRet = OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pStr);
+ }
+ return sRet;
+ }
+
+ void set(int pos, int col, std::u16string_view rText)
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ {
+ OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gtk_list_store_set(GTK_LIST_STORE(m_pTreeModel), &iter, col, aStr.getStr(), -1);
+ }
+ }
+
+ int find(std::u16string_view rStr, int col, bool bSearchMRUArea) const
+ {
+ GtkTreeIter iter;
+ if (!gtk_tree_model_get_iter_first(m_pTreeModel, &iter))
+ return -1;
+
+ int nRet = 0;
+
+ if (!bSearchMRUArea && m_nMRUCount)
+ {
+ if (!gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, m_nMRUCount + 1))
+ return -1;
+ nRet += (m_nMRUCount + 1);
+ }
+
+ OString aStr(OUStringToOString(rStr, RTL_TEXTENCODING_UTF8).getStr());
+ do
+ {
+ gchar* pStr;
+ gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
+ const bool bEqual = g_strcmp0(pStr, aStr.getStr()) == 0;
+ g_free(pStr);
+ if (bEqual)
+ return nRet;
+ ++nRet;
+ } while (gtk_tree_model_iter_next(m_pTreeModel, &iter));
+
+ return -1;
+ }
+
+ bool separator_function(const GtkTreePath* path)
+ {
+ return ::separator_function(path, m_aSeparatorRows);
+ }
+
+ bool separator_function(int pos)
+ {
+ GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
+ bool bRet = separator_function(path);
+ gtk_tree_path_free(path);
+ return bRet;
+ }
+
+ static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter);
+ bool bRet = pThis->separator_function(path);
+ gtk_tree_path_free(path);
+ return bRet;
+ }
+
+ // https://gitlab.gnome.org/GNOME/gtk/issues/310
+ //
+ // in the absence of a built-in solution
+ // a) support typeahead for the case where there is no entry widget, typing ahead
+ // into the button itself will select via the vcl selection engine, a matching
+ // entry
+ static gboolean signalKeyPress(GtkEventControllerKey*, guint keyval, guint keycode, GdkModifierType state, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_key_press(CreateKeyEvent(keyval, keycode, state, 0));
+ }
+
+ // tdf#131076 we want return in a ComboBox to act like return in a
+ // GtkEntry and activate the default dialog/assistant button
+ bool combobox_activate()
+ {
+ GtkWidget *pComboBox = GTK_WIDGET(m_pComboBox);
+ GtkWidget *pToplevel = widget_get_toplevel(pComboBox);
+ GtkWindow *pWindow = GTK_WINDOW(pToplevel);
+ if (!pWindow)
+ return false;
+ if (!GTK_IS_DIALOG(pWindow) && !GTK_IS_ASSISTANT(pWindow))
+ return false;
+ bool bDone = false;
+ GtkWidget *pDefaultWidget = gtk_window_get_default_widget(pWindow);
+ if (pDefaultWidget && pDefaultWidget != pComboBox && gtk_widget_get_sensitive(pDefaultWidget))
+ bDone = gtk_widget_activate(pDefaultWidget);
+ return bDone;
+ }
+
+ static gboolean signalEntryKeyPress(GtkEventControllerKey*, guint keyval, guint keycode, GdkModifierType state, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ LocalizeDecimalSeparator(keyval);
+ return pThis->signal_entry_key_press(CreateKeyEvent(keyval, keycode, state, 0));
+ }
+
+ bool signal_entry_key_press(const KeyEvent& rKEvt)
+ {
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ bool bDone = false;
+
+ auto nCode = aKeyCode.GetCode();
+ switch (nCode)
+ {
+ case KEY_DOWN:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nCount = get_count_including_mru();
+ int nActive = get_active_including_mru() + 1;
+ while (nActive < nCount && separator_function(nActive))
+ ++nActive;
+ if (nActive < nCount)
+ set_active_including_mru(nActive, true);
+ bDone = true;
+ }
+ else if (nKeyMod == KEY_MOD2 && !m_bPopupActive)
+ {
+ gtk_combo_box_popup(m_pComboBox);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_UP:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
+ int nActive = get_active_including_mru() - 1;
+ while (nActive >= nStartBound && separator_function(nActive))
+ --nActive;
+ if (nActive >= nStartBound)
+ set_active_including_mru(nActive, true);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_PAGEUP:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nCount = get_count_including_mru();
+ int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
+ int nActive = nStartBound;
+ while (nActive < nCount && separator_function(nActive))
+ ++nActive;
+ if (nActive < nCount)
+ set_active_including_mru(nActive, true);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_PAGEDOWN:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nActive = get_count_including_mru() - 1;
+ int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
+ while (nActive >= nStartBound && separator_function(nActive))
+ --nActive;
+ if (nActive >= nStartBound)
+ set_active_including_mru(nActive, true);
+ bDone = true;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return bDone;
+ }
+
+ bool signal_key_press(const KeyEvent& rKEvt)
+ {
+#if 0
+ if (m_bHoverSelection)
+ {
+ // once a key is pressed, turn off hover selection until mouse is
+ // moved again otherwise when the treeview scrolls it jumps to the
+ // position under the mouse.
+ gtk_tree_view_set_hover_selection(m_pTreeView, false);
+ m_bHoverSelection = false;
+ }
+#endif
+
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ bool bDone = false;
+
+ auto nCode = aKeyCode.GetCode();
+ switch (nCode)
+ {
+ case KEY_DOWN:
+ case KEY_UP:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN:
+ case KEY_HOME:
+ case KEY_END:
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ case KEY_RETURN:
+ {
+ m_aQuickSelectionEngine.Reset();
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ // tdf#131076 don't let bare return toggle menu popup active, but do allow deactivate
+ if (nCode == KEY_RETURN && !nKeyMod)
+ {
+ if (!m_bPopupActive)
+ bDone = combobox_activate();
+ else
+ {
+ // treat 'return' as if the active entry was clicked on
+ signalChanged(m_pComboBox, this);
+ gtk_combo_box_popdown(m_pComboBox);
+ bDone = true;
+ }
+ }
+ else if (nCode == KEY_UP && nKeyMod == KEY_MOD2 && m_bPopupActive)
+ {
+ gtk_combo_box_popdown(m_pComboBox);
+ bDone = true;
+ }
+ else if (nCode == KEY_DOWN && nKeyMod == KEY_MOD2 && !m_bPopupActive)
+ {
+ gtk_combo_box_popup(m_pComboBox);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_ESCAPE:
+ {
+ m_aQuickSelectionEngine.Reset();
+ if (m_bPopupActive)
+ {
+ gtk_combo_box_popdown(m_pComboBox);
+ bDone = true;
+ }
+ break;
+ }
+ default:
+ // tdf#131076 let base space toggle menu popup when it's not already visible
+ if (nCode == KEY_SPACE && !aKeyCode.GetModifier() && !m_bPopupActive)
+ bDone = false;
+ else
+ bDone = m_aQuickSelectionEngine.HandleKeyEvent(rKEvt);
+ break;
+ }
+
+ if (!bDone)
+ {
+ if (!m_pEntry)
+ bDone = signal_entry_key_press(rKEvt);
+ else
+ {
+ // with gtk4-4.2.1 the unconsumed keystrokes don't appear to get to
+ // the GtkEntry for up/down to move to the next entry without this extra help
+ // (which means this extra indirection is probably effectively
+ // the same as if calling signal_entry_key_press directly here)
+ bDone = gtk_event_controller_key_forward(GTK_EVENT_CONTROLLER_KEY(m_pMenuKeyController), m_pEntry);
+ }
+ }
+
+ return bDone;
+ }
+
+ vcl::StringEntryIdentifier typeahead_getEntry(int nPos, OUString& out_entryText) const
+ {
+ int nEntryCount(get_count_including_mru());
+ if (nPos >= nEntryCount)
+ nPos = 0;
+ out_entryText = get_text_including_mru(nPos);
+
+ // vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based
+ // => normalize
+ return reinterpret_cast<vcl::StringEntryIdentifier>(nPos + 1);
+ }
+
+ static int typeahead_getEntryPos(vcl::StringEntryIdentifier entry)
+ {
+ // our pos is 0-based, but StringEntryIdentifier does not allow for a NULL
+ return reinterpret_cast<sal_Int64>(entry) - 1;
+ }
+
+ int tree_view_get_cursor() const
+ {
+ int nRet = -1;
+#if 0
+ GtkTreePath* path;
+ gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
+ if (path)
+ {
+ gint depth;
+ gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
+ nRet = indices[depth-1];
+ gtk_tree_path_free(path);
+ }
+#endif
+
+ return nRet;
+ }
+
+ int get_selected_entry() const
+ {
+ if (m_bPopupActive)
+ return tree_view_get_cursor();
+ else
+ return get_active_including_mru();
+ }
+
+ void set_typeahead_selected_entry(int nSelect)
+ {
+ set_active_including_mru(nSelect, true);
+ }
+
+ virtual vcl::StringEntryIdentifier CurrentEntry(OUString& out_entryText) const override
+ {
+ int nCurrentPos = get_selected_entry();
+ return typeahead_getEntry((nCurrentPos == -1) ? 0 : nCurrentPos, out_entryText);
+ }
+
+ virtual vcl::StringEntryIdentifier NextEntry(vcl::StringEntryIdentifier currentEntry, OUString& out_entryText) const override
+ {
+ int nNextPos = typeahead_getEntryPos(currentEntry) + 1;
+ return typeahead_getEntry(nNextPos, out_entryText);
+ }
+
+ virtual void SelectEntry(vcl::StringEntryIdentifier entry) override
+ {
+ int nSelect = typeahead_getEntryPos(entry);
+ if (nSelect == get_selected_entry())
+ {
+ // ignore that. This method is a callback from the QuickSelectionEngine, which means the user attempted
+ // to select the given entry by typing its starting letters. No need to act.
+ return;
+ }
+
+ // normalize
+ int nCount = get_count_including_mru();
+ if (nSelect >= nCount)
+ nSelect = nCount ? nCount-1 : -1;
+
+ set_typeahead_selected_entry(nSelect);
+ }
+
+#if 0
+ static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->grab_broken(pEvent);
+ }
+
+ void grab_broken(const GdkEventGrabBroken *event)
+ {
+ if (event->grab_window == nullptr)
+ {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+ }
+ else if (!g_object_get_data(G_OBJECT(event->grab_window), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
+ {
+ //try and regrab, so when we lose the grab to the menu of the color palette
+ //combobox we regain it so the color palette doesn't itself disappear on next
+ //click on the color palette combobox
+ do_grab(GTK_WIDGET(m_pMenuWindow));
+ }
+ }
+
+ static gboolean signalButtonPress(GtkWidget* pWidget, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ return pThis->button_press(pWidget, pEvent);
+ }
+
+ bool button_press(GtkWidget* pWidget, GdkEventButton* pEvent)
+ {
+ //we want to pop down if the button was pressed outside our popup
+ gdouble x = pEvent->x_root;
+ gdouble y = pEvent->y_root;
+ gint xoffset, yoffset;
+ gdk_window_get_root_origin(widget_get_surface(pWidget), &xoffset, &yoffset);
+
+ GtkAllocation alloc;
+ gtk_widget_get_allocation(pWidget, &alloc);
+ xoffset += alloc.x;
+ yoffset += alloc.y;
+
+ gtk_widget_get_allocation(GTK_WIDGET(m_pMenuWindow), &alloc);
+ gint x1 = alloc.x + xoffset;
+ gint y1 = alloc.y + yoffset;
+ gint x2 = x1 + alloc.width;
+ gint y2 = y1 + alloc.height;
+
+ if (x > x1 && x < x2 && y > y1 && y < y2)
+ return false;
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+
+ return false;
+ }
+
+ static gboolean signalMotion(GtkWidget*, GdkEventMotion*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_motion();
+ return false;
+ }
+
+ void signal_motion()
+ {
+ // if hover-selection was disabled after pressing a key, then turn it back on again
+ if (!m_bHoverSelection && !m_bMouseInOverlayButton)
+ {
+ gtk_tree_view_set_hover_selection(m_pTreeView, true);
+ m_bHoverSelection = true;
+ }
+ }
+#endif
+
+ static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->handle_row_activated();
+ }
+
+ void handle_row_activated()
+ {
+ m_bUserSelectEntry = true;
+ m_bChangedByMenu = true;
+ disable_notify_events();
+ int nActive = get_active();
+ if (m_pEditable)
+ gtk_editable_set_text(m_pEditable, OUStringToOString(get_text(nActive), RTL_TEXTENCODING_UTF8).getStr());
+#if 0
+ else
+ tree_view_set_cursor(nActive);
+#endif
+ enable_notify_events();
+// gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+ fire_signal_changed();
+ update_mru();
+ }
+
+ void do_clear()
+ {
+ disable_notify_events();
+ gtk_combo_box_set_row_separator_func(m_pComboBox, nullptr, nullptr, nullptr);
+ m_aSeparatorRows.clear();
+ gtk_list_store_clear(GTK_LIST_STORE(m_pTreeModel));
+ m_nMRUCount = 0;
+ enable_notify_events();
+ }
+
+ virtual int get_max_mru_count() const override
+ {
+ return m_nMaxMRUCount;
+ }
+
+ virtual void set_max_mru_count(int nMaxMRUCount) override
+ {
+ m_nMaxMRUCount = nMaxMRUCount;
+ update_mru();
+ }
+
+ void update_mru()
+ {
+ int nMRUCount = m_nMRUCount;
+
+ if (m_nMaxMRUCount)
+ {
+ OUString sActiveText = get_active_text();
+ OUString sActiveId = get_active_id();
+ insert_including_mru(0, sActiveText, &sActiveId, nullptr, nullptr);
+ ++m_nMRUCount;
+
+ for (int i = 1; i < m_nMRUCount - 1; ++i)
+ {
+ if (get_text_including_mru(i) == sActiveText)
+ {
+ remove_including_mru(i);
+ --m_nMRUCount;
+ break;
+ }
+ }
+ }
+
+ while (m_nMRUCount > m_nMaxMRUCount)
+ {
+ remove_including_mru(m_nMRUCount - 1);
+ --m_nMRUCount;
+ }
+
+ if (m_nMRUCount && !nMRUCount)
+ insert_separator_including_mru(m_nMRUCount, "separator");
+ else if (!m_nMRUCount && nMRUCount)
+ remove_including_mru(m_nMRUCount); // remove separator
+ }
+
+ int get_count_including_mru() const
+ {
+ return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr);
+ }
+
+ int get_active_including_mru() const
+ {
+ return gtk_combo_box_get_active(m_pComboBox);
+ }
+
+ void set_active_including_mru(int pos, bool bInteractive)
+ {
+ disable_notify_events();
+
+ gtk_combo_box_set_active(m_pComboBox, pos);
+
+ m_bChangedByMenu = false;
+ enable_notify_events();
+
+ if (bInteractive && !m_bPopupActive)
+ signal_changed();
+ }
+
+ int find_text_including_mru(std::u16string_view rStr, bool bSearchMRU) const
+ {
+ return find(rStr, m_nTextCol, bSearchMRU);
+ }
+
+ int find_id_including_mru(std::u16string_view rId, bool bSearchMRU) const
+ {
+ return find(rId, m_nIdCol, bSearchMRU);
+ }
+
+ OUString get_text_including_mru(int pos) const
+ {
+ return get(pos, m_nTextCol);
+ }
+
+ OUString get_id_including_mru(int pos) const
+ {
+ return get(pos, m_nIdCol);
+ }
+
+ void set_id_including_mru(int pos, std::u16string_view rId)
+ {
+ set(pos, m_nIdCol, rId);
+ }
+
+ void remove_including_mru(int pos)
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
+ if (!m_aSeparatorRows.empty())
+ {
+ bool bFound = false;
+
+ GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
+
+ for (auto aIter = m_aSeparatorRows.begin(); aIter != m_aSeparatorRows.end(); ++aIter)
+ {
+ GtkTreePath* seppath = gtk_tree_row_reference_get_path(aIter->get());
+ if (seppath)
+ {
+ if (gtk_tree_path_compare(pPath, seppath) == 0)
+ bFound = true;
+ gtk_tree_path_free(seppath);
+ }
+ if (bFound)
+ {
+ m_aSeparatorRows.erase(aIter);
+ break;
+ }
+ }
+
+ gtk_tree_path_free(pPath);
+ }
+ gtk_list_store_remove(GTK_LIST_STORE(m_pTreeModel), &iter);
+ enable_notify_events();
+ }
+
+ void insert_separator_including_mru(int pos, const OUString& rId)
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ if (!gtk_combo_box_get_row_separator_func(m_pComboBox))
+ gtk_combo_box_set_row_separator_func(m_pComboBox, separatorFunction, this, nullptr);
+ insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, &rId, u"", nullptr, nullptr);
+ GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
+ m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(m_pTreeModel, pPath));
+ gtk_tree_path_free(pPath);
+ enable_notify_events();
+ }
+
+ void insert_including_mru(int pos, std::u16string_view rText, const OUString* pId, const OUString* pIconName, const VirtualDevice* pImageSurface)
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, pId, rText, pIconName, pImageSurface);
+ enable_notify_events();
+ }
+
+#if 0
+ static gboolean signalGetChildPosition(GtkOverlay*, GtkWidget*, GdkRectangle* pAllocation, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ return pThis->signal_get_child_position(pAllocation);
+ }
+
+ bool signal_get_child_position(GdkRectangle* pAllocation)
+ {
+ if (!gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton)))
+ return false;
+ if (!gtk_widget_get_realized(GTK_WIDGET(m_pTreeView)))
+ return false;
+ int nRow = find_id_including_mru(m_sMenuButtonRow, true);
+ if (nRow == -1)
+ return false;
+
+ gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &pAllocation->width, nullptr);
+
+ GtkTreePath* pPath = gtk_tree_path_new_from_indices(nRow, -1);
+ GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
+ tools::Rectangle aRect = get_row_area(m_pTreeView, pColumns, pPath);
+ gtk_tree_path_free(pPath);
+ g_list_free(pColumns);
+
+ pAllocation->x = aRect.Right() - pAllocation->width;
+ pAllocation->y = aRect.Top();
+ pAllocation->height = aRect.GetHeight();
+
+ return true;
+ }
+
+ static gboolean signalOverlayButtonCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_overlay_button_crossing(pEvent->type == GDK_ENTER_NOTIFY);
+ return false;
+ }
+
+ void signal_overlay_button_crossing(bool bEnter)
+ {
+ m_bMouseInOverlayButton = bEnter;
+ if (!bEnter)
+ return;
+
+ if (m_bHoverSelection)
+ {
+ // once toggled button is pressed, turn off hover selection until
+ // mouse leaves the overlay button
+ gtk_tree_view_set_hover_selection(m_pTreeView, false);
+ m_bHoverSelection = false;
+ }
+ int nRow = find_id_including_mru(m_sMenuButtonRow, true);
+ assert(nRow != -1);
+ tree_view_set_cursor(nRow); // select the buttons row
+ }
+#endif
+
+ int include_mru(int pos)
+ {
+ if (m_nMRUCount && pos != -1)
+ pos += (m_nMRUCount + 1);
+ return pos;
+ }
+
+public:
+ GtkInstanceComboBox(GtkComboBox* pComboBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pComboBox), pBuilder, bTakeOwnership)
+ , m_pComboBox(pComboBox)
+// , m_pOverlay(GTK_OVERLAY(gtk_builder_get_object(pComboBuilder, "overlay")))
+// , m_pTreeView(GTK_TREE_VIEW(gtk_builder_get_object(pComboBuilder, "treeview")))
+// , m_pOverlayButton(GTK_MENU_BUTTON(gtk_builder_get_object(pComboBuilder, "overlaybutton")))
+ , m_pMenuWindow(nullptr)
+ , m_pTreeModel(gtk_combo_box_get_model(pComboBox))
+ , m_pButtonTextRenderer(nullptr)
+// , m_pToggleButton(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "button")))
+ , m_pEntry(GTK_IS_ENTRY(gtk_combo_box_get_child(pComboBox)) ? gtk_combo_box_get_child(pComboBox) : nullptr)
+ , m_pEditable(GTK_EDITABLE(m_pEntry))
+ , m_aCustomFont(m_pWidget)
+// , m_pCellView(nullptr)
+ , m_aQuickSelectionEngine(*this)
+// , m_bHoverSelection(false)
+// , m_bMouseInOverlayButton(false)
+ , m_bPopupActive(false)
+ , m_bAutoComplete(false)
+ , m_bAutoCompleteCaseSensitive(false)
+ , m_bChangedByMenu(false)
+ , m_bCustomRenderer(false)
+ , m_bUserSelectEntry(false)
+ , m_nTextCol(gtk_combo_box_get_entry_text_column(pComboBox))
+ , m_nIdCol(gtk_combo_box_get_id_column(pComboBox))
+// , m_nToggleFocusInSignalId(0)
+// , m_nToggleFocusOutSignalId(0)
+// , m_nRowActivatedSignalId(g_signal_connect(m_pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
+ , m_nChangedSignalId(g_signal_connect(m_pComboBox, "changed", G_CALLBACK(signalChanged), this))
+ , m_nPopupShownSignalId(g_signal_connect(m_pComboBox, "notify::popup-shown", G_CALLBACK(signalPopupToggled), this))
+ , m_nAutoCompleteIdleId(0)
+// , m_nNonCustomLineHeight(-1)
+ , m_nPrePopupCursorPos(-1)
+ , m_nMRUCount(0)
+ , m_nMaxMRUCount(0)
+ {
+ for (GtkWidget* pChild = gtk_widget_get_first_child(GTK_WIDGET(m_pComboBox));
+ pChild; pChild = gtk_widget_get_next_sibling(pChild))
+ {
+ if (GTK_IS_POPOVER(pChild))
+ {
+ m_pMenuWindow = pChild;
+ break;
+ }
+ }
+ SAL_WARN_IF(!m_pMenuWindow, "vcl.gtk", "GtkInstanceComboBox: couldn't find popup menu");
+
+ bool bHasEntry = gtk_combo_box_get_has_entry(m_pComboBox);
+ bool bPixbufUsedSurface = gtk_tree_model_get_n_columns(m_pTreeModel) == 4;
+
+ bool bFindButtonTextRenderer = !bHasEntry;
+ GtkCellLayout* pCellLayout = GTK_CELL_LAYOUT(m_pComboBox);
+ GList* cells = gtk_cell_layout_get_cells(pCellLayout);
+ guint i = g_list_length(cells) - 1;;
+ // reorder the cell renderers
+ for (GList* pRenderer = g_list_first(cells); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ gtk_cell_layout_reorder(pCellLayout, pCellRenderer, i--);
+ if (bFindButtonTextRenderer)
+ {
+ m_pButtonTextRenderer = pCellRenderer;
+ bFindButtonTextRenderer = false;
+ }
+ }
+
+ // Seeing as GtkCellRendererPixbuf no longer takes a surface, then insert our own replacement
+ // to render that instead here
+ if (bPixbufUsedSurface)
+ {
+ GtkCellRenderer* pSurfaceRenderer = surface_cell_renderer_new();
+ gtk_cell_layout_pack_start(pCellLayout, pSurfaceRenderer, false);
+ gtk_cell_layout_reorder(pCellLayout, pSurfaceRenderer, 0);
+ gtk_cell_layout_set_attributes(pCellLayout, pSurfaceRenderer, "surface", 3, nullptr);
+ }
+
+ if (bHasEntry)
+ {
+ m_bAutoComplete = true;
+ m_nEntryInsertTextSignalId = g_signal_connect(m_pEditable, "insert-text", G_CALLBACK(signalEntryInsertText), this);
+ m_nEntryActivateSignalId = g_signal_connect(m_pEntry, "activate", G_CALLBACK(signalEntryActivate), this);
+ m_pEntryFocusController = GTK_EVENT_CONTROLLER(gtk_event_controller_focus_new());
+ m_nEntryFocusInSignalId = g_signal_connect(m_pEntryFocusController, "enter", G_CALLBACK(signalEntryFocusIn), this);
+ m_nEntryFocusOutSignalId = g_signal_connect(m_pEntryFocusController, "leave", G_CALLBACK(signalEntryFocusOut), this);
+ gtk_widget_add_controller(m_pEntry, m_pEntryFocusController);
+ m_pEntryKeyController = GTK_EVENT_CONTROLLER(gtk_event_controller_key_new());
+ m_nEntryKeyPressEventSignalId = g_signal_connect(m_pEntryKeyController, "key-pressed", G_CALLBACK(signalEntryKeyPress), this);
+ gtk_widget_add_controller(m_pEntry, m_pEntryKeyController);
+ m_nKeyPressEventSignalId = 0;
+ m_pKeyController = nullptr;
+ }
+ else
+ {
+ m_nEntryInsertTextSignalId = 0;
+ m_nEntryActivateSignalId = 0;
+ m_pEntryFocusController = nullptr;
+ m_nEntryFocusInSignalId = 0;
+ m_nEntryFocusOutSignalId = 0;
+ m_pEntryKeyController = nullptr;
+ m_nEntryKeyPressEventSignalId = 0;
+ m_pKeyController = GTK_EVENT_CONTROLLER(gtk_event_controller_key_new());
+ m_nKeyPressEventSignalId = g_signal_connect(m_pKeyController, "key-pressed", G_CALLBACK(signalKeyPress), this);
+ gtk_widget_add_controller(GTK_WIDGET(m_pComboBox), m_pKeyController);
+ }
+
+// g_signal_connect(m_pMenuWindow, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
+// g_signal_connect(m_pMenuWindow, "button-press-event", G_CALLBACK(signalButtonPress), this);
+// g_signal_connect(m_pMenuWindow, "motion-notify-event", G_CALLBACK(signalMotion), this);
+
+ // support typeahead for the menu itself, typing into the menu will
+ // select via the vcl selection engine, a matching entry.
+ if (m_pMenuWindow)
+ {
+ m_pMenuKeyController = GTK_EVENT_CONTROLLER(gtk_event_controller_key_new());
+ g_signal_connect(m_pMenuKeyController, "key-pressed", G_CALLBACK(signalKeyPress), this);
+ gtk_widget_add_controller(m_pMenuWindow, m_pMenuKeyController);
+ }
+ else
+ m_pMenuKeyController = nullptr;
+#if 0
+ g_signal_connect(m_pOverlay, "get-child-position", G_CALLBACK(signalGetChildPosition), this);
+ gtk_overlay_add_overlay(m_pOverlay, GTK_WIDGET(m_pOverlayButton));
+ g_signal_connect(m_pOverlayButton, "leave-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
+ g_signal_connect(m_pOverlayButton, "enter-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
+#endif
+ }
+
+ virtual int get_active() const override
+ {
+ int nActive = get_active_including_mru();
+ if (nActive == -1)
+ return -1;
+
+ if (m_nMRUCount)
+ {
+ if (nActive < m_nMRUCount)
+ nActive = find_text(get_text_including_mru(nActive));
+ else
+ nActive -= (m_nMRUCount + 1);
+ }
+
+ return nActive;
+ }
+
+ virtual OUString get_active_id() const override
+ {
+ int nActive = get_active();
+ return nActive != -1 ? get_id(nActive) : OUString();
+ }
+
+ virtual void set_active_id(const OUString& rStr) override
+ {
+ set_active(find_id(rStr));
+ m_bChangedByMenu = false;
+ }
+
+ virtual void set_size_request(int nWidth, int nHeight) override
+ {
+ if (m_pButtonTextRenderer)
+ {
+ // tweak the cell render to get a narrower size to stick
+ if (nWidth != -1)
+ {
+ // this bit isn't great, I really want to be able to ellipse the text in the comboboxtext itself and let
+ // the popup menu render them in full, in the interim ellipse both of them
+ g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_MIDDLE, nullptr);
+
+ // to find out how much of the width of the combobox belongs to the cell, set
+ // the cell and widget to the min cell width and see what the difference is
+ int min;
+ gtk_cell_renderer_get_preferred_width(m_pButtonTextRenderer, m_pWidget, &min, nullptr);
+ gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, min, -1);
+ gtk_widget_set_size_request(m_pWidget, min, -1);
+ int nNonCellWidth = get_preferred_size().Width() - min;
+
+ int nCellWidth = nWidth - nNonCellWidth;
+ if (nCellWidth >= 0)
+ {
+ // now set the cell to the max width which it can be within the
+ // requested widget width
+ gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, nWidth - nNonCellWidth, -1);
+ }
+ }
+ else
+ {
+ g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_NONE, nullptr);
+ gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, -1, -1);
+ }
+ }
+
+ gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
+ }
+
+ virtual void set_active(int pos) override
+ {
+ set_active_including_mru(include_mru(pos), false);
+ }
+
+ virtual OUString get_active_text() const override
+ {
+ if (m_pEditable)
+ {
+ const gchar* pText = gtk_editable_get_text(m_pEditable);
+ return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ int nActive = get_active();
+ if (nActive == -1)
+ return OUString();
+
+ return get_text(nActive);
+ }
+
+ virtual OUString get_text(int pos) const override
+ {
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ return get_text_including_mru(pos);
+ }
+
+ virtual OUString get_id(int pos) const override
+ {
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ return get_id_including_mru(pos);
+ }
+
+ virtual void set_id(int pos, const OUString& rId) override
+ {
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ set_id_including_mru(pos, rId);
+ }
+
+ virtual void insert_vector(const std::vector<weld::ComboBoxEntry>& rItems, bool bKeepExisting) override
+ {
+ freeze();
+
+ int nInsertionPoint;
+ if (!bKeepExisting)
+ {
+ clear();
+ nInsertionPoint = 0;
+ }
+ else
+ nInsertionPoint = get_count();
+
+ GtkTreeIter iter;
+ // tdf#125241 inserting backwards is faster
+ for (auto aI = rItems.rbegin(); aI != rItems.rend(); ++aI)
+ {
+ const auto& rItem = *aI;
+ insert_row(GTK_LIST_STORE(m_pTreeModel), iter, nInsertionPoint, rItem.sId.isEmpty() ? nullptr : &rItem.sId,
+ rItem.sString, rItem.sImage.isEmpty() ? nullptr : &rItem.sImage, nullptr);
+ }
+
+ thaw();
+ }
+
+ virtual void remove(int pos) override
+ {
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ remove_including_mru(pos);
+ }
+
+ virtual void insert(int pos, const OUString& rText, const OUString* pId, const OUString* pIconName, VirtualDevice* pImageSurface) override
+ {
+ insert_including_mru(include_mru(pos), rText, pId, pIconName, pImageSurface);
+ }
+
+ virtual void insert_separator(int pos, const OUString& rId) override
+ {
+ pos = pos == -1 ? get_count() : pos;
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ insert_separator_including_mru(pos, rId);
+ }
+
+ virtual int get_count() const override
+ {
+ int nCount = get_count_including_mru();
+ if (m_nMRUCount)
+ nCount -= (m_nMRUCount + 1);
+ return nCount;
+ }
+
+ virtual int find_text(const OUString& rStr) const override
+ {
+ int nPos = find_text_including_mru(rStr, false);
+ if (nPos != -1 && m_nMRUCount)
+ nPos -= (m_nMRUCount + 1);
+ return nPos;
+ }
+
+ virtual int find_id(const OUString& rId) const override
+ {
+ int nPos = find_id_including_mru(rId, false);
+ if (nPos != -1 && m_nMRUCount)
+ nPos -= (m_nMRUCount + 1);
+ return nPos;
+ }
+
+ virtual void clear() override
+ {
+ do_clear();
+ }
+
+ virtual void make_sorted() override
+ {
+ m_xSorter.reset(new comphelper::string::NaturalStringSorter(
+ ::comphelper::getProcessComponentContext(),
+ Application::GetSettings().GetUILanguageTag().getLocale()));
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
+ gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, default_sort_func, m_xSorter.get(), nullptr);
+ }
+
+ virtual bool has_entry() const override
+ {
+ return gtk_combo_box_get_has_entry(m_pComboBox);
+ }
+
+ virtual void set_entry_message_type(weld::EntryMessageType eType) override
+ {
+ assert(m_pEntry);
+ ::set_entry_message_type(GTK_ENTRY(m_pEntry), eType);
+ }
+
+ virtual void set_entry_text(const OUString& rText) override
+ {
+ assert(m_pEditable);
+ disable_notify_events();
+ gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+ enable_notify_events();
+ }
+
+ virtual void set_entry_width_chars(int nChars) override
+ {
+ assert(m_pEditable);
+ disable_notify_events();
+ gtk_editable_set_width_chars(m_pEditable, nChars);
+ gtk_editable_set_max_width_chars(m_pEditable, nChars);
+ enable_notify_events();
+ }
+
+ virtual void set_entry_max_length(int nChars) override
+ {
+ assert(m_pEntry);
+ disable_notify_events();
+ gtk_entry_set_max_length(GTK_ENTRY(m_pEntry), nChars);
+ enable_notify_events();
+ }
+
+ virtual void select_entry_region(int nStartPos, int nEndPos) override
+ {
+ assert(m_pEditable);
+ disable_notify_events();
+ gtk_editable_select_region(m_pEditable, nStartPos, nEndPos);
+ enable_notify_events();
+ }
+
+ virtual bool get_entry_selection_bounds(int& rStartPos, int &rEndPos) override
+ {
+ assert(m_pEditable);
+ return gtk_editable_get_selection_bounds(m_pEditable, &rStartPos, &rEndPos);
+ }
+
+ virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
+ {
+ m_bAutoComplete = bEnable;
+ m_bAutoCompleteCaseSensitive = bCaseSensitive;
+ }
+
+ virtual void set_entry_placeholder_text(const OUString& rText) override
+ {
+ assert(m_pEntry);
+ gtk_entry_set_placeholder_text(GTK_ENTRY(m_pEntry), rText.toUtf8().getStr());
+ }
+
+ virtual void set_entry_editable(bool bEditable) override
+ {
+ assert(m_pEditable);
+ gtk_editable_set_editable(m_pEditable, bEditable);
+ }
+
+ virtual void cut_entry_clipboard() override
+ {
+ assert(m_pEntry);
+ gtk_widget_activate_action(m_pEntry, "cut.clipboard", nullptr);
+ }
+
+ virtual void copy_entry_clipboard() override
+ {
+ assert(m_pEntry);
+ gtk_widget_activate_action(m_pEntry, "copy.clipboard", nullptr);
+ }
+
+ virtual void paste_entry_clipboard() override
+ {
+ assert(m_pEntry);
+ gtk_widget_activate_action(m_pEntry, "paste.clipboard", nullptr);
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_aCustomFont.use_custom_font(&rFont, u"combobox");
+ }
+
+ virtual vcl::Font get_font() override
+ {
+ if (const vcl::Font* pFont = m_aCustomFont.get_custom_font())
+ return *pFont;
+ return GtkInstanceWidget::get_font();
+ }
+
+ virtual void set_entry_font(const vcl::Font& rFont) override
+ {
+ m_xEntryFont = rFont;
+ assert(m_pEntry);
+ PangoAttrList* pOrigList = gtk_entry_get_attributes(GTK_ENTRY(m_pEntry));
+ PangoAttrList* pAttrList = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
+ update_attr_list(pAttrList, rFont);
+ gtk_entry_set_attributes(GTK_ENTRY(m_pEntry), pAttrList);
+ pango_attr_list_unref(pAttrList);
+ }
+
+ virtual vcl::Font get_entry_font() override
+ {
+ if (m_xEntryFont)
+ return *m_xEntryFont;
+ assert(m_pEntry);
+ PangoContext* pContext = gtk_widget_get_pango_context(m_pEntry);
+ return pango_to_vcl(pango_context_get_font_description(pContext),
+ Application::GetSettings().GetUILanguageTag().getLocale());
+ }
+
+ virtual void disable_notify_events() override
+ {
+ if (m_pEditable)
+ {
+ g_signal_handler_block(m_pEditable, m_nEntryInsertTextSignalId);
+ g_signal_handler_block(m_pEntry, m_nEntryActivateSignalId);
+ g_signal_handler_block(m_pEntryFocusController, m_nEntryFocusInSignalId);
+ g_signal_handler_block(m_pEntryFocusController, m_nEntryFocusOutSignalId);
+ g_signal_handler_block(m_pEntryKeyController, m_nEntryKeyPressEventSignalId);
+ }
+ else
+ g_signal_handler_block(m_pKeyController, m_nKeyPressEventSignalId);
+
+// if (m_nToggleFocusInSignalId)
+// g_signal_handler_block(m_pToggleButton, m_nToggleFocusInSignalId);
+// if (m_nToggleFocusOutSignalId)
+// g_signal_handler_block(m_pToggleButton, m_nToggleFocusOutSignalId);
+// g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
+ g_signal_handler_block(m_pComboBox, m_nPopupShownSignalId);
+ g_signal_handler_block(m_pComboBox, m_nChangedSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pComboBox, m_nChangedSignalId);
+ g_signal_handler_unblock(m_pComboBox, m_nPopupShownSignalId);
+// g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
+// if (m_nToggleFocusInSignalId)
+// g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusInSignalId);
+// if (m_nToggleFocusOutSignalId)
+// g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusOutSignalId);
+ if (m_pEditable)
+ {
+ g_signal_handler_unblock(m_pEntry, m_nEntryActivateSignalId);
+ g_signal_handler_unblock(m_pEntryFocusController, m_nEntryFocusInSignalId);
+ g_signal_handler_unblock(m_pEntryFocusController, m_nEntryFocusOutSignalId);
+ g_signal_handler_unblock(m_pEntryKeyController, m_nEntryKeyPressEventSignalId);
+ g_signal_handler_unblock(m_pEditable, m_nEntryInsertTextSignalId);
+ }
+ else
+ g_signal_handler_unblock(m_pKeyController, m_nKeyPressEventSignalId);
+ }
+
+ virtual void freeze() override
+ {
+ disable_notify_events();
+ bool bIsFirstFreeze = IsFirstFreeze();
+ GtkInstanceWidget::freeze();
+ if (bIsFirstFreeze)
+ {
+ g_object_ref(m_pTreeModel);
+// gtk_tree_view_set_model(m_pTreeView, nullptr);
+ g_object_freeze_notify(G_OBJECT(m_pTreeModel));
+ if (m_xSorter)
+ {
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
+ }
+ }
+ enable_notify_events();
+ }
+
+ virtual void thaw() override
+ {
+ disable_notify_events();
+ if (IsLastThaw())
+ {
+ if (m_xSorter)
+ {
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
+ }
+ g_object_thaw_notify(G_OBJECT(m_pTreeModel));
+// gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
+ g_object_unref(m_pTreeModel);
+ }
+ GtkInstanceWidget::thaw();
+ enable_notify_events();
+ }
+
+ virtual bool get_popup_shown() const override
+ {
+ return m_bPopupActive;
+ }
+
+ virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
+ {
+// if (!m_nToggleFocusInSignalId)
+// m_nToggleFocusInSignalId = g_signal_connect_after(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this);
+ GtkInstanceWidget::connect_focus_in(rLink);
+ }
+
+ virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
+ {
+// if (!m_nToggleFocusOutSignalId)
+// m_nToggleFocusOutSignalId = g_signal_connect_after(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this);
+ GtkInstanceWidget::connect_focus_out(rLink);
+ }
+
+ virtual void grab_focus() override
+ {
+ if (has_focus())
+ return;
+ if (m_pEntry)
+ gtk_widget_grab_focus(m_pEntry);
+ else
+ {
+// gtk_widget_grab_focus(m_pToggleButton);
+ gtk_widget_grab_focus(GTK_WIDGET(m_pComboBox));
+ }
+ }
+
+ virtual bool has_focus() const override
+ {
+ if (m_pEntry && gtk_widget_has_focus(m_pEntry))
+ return true;
+
+// if (gtk_widget_has_focus(m_pToggleButton))
+// return true;
+
+#if 0
+ if (gtk_widget_get_visible(GTK_WIDGET(m_pMenuWindow)))
+ {
+ if (gtk_widget_has_focus(GTK_WIDGET(m_pOverlayButton)) || gtk_widget_has_focus(GTK_WIDGET(m_pTreeView)))
+ return true;
+ }
+#endif
+
+ return GtkInstanceWidget::has_focus();
+ }
+
+ virtual bool changed_by_direct_pick() const override
+ {
+ return m_bChangedByMenu;
+ }
+
+ virtual void set_custom_renderer(bool bOn) override
+ {
+ if (bOn == m_bCustomRenderer)
+ return;
+#if 0
+ GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
+ // keep the original height around for optimal popup height calculation
+ m_nNonCustomLineHeight = bOn ? ::get_height_row(m_pTreeView, pColumns) : -1;
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pColumns->data);
+ gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn));
+ if (bOn)
+ {
+ GtkCellRenderer *pRenderer = custom_cell_renderer_new();
+ GValue value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_POINTER);
+ g_value_set_pointer(&value, static_cast<gpointer>(this));
+ g_object_set_property(G_OBJECT(pRenderer), "instance", &value);
+ gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "id", m_nIdCol);
+ }
+ else
+ {
+ GtkCellRenderer *pRenderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
+ }
+ g_list_free(pColumns);
+ m_bCustomRenderer = bOn;
+#endif
+ }
+
+ void call_signal_custom_render(VirtualDevice& rOutput, const tools::Rectangle& rRect, bool bSelected, const OUString& rId)
+ {
+ signal_custom_render(rOutput, rRect, bSelected, rId);
+ }
+
+ Size call_signal_custom_get_size(VirtualDevice& rOutput)
+ {
+ return signal_custom_get_size(rOutput);
+ }
+
+ VclPtr<VirtualDevice> create_render_virtual_device() const override
+ {
+ return create_virtual_device();
+ }
+
+ virtual void set_item_menu(const OString& rIdent, weld::Menu* pMenu) override
+ {
+#if 0
+ m_xCustomMenuButtonHelper.reset();
+ GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
+ GtkWidget* pMenuWidget = GTK_WIDGET(pPopoverWidget ? pPopoverWidget->getMenu() : nullptr);
+ gtk_menu_button_set_popup(m_pOverlayButton, pMenuWidget);
+ gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), pMenuWidget != nullptr);
+ gtk_widget_queue_resize_no_redraw(GTK_WIDGET(m_pOverlayButton)); // force location recalc
+ if (pMenuWidget)
+ m_xCustomMenuButtonHelper.reset(new CustomRenderMenuButtonHelper(GTK_MENU(pMenuWidget), GTK_TOGGLE_BUTTON(m_pToggleButton)));
+ m_sMenuButtonRow = OUString::fromUtf8(rIdent);
+#else
+ (void)rIdent; (void)pMenu;
+#endif
+ }
+
+ OUString get_mru_entries() const override
+ {
+ const sal_Unicode cSep = ';';
+
+ OUStringBuffer aEntries;
+ for (sal_Int32 n = 0; n < m_nMRUCount; n++)
+ {
+ aEntries.append(get_text_including_mru(n));
+ if (n < m_nMRUCount - 1)
+ aEntries.append(cSep);
+ }
+ return aEntries.makeStringAndClear();
+ }
+
+ virtual void set_mru_entries(const OUString& rEntries) override
+ {
+ const sal_Unicode cSep = ';';
+
+ // Remove old MRU entries
+ for (sal_Int32 n = m_nMRUCount; n;)
+ remove_including_mru(--n);
+
+ sal_Int32 nMRUCount = 0;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OUString aEntry = rEntries.getToken(0, cSep, nIndex);
+ // Accept only existing entries
+ int nPos = find_text(aEntry);
+ if (nPos != -1)
+ {
+ OUString sId = get_id(nPos);
+ insert_including_mru(0, aEntry, &sId, nullptr, nullptr);
+ ++nMRUCount;
+ }
+ }
+ while (nIndex >= 0);
+
+ if (nMRUCount && !m_nMRUCount)
+ insert_separator_including_mru(nMRUCount, "separator");
+ else if (!nMRUCount && m_nMRUCount)
+ remove_including_mru(m_nMRUCount); // remove separator
+
+ m_nMRUCount = nMRUCount;
+ }
+
+ int get_menu_button_width() const override
+ {
+#if 0
+ bool bVisible = gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton));
+ if (!bVisible)
+ gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), true);
+ gint nWidth;
+ gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &nWidth, nullptr);
+ if (!bVisible)
+ gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), false);
+ return nWidth;
+#else
+ return 0;
+#endif
+ }
+
+ virtual ~GtkInstanceComboBox() override
+ {
+// m_xCustomMenuButtonHelper.reset();
+ do_clear();
+ if (m_nAutoCompleteIdleId)
+ g_source_remove(m_nAutoCompleteIdleId);
+ if (m_pEditable)
+ {
+ g_signal_handler_disconnect(m_pEditable, m_nEntryInsertTextSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nEntryActivateSignalId);
+ g_signal_handler_disconnect(m_pEntryFocusController, m_nEntryFocusInSignalId);
+ g_signal_handler_disconnect(m_pEntryFocusController, m_nEntryFocusOutSignalId);
+ g_signal_handler_disconnect(m_pEntryKeyController, m_nEntryKeyPressEventSignalId);
+ }
+ else
+ g_signal_handler_disconnect(m_pKeyController, m_nKeyPressEventSignalId);
+// if (m_nToggleFocusInSignalId)
+// g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusInSignalId);
+// if (m_nToggleFocusOutSignalId)
+// g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusOutSignalId);
+// g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
+ g_signal_handler_disconnect(m_pComboBox, m_nPopupShownSignalId);
+ g_signal_handler_disconnect(m_pComboBox, m_nChangedSignalId);
+
+// gtk_tree_view_set_model(m_pTreeView, nullptr);
+
+ }
+};
+
+#else
+
+class GtkInstanceComboBox : public GtkInstanceContainer, public vcl::ISearchableStringList, public virtual weld::ComboBox
+{
+private:
+ GtkBuilder* m_pComboBuilder;
+ GtkComboBox* m_pComboBox;
+ GtkOverlay* m_pOverlay;
+ GtkTreeView* m_pTreeView;
+ GtkMenuButton* m_pOverlayButton; // button that the StyleDropdown uses on an active row
+ GtkWindow* m_pMenuWindow;
+ GtkTreeModel* m_pTreeModel;
+ GtkCellRenderer* m_pButtonTextRenderer;
+ GtkCellRenderer* m_pMenuTextRenderer;
+ GtkWidget* m_pToggleButton;
+ GtkWidget* m_pEntry;
+ GtkCellView* m_pCellView;
+ WidgetFont m_aCustomFont;
+ std::unique_ptr<CustomRenderMenuButtonHelper> m_xCustomMenuButtonHelper;
+ std::optional<vcl::Font> m_xEntryFont;
+ std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
+ vcl::QuickSelectionEngine m_aQuickSelectionEngine;
+ std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>> m_aSeparatorRows;
+ OUString m_sMenuButtonRow;
+ bool m_bHoverSelection;
+ bool m_bMouseInOverlayButton;
+ bool m_bPopupActive;
+ bool m_bAutoComplete;
+ bool m_bAutoCompleteCaseSensitive;
+ bool m_bChangedByMenu;
+ bool m_bCustomRenderer;
+ bool m_bActivateCalled;
+ gint m_nTextCol;
+ gint m_nIdCol;
+ gulong m_nToggleFocusInSignalId;
+ gulong m_nToggleFocusOutSignalId;
+ gulong m_nRowActivatedSignalId;
+ gulong m_nChangedSignalId;
+ gulong m_nPopupShownSignalId;
+ gulong m_nKeyPressEventSignalId;
+ gulong m_nEntryInsertTextSignalId;
+ gulong m_nEntryActivateSignalId;
+ gulong m_nEntryFocusInSignalId;
+ gulong m_nEntryFocusOutSignalId;
+ gulong m_nEntryKeyPressEventSignalId;
+ guint m_nAutoCompleteIdleId;
+ gint m_nNonCustomLineHeight;
+ gint m_nPrePopupCursorPos;
+ int m_nMRUCount;
+ int m_nMaxMRUCount;
+
+ static gboolean idleAutoComplete(gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->auto_complete();
+ return false;
+ }
+
+ void auto_complete()
+ {
+ m_nAutoCompleteIdleId = 0;
+ OUString aStartText = get_active_text();
+ int nStartPos, nEndPos;
+ get_entry_selection_bounds(nStartPos, nEndPos);
+ int nMaxSelection = std::max(nStartPos, nEndPos);
+ if (nMaxSelection != aStartText.getLength())
+ return;
+
+ disable_notify_events();
+ int nActive = get_active();
+ int nStart = nActive;
+
+ if (nStart == -1)
+ nStart = 0;
+
+ int nPos = -1;
+
+ int nZeroRow = 0;
+ if (m_nMRUCount)
+ nZeroRow += (m_nMRUCount + 1);
+
+ if (!m_bAutoCompleteCaseSensitive)
+ {
+ // Try match case insensitive from current position
+ nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, false);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case insensitive, but from start
+ nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, false);
+ }
+ }
+
+ if (nPos == -1)
+ {
+ // Try match case sensitive from current position
+ nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, true);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case sensitive, but from start
+ nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, true);
+ }
+ }
+
+ if (nPos != -1)
+ {
+ OUString aText = get_text_including_mru(nPos);
+ if (aText != aStartText)
+ {
+ SolarMutexGuard aGuard;
+ set_active_including_mru(nPos, true);
+ }
+ select_entry_region(aText.getLength(), aStartText.getLength());
+ }
+ enable_notify_events();
+ }
+
+ static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
+ gint* position, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
+ }
+
+ void signal_entry_insert_text(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength, gint* position)
+ {
+ // first filter inserted text
+ if (m_aEntryInsertTextHdl.IsSet())
+ {
+ OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8);
+ const bool bContinue = m_aEntryInsertTextHdl.Call(sText);
+ if (bContinue && !sText.isEmpty())
+ {
+ OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8));
+ g_signal_handlers_block_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
+ gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position);
+ g_signal_handlers_unblock_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this);
+ }
+ g_signal_stop_emission_by_name(pEntry, "insert-text");
+ }
+ if (m_bAutoComplete)
+ {
+ // now check for autocompletes
+ if (m_nAutoCompleteIdleId)
+ g_source_remove(m_nAutoCompleteIdleId);
+ m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
+ }
+ }
+
+ static void signalChanged(GtkEntry*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->fire_signal_changed();
+ }
+
+ void fire_signal_changed()
+ {
+ signal_changed();
+ m_bChangedByMenu = false;
+ }
+
+ static void signalPopupToggled(GtkToggleButton* /*pToggleButton*/, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_popup_toggled();
+ }
+
+ int get_popup_height(gint& rPopupWidth)
+ {
+ const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
+
+ int nMaxRows = rSettings.GetListBoxMaximumLineCount();
+ bool bAddScrollWidth = false;
+ int nRows = get_count_including_mru();
+ if (nMaxRows < nRows)
+ {
+ nRows = nMaxRows;
+ bAddScrollWidth = true;
+ }
+
+ GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
+ gint nRowHeight = get_height_row(m_pTreeView, pColumns);
+ g_list_free(pColumns);
+
+ gint nSeparatorHeight = get_height_row_separator(m_pTreeView);
+ gint nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nRows);
+
+ // if we're using a custom renderer, limit the height to the height nMaxRows would be
+ // for a normal renderer, and then round down to how many custom rows fit in that
+ // space
+ if (m_nNonCustomLineHeight != -1 && nRowHeight)
+ {
+ gint nNormalHeight = get_height_rows(m_nNonCustomLineHeight, nSeparatorHeight, nMaxRows);
+ if (nHeight > nNormalHeight)
+ {
+ gint nRowsOnly = nNormalHeight - get_height_rows(0, nSeparatorHeight, nMaxRows);
+ gint nCustomRows = (nRowsOnly + (nRowHeight - 1)) / nRowHeight;
+ nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nCustomRows);
+ }
+ }
+
+ if (bAddScrollWidth)
+ rPopupWidth += rSettings.GetScrollBarSize();
+
+ return nHeight;
+ }
+
+ void menu_toggled()
+ {
+ if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton)))
+ {
+ if (m_bHoverSelection)
+ {
+ // turn hover selection back off until mouse is moved again
+ // *after* menu is shown again
+ gtk_tree_view_set_hover_selection(m_pTreeView, false);
+ m_bHoverSelection = false;
+ }
+
+ bool bHadFocus = gtk_window_has_toplevel_focus(m_pMenuWindow);
+
+ do_ungrab(GTK_WIDGET(m_pMenuWindow));
+
+ gtk_widget_hide(GTK_WIDGET(m_pMenuWindow));
+
+ GdkSurface* pSurface = widget_get_surface(GTK_WIDGET(m_pMenuWindow));
+ g_object_set_data(G_OBJECT(pSurface), "g-lo-InstancePopup", GINT_TO_POINTER(false));
+
+ // so gdk_window_move_to_rect will work again the next time
+ gtk_widget_unrealize(GTK_WIDGET(m_pMenuWindow));
+
+ gtk_widget_set_size_request(GTK_WIDGET(m_pMenuWindow), -1, -1);
+
+ if (!m_bActivateCalled)
+ tree_view_set_cursor(m_nPrePopupCursorPos);
+
+ // undo show_menu tooltip blocking
+ GtkWidget* pParent = widget_get_toplevel(m_pToggleButton);
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
+ if (pFrame)
+ pFrame->UnblockTooltip();
+
+ if (bHadFocus)
+ {
+ GdkSurface* pParentSurface = pParent ? widget_get_surface(pParent) : nullptr;
+ void* pParentIsPopover = pParentSurface ? g_object_get_data(G_OBJECT(pParentSurface), "g-lo-InstancePopup") : nullptr;
+ if (pParentIsPopover)
+ do_grab(m_pToggleButton);
+ gtk_widget_grab_focus(m_pToggleButton);
+ }
+ }
+ else
+ {
+ GtkWidget* pComboBox = GTK_WIDGET(getContainer());
+
+ gint nComboWidth = gtk_widget_get_allocated_width(pComboBox);
+ GtkRequisition size;
+ gtk_widget_get_preferred_size(GTK_WIDGET(m_pMenuWindow), nullptr, &size);
+
+ gint nPopupWidth = size.width;
+ gint nPopupHeight = get_popup_height(nPopupWidth);
+ nPopupWidth = std::max(nPopupWidth, nComboWidth);
+
+ gtk_widget_set_size_request(GTK_WIDGET(m_pMenuWindow), nPopupWidth, nPopupHeight);
+
+ m_nPrePopupCursorPos = get_active();
+
+ m_bActivateCalled = false;
+
+ // if we are in mru mode always start with the cursor at the top of the menu
+ if (m_nMaxMRUCount)
+ tree_view_set_cursor(0);
+
+ GdkRectangle aAnchor {0, 0, gtk_widget_get_allocated_width(pComboBox), gtk_widget_get_allocated_height(pComboBox) };
+ show_menu(pComboBox, m_pMenuWindow, aAnchor, weld::Placement::Under, true);
+ GdkSurface* pSurface = widget_get_surface(GTK_WIDGET(m_pMenuWindow));
+ g_object_set_data(G_OBJECT(pSurface), "g-lo-InstancePopup", GINT_TO_POINTER(true));
+ }
+ }
+
+ virtual void signal_popup_toggled() override
+ {
+ m_aQuickSelectionEngine.Reset();
+
+ menu_toggled();
+
+ bool bIsShown = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton));
+ if (m_bPopupActive == bIsShown)
+ return;
+
+ m_bPopupActive = bIsShown;
+ ComboBox::signal_popup_toggled();
+ if (!m_bPopupActive && m_pEntry)
+ {
+ disable_notify_events();
+ //restore focus to the GtkEntry when the popup is gone, which
+ //is what the vcl case does, to ease the transition a little
+ gtk_widget_grab_focus(m_pEntry);
+ enable_notify_events();
+ }
+ }
+
+ static gboolean signalEntryFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_entry_focus_in();
+ return false;
+ }
+
+ void signal_entry_focus_in()
+ {
+ signal_focus_in();
+ }
+
+ static gboolean signalEntryFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_entry_focus_out();
+ return false;
+ }
+
+ void signal_entry_focus_out()
+ {
+ // if we have an untidy selection on losing focus remove the selection
+ int nStartPos, nEndPos;
+ if (get_entry_selection_bounds(nStartPos, nEndPos))
+ {
+ int nMin = std::min(nStartPos, nEndPos);
+ int nMax = std::max(nStartPos, nEndPos);
+ if (nMin != 0 || nMax != get_active_text().getLength())
+ select_entry_region(0, 0);
+ }
+ signal_focus_out();
+ }
+
+ static void signalEntryActivate(GtkEntry*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_entry_activate();
+ }
+
+ void signal_entry_activate()
+ {
+ if (m_aEntryActivateHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ if (m_aEntryActivateHdl.Call(*this))
+ g_signal_stop_emission_by_name(m_pEntry, "activate");
+ }
+ update_mru();
+ }
+
+ OUString get(int pos, int col) const
+ {
+ OUString sRet;
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ {
+ gchar* pStr;
+ gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
+ sRet = OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ g_free(pStr);
+ }
+ return sRet;
+ }
+
+ void set(int pos, int col, std::u16string_view rText)
+ {
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos))
+ {
+ OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gtk_list_store_set(GTK_LIST_STORE(m_pTreeModel), &iter, col, aStr.getStr(), -1);
+ }
+ }
+
+ int find(std::u16string_view rStr, int col, bool bSearchMRUArea) const
+ {
+ GtkTreeIter iter;
+ if (!gtk_tree_model_get_iter_first(m_pTreeModel, &iter))
+ return -1;
+
+ int nRet = 0;
+
+ if (!bSearchMRUArea && m_nMRUCount)
+ {
+ if (!gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, m_nMRUCount + 1))
+ return -1;
+ nRet += (m_nMRUCount + 1);
+ }
+
+ OString aStr(OUStringToOString(rStr, RTL_TEXTENCODING_UTF8).getStr());
+ do
+ {
+ gchar* pStr;
+ gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
+ const bool bEqual = g_strcmp0(pStr, aStr.getStr()) == 0;
+ g_free(pStr);
+ if (bEqual)
+ return nRet;
+ ++nRet;
+ } while (gtk_tree_model_iter_next(m_pTreeModel, &iter));
+
+ return -1;
+ }
+
+ bool separator_function(const GtkTreePath* path)
+ {
+ return ::separator_function(path, m_aSeparatorRows);
+ }
+
+ bool separator_function(int pos)
+ {
+ GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1);
+ bool bRet = separator_function(path);
+ gtk_tree_path_free(path);
+ return bRet;
+ }
+
+ static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter);
+ bool bRet = pThis->separator_function(path);
+ gtk_tree_path_free(path);
+ return bRet;
+ }
+
+ // https://gitlab.gnome.org/GNOME/gtk/issues/310
+ //
+ // in the absence of a built-in solution
+ // a) support typeahead for the case where there is no entry widget, typing ahead
+ // into the button itself will select via the vcl selection engine, a matching
+ // entry
+ static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ return pThis->signal_key_press(pEvent);
+ }
+
+ // tdf#131076 we want return in a ComboBox to act like return in a
+ // GtkEntry and activate the default dialog/assistant button
+ bool combobox_activate()
+ {
+ GtkWidget *pComboBox = GTK_WIDGET(m_pToggleButton);
+ GtkWidget *pToplevel = widget_get_toplevel(pComboBox);
+ GtkWindow *pWindow = GTK_WINDOW(pToplevel);
+ if (!pWindow)
+ return false;
+ if (!GTK_IS_DIALOG(pWindow) && !GTK_IS_ASSISTANT(pWindow))
+ return false;
+ bool bDone = false;
+ GtkWidget *pDefaultWidget = gtk_window_get_default_widget(pWindow);
+ if (pDefaultWidget && pDefaultWidget != m_pToggleButton && gtk_widget_get_sensitive(pDefaultWidget))
+ bDone = gtk_widget_activate(pDefaultWidget);
+ return bDone;
+ }
+
+ static gboolean signalEntryKeyPress(GtkEntry* pEntry, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ LocalizeDecimalSeparator(pEvent->keyval);
+ if (signalEntryInsertSpecialCharKeyPress(pEntry, pEvent, nullptr))
+ return true;
+ return pThis->signal_entry_key_press(pEvent);
+ }
+
+ bool signal_entry_key_press(const GdkEventKey* pEvent)
+ {
+ KeyEvent aKEvt(GtkToVcl(*pEvent));
+
+ vcl::KeyCode aKeyCode = aKEvt.GetKeyCode();
+
+ bool bDone = false;
+
+ auto nCode = aKeyCode.GetCode();
+ switch (nCode)
+ {
+ case KEY_DOWN:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nCount = get_count_including_mru();
+ int nActive = get_active_including_mru() + 1;
+ while (nActive < nCount && separator_function(nActive))
+ ++nActive;
+ if (nActive < nCount)
+ set_active_including_mru(nActive, true);
+ bDone = true;
+ }
+ else if (nKeyMod == KEY_MOD2 && !m_bPopupActive)
+ {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), true);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_UP:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nStartBound = m_bPopupActive || !m_nMRUCount ? 0 : (m_nMRUCount + 1);
+ int nActive = get_active_including_mru() - 1;
+ while (nActive >= nStartBound && separator_function(nActive))
+ --nActive;
+ if (nActive >= nStartBound)
+ set_active_including_mru(nActive, true);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_PAGEUP:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nCount = get_count_including_mru();
+ int nStartBound = m_bPopupActive || !m_nMaxMRUCount ? 0 : (m_nMRUCount + 1);
+ int nActive = nStartBound;
+ while (nActive < nCount && separator_function(nActive))
+ ++nActive;
+ if (nActive < nCount)
+ set_active_including_mru(nActive, true);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_PAGEDOWN:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nActive = get_count_including_mru() - 1;
+ int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1);
+ while (nActive >= nStartBound && separator_function(nActive))
+ --nActive;
+ if (nActive >= nStartBound)
+ set_active_including_mru(nActive, true);
+ bDone = true;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return bDone;
+ }
+
+ bool signal_key_press(const GdkEventKey* pEvent)
+ {
+ if (m_bHoverSelection)
+ {
+ // once a key is pressed, turn off hover selection until mouse is
+ // moved again otherwise when the treeview scrolls it jumps to the
+ // position under the mouse.
+ gtk_tree_view_set_hover_selection(m_pTreeView, false);
+ m_bHoverSelection = false;
+ }
+
+ KeyEvent aKEvt(GtkToVcl(*pEvent));
+
+ vcl::KeyCode aKeyCode = aKEvt.GetKeyCode();
+
+ bool bDone = false;
+
+ auto nCode = aKeyCode.GetCode();
+ switch (nCode)
+ {
+ case KEY_DOWN:
+ case KEY_UP:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN:
+ case KEY_HOME:
+ case KEY_END:
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ case KEY_RETURN:
+ {
+ m_aQuickSelectionEngine.Reset();
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ // tdf#131076 don't let bare return toggle menu popup active, but do allow deactivate
+ if (nCode == KEY_RETURN && !nKeyMod && !m_bPopupActive)
+ bDone = combobox_activate();
+ else if (nCode == KEY_UP && nKeyMod == KEY_MOD2 && m_bPopupActive)
+ {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+ bDone = true;
+ }
+ else if (nCode == KEY_DOWN && nKeyMod == KEY_MOD2 && !m_bPopupActive)
+ {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), true);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_ESCAPE:
+ {
+ m_aQuickSelectionEngine.Reset();
+ if (m_bPopupActive)
+ {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+ bDone = true;
+ }
+ break;
+ }
+ default:
+ // tdf#131076 let base space toggle menu popup when it's not already visible
+ if (nCode == KEY_SPACE && !aKeyCode.GetModifier() && !m_bPopupActive)
+ bDone = false;
+ else
+ bDone = m_aQuickSelectionEngine.HandleKeyEvent(aKEvt);
+ break;
+ }
+
+ if (!bDone && !m_pEntry)
+ bDone = signal_entry_key_press(pEvent);
+
+ return bDone;
+ }
+
+ vcl::StringEntryIdentifier typeahead_getEntry(int nPos, OUString& out_entryText) const
+ {
+ int nEntryCount(get_count_including_mru());
+ if (nPos >= nEntryCount)
+ nPos = 0;
+ out_entryText = get_text_including_mru(nPos);
+
+ // vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based
+ // => normalize
+ return reinterpret_cast<vcl::StringEntryIdentifier>(nPos + 1);
+ }
+
+ static int typeahead_getEntryPos(vcl::StringEntryIdentifier entry)
+ {
+ // our pos is 0-based, but StringEntryIdentifier does not allow for a NULL
+ return reinterpret_cast<sal_Int64>(entry) - 1;
+ }
+
+ void tree_view_set_cursor(int pos)
+ {
+ GtkTreePath* path;
+ if (pos == -1)
+ {
+ path = gtk_tree_path_new_from_indices(G_MAXINT, -1);
+ gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(m_pTreeView));
+ if (m_pCellView)
+ gtk_cell_view_set_displayed_row(m_pCellView, nullptr);
+ }
+ else
+ {
+ path = gtk_tree_path_new_from_indices(pos, -1);
+ if (gtk_tree_view_get_model(m_pTreeView))
+ gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0);
+ if (m_pCellView)
+ gtk_cell_view_set_displayed_row(m_pCellView, path);
+ }
+ gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
+ gtk_tree_path_free(path);
+ }
+
+ int tree_view_get_cursor() const
+ {
+ int nRet = -1;
+
+ GtkTreePath* path;
+ gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
+ if (path)
+ {
+ gint depth;
+ gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth);
+ nRet = indices[depth-1];
+ gtk_tree_path_free(path);
+ }
+
+ return nRet;
+ }
+
+ int get_selected_entry() const
+ {
+ if (m_bPopupActive)
+ return tree_view_get_cursor();
+ else
+ return get_active_including_mru();
+ }
+
+ void set_typeahead_selected_entry(int nSelect)
+ {
+ if (m_bPopupActive)
+ tree_view_set_cursor(nSelect);
+ else
+ set_active_including_mru(nSelect, true);
+ }
+
+ virtual vcl::StringEntryIdentifier CurrentEntry(OUString& out_entryText) const override
+ {
+ int nCurrentPos = get_selected_entry();
+ return typeahead_getEntry((nCurrentPos == -1) ? 0 : nCurrentPos, out_entryText);
+ }
+
+ virtual vcl::StringEntryIdentifier NextEntry(vcl::StringEntryIdentifier currentEntry, OUString& out_entryText) const override
+ {
+ int nNextPos = typeahead_getEntryPos(currentEntry) + 1;
+ return typeahead_getEntry(nNextPos, out_entryText);
+ }
+
+ virtual void SelectEntry(vcl::StringEntryIdentifier entry) override
+ {
+ int nSelect = typeahead_getEntryPos(entry);
+ if (nSelect == get_selected_entry())
+ {
+ // ignore that. This method is a callback from the QuickSelectionEngine, which means the user attempted
+ // to select the given entry by typing its starting letters. No need to act.
+ return;
+ }
+
+ // normalize
+ int nCount = get_count_including_mru();
+ if (nSelect >= nCount)
+ nSelect = nCount ? nCount-1 : -1;
+
+ set_typeahead_selected_entry(nSelect);
+ }
+
+ static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->grab_broken(pEvent);
+ }
+
+ void grab_broken(const GdkEventGrabBroken *event)
+ {
+ if (event->grab_window == nullptr)
+ {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+ }
+ else if (!g_object_get_data(G_OBJECT(event->grab_window), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
+ {
+ //try and regrab, so when we lose the grab to the menu of the color palette
+ //combobox we regain it so the color palette doesn't itself disappear on next
+ //click on the color palette combobox
+ do_grab(GTK_WIDGET(m_pMenuWindow));
+ }
+ }
+
+ static gboolean signalButtonPress(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ return pThis->button_press(pEvent);
+ }
+
+ bool button_press(GdkEventButton* pEvent)
+ {
+ //we want to pop down if the button was pressed outside our popup
+ if (button_event_is_outside(GTK_WIDGET(m_pMenuWindow), pEvent))
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+ return false;
+ }
+
+ static gboolean signalMotion(GtkWidget*, GdkEventMotion*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_motion();
+ return false;
+ }
+
+ void signal_motion()
+ {
+ // if hover-selection was disabled after pressing a key, then turn it back on again
+ if (!m_bHoverSelection && !m_bMouseInOverlayButton)
+ {
+ gtk_tree_view_set_hover_selection(m_pTreeView, true);
+ m_bHoverSelection = true;
+ }
+ }
+
+ static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->handle_row_activated();
+ }
+
+ void handle_row_activated()
+ {
+ m_bActivateCalled = true;
+ m_bChangedByMenu = true;
+ disable_notify_events();
+ int nActive = get_active();
+ if (m_pEntry)
+ gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(get_text(nActive), RTL_TEXTENCODING_UTF8).getStr());
+ else
+ tree_view_set_cursor(nActive);
+ enable_notify_events();
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+ fire_signal_changed();
+ update_mru();
+ }
+
+ void do_clear()
+ {
+ disable_notify_events();
+ gtk_tree_view_set_row_separator_func(m_pTreeView, nullptr, nullptr, nullptr);
+ m_aSeparatorRows.clear();
+ gtk_list_store_clear(GTK_LIST_STORE(m_pTreeModel));
+ m_nMRUCount = 0;
+ enable_notify_events();
+ }
+
+ virtual int get_max_mru_count() const override
+ {
+ return m_nMaxMRUCount;
+ }
+
+ virtual void set_max_mru_count(int nMaxMRUCount) override
+ {
+ m_nMaxMRUCount = nMaxMRUCount;
+ update_mru();
+ }
+
+ void update_mru()
+ {
+ int nMRUCount = m_nMRUCount;
+
+ if (m_nMaxMRUCount)
+ {
+ OUString sActiveText = get_active_text();
+ OUString sActiveId = get_active_id();
+ insert_including_mru(0, sActiveText, &sActiveId, nullptr, nullptr);
+ ++m_nMRUCount;
+
+ for (int i = 1; i < m_nMRUCount - 1; ++i)
+ {
+ if (get_text_including_mru(i) == sActiveText)
+ {
+ remove_including_mru(i);
+ --m_nMRUCount;
+ break;
+ }
+ }
+ }
+
+ while (m_nMRUCount > m_nMaxMRUCount)
+ {
+ remove_including_mru(m_nMRUCount - 1);
+ --m_nMRUCount;
+ }
+
+ if (m_nMRUCount && !nMRUCount)
+ insert_separator_including_mru(m_nMRUCount, "separator");
+ else if (!m_nMRUCount && nMRUCount)
+ remove_including_mru(m_nMRUCount); // remove separator
+ }
+
+ int get_count_including_mru() const
+ {
+ return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr);
+ }
+
+ int get_active_including_mru() const
+ {
+ return tree_view_get_cursor();
+ }
+
+ void set_active_including_mru(int pos, bool bInteractive)
+ {
+ disable_notify_events();
+
+ tree_view_set_cursor(pos);
+
+ if (m_pEntry)
+ {
+ if (pos != -1)
+ gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(get_text_including_mru(pos), RTL_TEXTENCODING_UTF8).getStr());
+ else
+ gtk_entry_set_text(GTK_ENTRY(m_pEntry), "");
+ }
+
+ m_bChangedByMenu = false;
+ enable_notify_events();
+
+ if (bInteractive && !m_bPopupActive)
+ signal_changed();
+ }
+
+ int find_text_including_mru(std::u16string_view rStr, bool bSearchMRU) const
+ {
+ return find(rStr, m_nTextCol, bSearchMRU);
+ }
+
+ int find_id_including_mru(std::u16string_view rId, bool bSearchMRU) const
+ {
+ return find(rId, m_nIdCol, bSearchMRU);
+ }
+
+ OUString get_text_including_mru(int pos) const
+ {
+ return get(pos, m_nTextCol);
+ }
+
+ OUString get_id_including_mru(int pos) const
+ {
+ return get(pos, m_nIdCol);
+ }
+
+ void set_id_including_mru(int pos, std::u16string_view rId)
+ {
+ set(pos, m_nIdCol, rId);
+ }
+
+ void remove_including_mru(int pos)
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos);
+ if (!m_aSeparatorRows.empty())
+ {
+ bool bFound = false;
+
+ GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
+
+ for (auto aIter = m_aSeparatorRows.begin(); aIter != m_aSeparatorRows.end(); ++aIter)
+ {
+ GtkTreePath* seppath = gtk_tree_row_reference_get_path(aIter->get());
+ if (seppath)
+ {
+ if (gtk_tree_path_compare(pPath, seppath) == 0)
+ bFound = true;
+ gtk_tree_path_free(seppath);
+ }
+ if (bFound)
+ {
+ m_aSeparatorRows.erase(aIter);
+ break;
+ }
+ }
+
+ gtk_tree_path_free(pPath);
+ }
+ gtk_list_store_remove(GTK_LIST_STORE(m_pTreeModel), &iter);
+ enable_notify_events();
+ }
+
+ void insert_separator_including_mru(int pos, const OUString& rId)
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ if (!gtk_tree_view_get_row_separator_func(m_pTreeView))
+ gtk_tree_view_set_row_separator_func(m_pTreeView, separatorFunction, this, nullptr);
+ insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, &rId, u"", nullptr, nullptr);
+ GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1);
+ m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(m_pTreeModel, pPath));
+ gtk_tree_path_free(pPath);
+ enable_notify_events();
+ }
+
+ void insert_including_mru(int pos, std::u16string_view rText, const OUString* pId, const OUString* pIconName, const VirtualDevice* pImageSurface)
+ {
+ disable_notify_events();
+ GtkTreeIter iter;
+ insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, pId, rText, pIconName, pImageSurface);
+ enable_notify_events();
+ }
+
+ static gboolean signalGetChildPosition(GtkOverlay*, GtkWidget*, GdkRectangle* pAllocation, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ return pThis->signal_get_child_position(pAllocation);
+ }
+
+ bool signal_get_child_position(GdkRectangle* pAllocation)
+ {
+ if (!gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton)))
+ return false;
+ if (!gtk_widget_get_realized(GTK_WIDGET(m_pTreeView)))
+ return false;
+ int nRow = find_id_including_mru(m_sMenuButtonRow, true);
+ if (nRow == -1)
+ return false;
+
+ gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &pAllocation->width, nullptr);
+
+ GtkTreePath* pPath = gtk_tree_path_new_from_indices(nRow, -1);
+ GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
+ tools::Rectangle aRect = get_row_area(m_pTreeView, pColumns, pPath);
+ gtk_tree_path_free(pPath);
+ g_list_free(pColumns);
+
+ pAllocation->x = aRect.Right() - pAllocation->width;
+ pAllocation->y = aRect.Top();
+ pAllocation->height = aRect.GetHeight();
+
+ return true;
+ }
+
+ static gboolean signalOverlayButtonCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_overlay_button_crossing(pEvent->type == GDK_ENTER_NOTIFY);
+ return false;
+ }
+
+ void signal_overlay_button_crossing(bool bEnter)
+ {
+ m_bMouseInOverlayButton = bEnter;
+ if (!bEnter)
+ return;
+
+ if (m_bHoverSelection)
+ {
+ // once toggled button is pressed, turn off hover selection until
+ // mouse leaves the overlay button
+ gtk_tree_view_set_hover_selection(m_pTreeView, false);
+ m_bHoverSelection = false;
+ }
+ int nRow = find_id_including_mru(m_sMenuButtonRow, true);
+ assert(nRow != -1);
+ tree_view_set_cursor(nRow); // select the buttons row
+ }
+
+ void signal_combo_mnemonic_activate()
+ {
+ if (m_pEntry)
+ gtk_widget_grab_focus(m_pEntry);
+ else
+ gtk_widget_grab_focus(m_pToggleButton);
+ }
+
+ static gboolean signalComboMnemonicActivate(GtkWidget*, gboolean, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ pThis->signal_combo_mnemonic_activate();
+ return true;
+ }
+
+ static gboolean signalComboTooltipQuery(GtkWidget* /*pWidget*/, gint x, gint y,
+ gboolean keyboard_mode, GtkTooltip *tooltip,
+ gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ return signalTooltipQuery(GTK_WIDGET(pThis->m_pComboBox), x, y, keyboard_mode, tooltip);
+ }
+
+ int include_mru(int pos)
+ {
+ if (m_nMRUCount && pos != -1)
+ pos += (m_nMRUCount + 1);
+ return pos;
+ }
+
+public:
+ GtkInstanceComboBox(GtkBuilder* pComboBuilder, GtkComboBox* pComboBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceContainer(GTK_CONTAINER(gtk_builder_get_object(pComboBuilder, "box")), pBuilder, bTakeOwnership)
+ , m_pComboBuilder(pComboBuilder)
+ , m_pComboBox(pComboBox)
+ , m_pOverlay(GTK_OVERLAY(gtk_builder_get_object(pComboBuilder, "overlay")))
+ , m_pTreeView(GTK_TREE_VIEW(gtk_builder_get_object(pComboBuilder, "treeview")))
+ , m_pOverlayButton(GTK_MENU_BUTTON(gtk_builder_get_object(pComboBuilder, "overlaybutton")))
+ , m_pMenuWindow(GTK_WINDOW(gtk_builder_get_object(pComboBuilder, "popup")))
+ , m_pTreeModel(gtk_combo_box_get_model(pComboBox))
+ , m_pButtonTextRenderer(nullptr)
+ , m_pToggleButton(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "button")))
+ , m_pEntry(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "entry")))
+ , m_pCellView(nullptr)
+ , m_aCustomFont(m_pWidget)
+ , m_aQuickSelectionEngine(*this)
+ , m_bHoverSelection(false)
+ , m_bMouseInOverlayButton(false)
+ , m_bPopupActive(false)
+ , m_bAutoComplete(false)
+ , m_bAutoCompleteCaseSensitive(false)
+ , m_bChangedByMenu(false)
+ , m_bCustomRenderer(false)
+ , m_bActivateCalled(false)
+ , m_nTextCol(gtk_combo_box_get_entry_text_column(pComboBox))
+ , m_nIdCol(gtk_combo_box_get_id_column(pComboBox))
+ , m_nToggleFocusInSignalId(0)
+ , m_nToggleFocusOutSignalId(0)
+ , m_nRowActivatedSignalId(g_signal_connect(m_pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this))
+ , m_nChangedSignalId(g_signal_connect(m_pEntry, "changed", G_CALLBACK(signalChanged), this))
+ , m_nPopupShownSignalId(g_signal_connect(m_pToggleButton, "toggled", G_CALLBACK(signalPopupToggled), this))
+ , m_nAutoCompleteIdleId(0)
+ , m_nNonCustomLineHeight(-1)
+ , m_nPrePopupCursorPos(-1)
+ , m_nMRUCount(0)
+ , m_nMaxMRUCount(0)
+ {
+ int nActive = gtk_combo_box_get_active(m_pComboBox);
+
+ if (gtk_style_context_has_class(gtk_widget_get_style_context(GTK_WIDGET(m_pComboBox)), "small-button"))
+ gtk_style_context_add_class(gtk_widget_get_style_context(GTK_WIDGET(getContainer())), "small-button");
+
+ if (gtk_widget_get_has_tooltip(GTK_WIDGET(m_pComboBox)))
+ {
+ gtk_widget_set_has_tooltip(GTK_WIDGET(getContainer()), true);
+ g_signal_connect(getContainer(), "query-tooltip", G_CALLBACK(signalComboTooltipQuery), this);
+ }
+
+ insertAsParent(GTK_WIDGET(m_pComboBox), GTK_WIDGET(getContainer()));
+ gtk_widget_set_visible(GTK_WIDGET(m_pComboBox), false);
+ gtk_widget_set_no_show_all(GTK_WIDGET(m_pComboBox), true);
+
+ gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
+ /* tdf#136455 gtk_combo_box_set_model with a null Model should be good
+ enough. But in practice, while the ComboBox model is unset, GTK
+ doesn't unset the ComboBox menus model, so that remains listening to
+ additions to the ListStore and slowing things down massively.
+ Using a new model does reset the menu to listen to that unused one instead */
+ gtk_combo_box_set_model(m_pComboBox, GTK_TREE_MODEL(gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING)));
+
+ GtkTreeViewColumn* pCol = gtk_tree_view_column_new();
+ gtk_tree_view_append_column(m_pTreeView, pCol);
+
+ bool bPixbufUsedSurface = gtk_tree_model_get_n_columns(m_pTreeModel) == 4;
+
+ GList* cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_pComboBox));
+ // move the cell renderers from the combobox to the replacement treeview
+ m_pMenuTextRenderer = static_cast<GtkCellRenderer*>(cells->data);
+ for (GList* pRenderer = g_list_first(cells); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ bool bTextRenderer = pCellRenderer == m_pMenuTextRenderer;
+ gtk_tree_view_column_pack_end(pCol, pCellRenderer, bTextRenderer);
+ if (!bTextRenderer)
+ {
+ if (bPixbufUsedSurface)
+ gtk_tree_view_column_set_attributes(pCol, pCellRenderer, "surface", 3, nullptr);
+ else
+ gtk_tree_view_column_set_attributes(pCol, pCellRenderer, "pixbuf", 2, nullptr);
+ }
+ }
+
+ gtk_tree_view_column_set_attributes(pCol, m_pMenuTextRenderer, "text", m_nTextCol, nullptr);
+
+ if (gtk_combo_box_get_has_entry(m_pComboBox))
+ {
+ m_bAutoComplete = true;
+ m_nEntryInsertTextSignalId = g_signal_connect(m_pEntry, "insert-text", G_CALLBACK(signalEntryInsertText), this);
+ m_nEntryActivateSignalId = g_signal_connect(m_pEntry, "activate", G_CALLBACK(signalEntryActivate), this);
+ m_nEntryFocusInSignalId = g_signal_connect(m_pEntry, "focus-in-event", G_CALLBACK(signalEntryFocusIn), this);
+ m_nEntryFocusOutSignalId = g_signal_connect(m_pEntry, "focus-out-event", G_CALLBACK(signalEntryFocusOut), this);
+ m_nEntryKeyPressEventSignalId = g_signal_connect(m_pEntry, "key-press-event", G_CALLBACK(signalEntryKeyPress), this);
+ m_nKeyPressEventSignalId = 0;
+ }
+ else
+ {
+ gtk_widget_set_visible(m_pEntry, false);
+ m_pEntry = nullptr;
+
+ GtkWidget* pArrow = GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "arrow"));
+ gtk_container_child_set(getContainer(), m_pToggleButton, "expand", true, nullptr);
+
+ auto m_pCellArea = gtk_cell_area_box_new();
+ m_pCellView = GTK_CELL_VIEW(gtk_cell_view_new_with_context(m_pCellArea, nullptr));
+ gtk_widget_set_hexpand(GTK_WIDGET(m_pCellView), true);
+ GtkBox* pBox = GTK_BOX(gtk_widget_get_parent(pArrow));
+
+ gint nImageSpacing(2);
+ GtkStyleContext *pContext = gtk_widget_get_style_context(GTK_WIDGET(m_pToggleButton));
+ gtk_style_context_get_style(pContext, "image-spacing", &nImageSpacing, nullptr);
+ gtk_box_set_spacing(pBox, nImageSpacing);
+
+ gtk_box_pack_start(pBox, GTK_WIDGET(m_pCellView), false, true, 0);
+
+ gtk_cell_view_set_fit_model(m_pCellView, true);
+ gtk_cell_view_set_model(m_pCellView, m_pTreeModel);
+
+ m_pButtonTextRenderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pCellView), m_pButtonTextRenderer, true);
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), m_pButtonTextRenderer, "text", m_nTextCol, nullptr);
+ if (g_list_length(cells) > 1)
+ {
+ GtkCellRenderer* pCellRenderer = gtk_cell_renderer_pixbuf_new();
+ gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, false);
+ if (bPixbufUsedSurface)
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, "surface", 3, nullptr);
+ else
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, "pixbuf", 2, nullptr);
+ }
+
+ gtk_widget_show_all(GTK_WIDGET(m_pCellView));
+
+ m_nEntryInsertTextSignalId = 0;
+ m_nEntryActivateSignalId = 0;
+ m_nEntryFocusInSignalId = 0;
+ m_nEntryFocusOutSignalId = 0;
+ m_nEntryKeyPressEventSignalId = 0;
+ m_nKeyPressEventSignalId = g_signal_connect(m_pToggleButton, "key-press-event", G_CALLBACK(signalKeyPress), this);
+ }
+
+ g_list_free(cells);
+
+ if (nActive != -1)
+ tree_view_set_cursor(nActive);
+
+ g_signal_connect(getContainer(), "mnemonic-activate", G_CALLBACK(signalComboMnemonicActivate), this);
+
+ g_signal_connect(m_pMenuWindow, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
+ g_signal_connect(m_pMenuWindow, "button-press-event", G_CALLBACK(signalButtonPress), this);
+ g_signal_connect(m_pMenuWindow, "motion-notify-event", G_CALLBACK(signalMotion), this);
+ // support typeahead for the menu itself, typing into the menu will
+ // select via the vcl selection engine, a matching entry.
+ g_signal_connect(m_pMenuWindow, "key-press-event", G_CALLBACK(signalKeyPress), this);
+
+ g_signal_connect(m_pOverlay, "get-child-position", G_CALLBACK(signalGetChildPosition), this);
+ gtk_overlay_add_overlay(m_pOverlay, GTK_WIDGET(m_pOverlayButton));
+ g_signal_connect(m_pOverlayButton, "leave-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
+ g_signal_connect(m_pOverlayButton, "enter-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this);
+ }
+
+ virtual int get_active() const override
+ {
+ int nActive = get_active_including_mru();
+ if (nActive == -1)
+ return -1;
+
+ if (m_nMRUCount)
+ {
+ if (nActive < m_nMRUCount)
+ nActive = find_text(get_text_including_mru(nActive));
+ else
+ nActive -= (m_nMRUCount + 1);
+ }
+
+ return nActive;
+ }
+
+ virtual OUString get_active_id() const override
+ {
+ int nActive = get_active();
+ return nActive != -1 ? get_id(nActive) : OUString();
+ }
+
+ virtual void set_active_id(const OUString& rStr) override
+ {
+ set_active(find_id(rStr));
+ m_bChangedByMenu = false;
+ }
+
+ virtual void set_size_request(int nWidth, int nHeight) override
+ {
+ if (m_pButtonTextRenderer)
+ {
+ // tweak the cell render to get a narrower size to stick
+ if (nWidth != -1)
+ {
+ // this bit isn't great, I really want to be able to ellipse the text in the comboboxtext itself and let
+ // the popup menu render them in full, in the interim ellipse both of them
+ g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_MIDDLE, nullptr);
+
+ // to find out how much of the width of the combobox belongs to the cell, set
+ // the cell and widget to the min cell width and see what the difference is
+ int min;
+ gtk_cell_renderer_get_preferred_width(m_pButtonTextRenderer, m_pWidget, &min, nullptr);
+ gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, min, -1);
+ gtk_widget_set_size_request(m_pWidget, min, -1);
+ int nNonCellWidth = get_preferred_size().Width() - min;
+
+ int nCellWidth = nWidth - nNonCellWidth;
+ if (nCellWidth >= 0)
+ {
+ // now set the cell to the max width which it can be within the
+ // requested widget width
+ gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, nWidth - nNonCellWidth, -1);
+ }
+ }
+ else
+ {
+ g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_NONE, nullptr);
+ gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, -1, -1);
+ }
+ }
+
+ gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
+ }
+
+ virtual void set_active(int pos) override
+ {
+ set_active_including_mru(include_mru(pos), false);
+ }
+
+ virtual OUString get_active_text() const override
+ {
+ if (m_pEntry)
+ {
+ const gchar* pText = gtk_entry_get_text(GTK_ENTRY(m_pEntry));
+ return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ int nActive = get_active();
+ if (nActive == -1)
+ return OUString();
+
+ return get_text(nActive);
+ }
+
+ virtual OUString get_text(int pos) const override
+ {
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ return get_text_including_mru(pos);
+ }
+
+ virtual OUString get_id(int pos) const override
+ {
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ return get_id_including_mru(pos);
+ }
+
+ virtual void set_id(int pos, const OUString& rId) override
+ {
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ set_id_including_mru(pos, rId);
+ }
+
+ virtual void insert_vector(const std::vector<weld::ComboBoxEntry>& rItems, bool bKeepExisting) override
+ {
+ freeze();
+
+ int nInsertionPoint;
+ if (!bKeepExisting)
+ {
+ clear();
+ nInsertionPoint = 0;
+ }
+ else
+ nInsertionPoint = get_count();
+
+ GtkTreeIter iter;
+ // tdf#125241 inserting backwards is faster
+ for (auto aI = rItems.rbegin(); aI != rItems.rend(); ++aI)
+ {
+ const auto& rItem = *aI;
+ insert_row(GTK_LIST_STORE(m_pTreeModel), iter, nInsertionPoint, rItem.sId.isEmpty() ? nullptr : &rItem.sId,
+ rItem.sString, rItem.sImage.isEmpty() ? nullptr : &rItem.sImage, nullptr);
+ }
+
+ thaw();
+ }
+
+ virtual void remove(int pos) override
+ {
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ remove_including_mru(pos);
+ }
+
+ virtual void insert(int pos, const OUString& rText, const OUString* pId, const OUString* pIconName, VirtualDevice* pImageSurface) override
+ {
+ insert_including_mru(include_mru(pos), rText, pId, pIconName, pImageSurface);
+ }
+
+ virtual void insert_separator(int pos, const OUString& rId) override
+ {
+ pos = pos == -1 ? get_count() : pos;
+ if (m_nMRUCount)
+ pos += (m_nMRUCount + 1);
+ insert_separator_including_mru(pos, rId);
+ }
+
+ virtual int get_count() const override
+ {
+ int nCount = get_count_including_mru();
+ if (m_nMRUCount)
+ nCount -= (m_nMRUCount + 1);
+ return nCount;
+ }
+
+ virtual int find_text(const OUString& rStr) const override
+ {
+ int nPos = find_text_including_mru(rStr, false);
+ if (nPos != -1 && m_nMRUCount)
+ nPos -= (m_nMRUCount + 1);
+ return nPos;
+ }
+
+ virtual int find_id(const OUString& rId) const override
+ {
+ int nPos = find_id_including_mru(rId, false);
+ if (nPos != -1 && m_nMRUCount)
+ nPos -= (m_nMRUCount + 1);
+ return nPos;
+ }
+
+ virtual void clear() override
+ {
+ do_clear();
+ }
+
+ virtual void make_sorted() override
+ {
+ m_xSorter.reset(new comphelper::string::NaturalStringSorter(
+ ::comphelper::getProcessComponentContext(),
+ Application::GetSettings().GetUILanguageTag().getLocale()));
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
+ gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, default_sort_func, m_xSorter.get(), nullptr);
+ }
+
+ virtual bool has_entry() const override
+ {
+ return gtk_combo_box_get_has_entry(m_pComboBox);
+ }
+
+ virtual void set_entry_message_type(weld::EntryMessageType eType) override
+ {
+ assert(m_pEntry);
+ ::set_entry_message_type(GTK_ENTRY(m_pEntry), eType);
+ }
+
+ virtual void set_entry_text(const OUString& rText) override
+ {
+ assert(m_pEntry);
+ disable_notify_events();
+ gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+ enable_notify_events();
+ }
+
+ virtual void set_entry_width_chars(int nChars) override
+ {
+ assert(m_pEntry);
+ disable_notify_events();
+ gtk_entry_set_width_chars(GTK_ENTRY(m_pEntry), nChars);
+ gtk_entry_set_max_width_chars(GTK_ENTRY(m_pEntry), nChars);
+ enable_notify_events();
+ }
+
+ virtual void set_entry_max_length(int nChars) override
+ {
+ assert(m_pEntry);
+ disable_notify_events();
+ gtk_entry_set_max_length(GTK_ENTRY(m_pEntry), nChars);
+ enable_notify_events();
+ }
+
+ virtual void select_entry_region(int nStartPos, int nEndPos) override
+ {
+ assert(m_pEntry);
+ disable_notify_events();
+ gtk_editable_select_region(GTK_EDITABLE(m_pEntry), nStartPos, nEndPos);
+ enable_notify_events();
+ }
+
+ virtual bool get_entry_selection_bounds(int& rStartPos, int &rEndPos) override
+ {
+ assert(m_pEntry);
+ return gtk_editable_get_selection_bounds(GTK_EDITABLE(m_pEntry), &rStartPos, &rEndPos);
+ }
+
+ virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
+ {
+ m_bAutoComplete = bEnable;
+ m_bAutoCompleteCaseSensitive = bCaseSensitive;
+ }
+
+ virtual void set_entry_placeholder_text(const OUString& rText) override
+ {
+ assert(m_pEntry);
+ gtk_entry_set_placeholder_text(GTK_ENTRY(m_pEntry), rText.toUtf8().getStr());
+ }
+
+ virtual void set_entry_editable(bool bEditable) override
+ {
+ assert(m_pEntry);
+ gtk_editable_set_editable(GTK_EDITABLE(m_pEntry), bEditable);
+ }
+
+ virtual void cut_entry_clipboard() override
+ {
+ assert(m_pEntry);
+ gtk_editable_cut_clipboard(GTK_EDITABLE(m_pEntry));
+ }
+
+ virtual void copy_entry_clipboard() override
+ {
+ assert(m_pEntry);
+ gtk_editable_copy_clipboard(GTK_EDITABLE(m_pEntry));
+ }
+
+ virtual void paste_entry_clipboard() override
+ {
+ assert(m_pEntry);
+ gtk_editable_paste_clipboard(GTK_EDITABLE(m_pEntry));
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_aCustomFont.use_custom_font(&rFont, u"box#combobox");
+ }
+
+ virtual vcl::Font get_font() override
+ {
+ if (const vcl::Font* pFont = m_aCustomFont.get_custom_font())
+ return *pFont;
+ return GtkInstanceWidget::get_font();
+ }
+
+ virtual void set_entry_font(const vcl::Font& rFont) override
+ {
+ m_xEntryFont = rFont;
+ assert(m_pEntry);
+ PangoAttrList* pOrigList = gtk_entry_get_attributes(GTK_ENTRY(m_pEntry));
+ PangoAttrList* pAttrList = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new();
+ update_attr_list(pAttrList, rFont);
+ gtk_entry_set_attributes(GTK_ENTRY(m_pEntry), pAttrList);
+ pango_attr_list_unref(pAttrList);
+ }
+
+ virtual vcl::Font get_entry_font() override
+ {
+ if (m_xEntryFont)
+ return *m_xEntryFont;
+ assert(m_pEntry);
+ PangoContext* pContext = gtk_widget_get_pango_context(m_pEntry);
+ return pango_to_vcl(pango_context_get_font_description(pContext),
+ Application::GetSettings().GetUILanguageTag().getLocale());
+ }
+
+ virtual void disable_notify_events() override
+ {
+ if (m_pEntry)
+ {
+ g_signal_handler_block(m_pEntry, m_nEntryInsertTextSignalId);
+ g_signal_handler_block(m_pEntry, m_nEntryActivateSignalId);
+ g_signal_handler_block(m_pEntry, m_nEntryFocusInSignalId);
+ g_signal_handler_block(m_pEntry, m_nEntryFocusOutSignalId);
+ g_signal_handler_block(m_pEntry, m_nEntryKeyPressEventSignalId);
+ g_signal_handler_block(m_pEntry, m_nChangedSignalId);
+ }
+ else
+ g_signal_handler_block(m_pToggleButton, m_nKeyPressEventSignalId);
+ if (m_nToggleFocusInSignalId)
+ g_signal_handler_block(m_pToggleButton, m_nToggleFocusInSignalId);
+ if (m_nToggleFocusOutSignalId)
+ g_signal_handler_block(m_pToggleButton, m_nToggleFocusOutSignalId);
+ g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId);
+ g_signal_handler_block(m_pToggleButton, m_nPopupShownSignalId);
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::enable_notify_events();
+ g_signal_handler_unblock(m_pToggleButton, m_nPopupShownSignalId);
+ g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId);
+ if (m_nToggleFocusInSignalId)
+ g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusInSignalId);
+ if (m_nToggleFocusOutSignalId)
+ g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusOutSignalId);
+ if (m_pEntry)
+ {
+ g_signal_handler_unblock(m_pEntry, m_nChangedSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nEntryActivateSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nEntryFocusInSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nEntryFocusOutSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nEntryKeyPressEventSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nEntryInsertTextSignalId);
+ }
+ else
+ g_signal_handler_unblock(m_pToggleButton, m_nKeyPressEventSignalId);
+ }
+
+ virtual void freeze() override
+ {
+ disable_notify_events();
+ bool bIsFirstFreeze = IsFirstFreeze();
+ GtkInstanceContainer::freeze();
+ if (bIsFirstFreeze)
+ {
+ g_object_ref(m_pTreeModel);
+ gtk_tree_view_set_model(m_pTreeView, nullptr);
+ g_object_freeze_notify(G_OBJECT(m_pTreeModel));
+ if (m_xSorter)
+ {
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
+ }
+ }
+ enable_notify_events();
+ }
+
+ virtual void thaw() override
+ {
+ disable_notify_events();
+ if (IsLastThaw())
+ {
+ if (m_xSorter)
+ {
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
+ }
+ g_object_thaw_notify(G_OBJECT(m_pTreeModel));
+ gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
+ g_object_unref(m_pTreeModel);
+ }
+ GtkInstanceContainer::thaw();
+ enable_notify_events();
+ }
+
+ virtual bool get_popup_shown() const override
+ {
+ return m_bPopupActive;
+ }
+
+ virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
+ {
+ if (!m_nToggleFocusInSignalId)
+ m_nToggleFocusInSignalId = g_signal_connect_after(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this);
+ GtkInstanceContainer::connect_focus_in(rLink);
+ }
+
+ virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
+ {
+ if (!m_nToggleFocusOutSignalId)
+ m_nToggleFocusOutSignalId = g_signal_connect_after(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this);
+ GtkInstanceContainer::connect_focus_out(rLink);
+ }
+
+ virtual void grab_focus() override
+ {
+ if (has_focus())
+ return;
+ if (m_pEntry)
+ gtk_widget_grab_focus(m_pEntry);
+ else
+ gtk_widget_grab_focus(m_pToggleButton);
+ }
+
+ virtual bool has_focus() const override
+ {
+ if (m_pEntry && gtk_widget_has_focus(m_pEntry))
+ return true;
+
+ if (gtk_widget_has_focus(m_pToggleButton))
+ return true;
+
+ if (gtk_widget_get_visible(GTK_WIDGET(m_pMenuWindow)))
+ {
+ if (gtk_widget_has_focus(GTK_WIDGET(m_pOverlayButton)) || gtk_widget_has_focus(GTK_WIDGET(m_pTreeView)))
+ return true;
+ }
+
+ return GtkInstanceWidget::has_focus();
+ }
+
+ virtual bool changed_by_direct_pick() const override
+ {
+ return m_bChangedByMenu;
+ }
+
+ virtual void set_custom_renderer(bool bOn) override
+ {
+ if (bOn == m_bCustomRenderer)
+ return;
+ GList* pColumns = gtk_tree_view_get_columns(m_pTreeView);
+ // keep the original height around for optimal popup height calculation
+ m_nNonCustomLineHeight = bOn ? ::get_height_row(m_pTreeView, pColumns) : -1;
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pColumns->data);
+ gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn));
+ if (bOn)
+ {
+ GtkCellRenderer *pRenderer = custom_cell_renderer_new();
+ GValue value = G_VALUE_INIT;
+ g_value_init(&value, G_TYPE_POINTER);
+ g_value_set_pointer(&value, static_cast<gpointer>(this));
+ g_object_set_property(G_OBJECT(pRenderer), "instance", &value);
+ gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "id", m_nIdCol);
+ }
+ else
+ {
+ GtkCellRenderer *pRenderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(pColumn, pRenderer, true);
+ gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol);
+ }
+ g_list_free(pColumns);
+ m_bCustomRenderer = bOn;
+ }
+
+ void call_signal_custom_render(VirtualDevice& rOutput, const tools::Rectangle& rRect, bool bSelected, const OUString& rId)
+ {
+ signal_custom_render(rOutput, rRect, bSelected, rId);
+ }
+
+ Size call_signal_custom_get_size(VirtualDevice& rOutput)
+ {
+ return signal_custom_get_size(rOutput);
+ }
+
+ VclPtr<VirtualDevice> create_render_virtual_device() const override
+ {
+ return create_virtual_device();
+ }
+
+ virtual void set_item_menu(const OString& rIdent, weld::Menu* pMenu) override
+ {
+ m_xCustomMenuButtonHelper.reset();
+ GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
+ GtkWidget* pMenuWidget = GTK_WIDGET(pPopoverWidget ? pPopoverWidget->getMenu() : nullptr);
+ gtk_menu_button_set_popup(m_pOverlayButton, pMenuWidget);
+ gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), pMenuWidget != nullptr);
+ gtk_widget_queue_resize_no_redraw(GTK_WIDGET(m_pOverlayButton)); // force location recalc
+ if (pMenuWidget)
+ m_xCustomMenuButtonHelper.reset(new CustomRenderMenuButtonHelper(GTK_MENU(pMenuWidget), GTK_TOGGLE_BUTTON(m_pToggleButton)));
+ m_sMenuButtonRow = OUString::fromUtf8(rIdent);
+ }
+
+ OUString get_mru_entries() const override
+ {
+ const sal_Unicode cSep = ';';
+
+ OUStringBuffer aEntries;
+ for (sal_Int32 n = 0; n < m_nMRUCount; n++)
+ {
+ aEntries.append(get_text_including_mru(n));
+ if (n < m_nMRUCount - 1)
+ aEntries.append(cSep);
+ }
+ return aEntries.makeStringAndClear();
+ }
+
+ virtual void set_mru_entries(const OUString& rEntries) override
+ {
+ const sal_Unicode cSep = ';';
+
+ // Remove old MRU entries
+ for (sal_Int32 n = m_nMRUCount; n;)
+ remove_including_mru(--n);
+
+ sal_Int32 nMRUCount = 0;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OUString aEntry = rEntries.getToken(0, cSep, nIndex);
+ // Accept only existing entries
+ int nPos = find_text(aEntry);
+ if (nPos != -1)
+ {
+ OUString sId = get_id(nPos);
+ insert_including_mru(0, aEntry, &sId, nullptr, nullptr);
+ ++nMRUCount;
+ }
+ }
+ while (nIndex >= 0);
+
+ if (nMRUCount && !m_nMRUCount)
+ insert_separator_including_mru(nMRUCount, "separator");
+ else if (!nMRUCount && m_nMRUCount)
+ remove_including_mru(m_nMRUCount); // remove separator
+
+ m_nMRUCount = nMRUCount;
+ }
+
+ int get_menu_button_width() const override
+ {
+ bool bVisible = gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton));
+ if (!bVisible)
+ gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), true);
+ gint nWidth;
+ gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &nWidth, nullptr);
+ if (!bVisible)
+ gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), false);
+ return nWidth;
+ }
+
+ virtual ~GtkInstanceComboBox() override
+ {
+ m_xCustomMenuButtonHelper.reset();
+ do_clear();
+ if (m_nAutoCompleteIdleId)
+ g_source_remove(m_nAutoCompleteIdleId);
+ if (m_pEntry)
+ {
+ g_signal_handler_disconnect(m_pEntry, m_nChangedSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nEntryInsertTextSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nEntryActivateSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nEntryFocusInSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nEntryFocusOutSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nEntryKeyPressEventSignalId);
+ }
+ else
+ g_signal_handler_disconnect(m_pToggleButton, m_nKeyPressEventSignalId);
+ if (m_nToggleFocusInSignalId)
+ g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusInSignalId);
+ if (m_nToggleFocusOutSignalId)
+ g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusOutSignalId);
+ g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
+ g_signal_handler_disconnect(m_pToggleButton, m_nPopupShownSignalId);
+
+ gtk_combo_box_set_model(m_pComboBox, m_pTreeModel);
+ gtk_tree_view_set_model(m_pTreeView, nullptr);
+
+ // restore original hierarchy in dtor so a new GtkInstanceComboBox will
+ // result in the same layout each time
+ {
+ DisconnectMouseEvents();
+
+ g_object_ref(m_pComboBox);
+
+ GtkContainer* pContainer = getContainer();
+
+ gtk_container_remove(pContainer, GTK_WIDGET(m_pComboBox));
+
+ replaceWidget(GTK_WIDGET(pContainer), GTK_WIDGET(m_pComboBox));
+
+ g_object_unref(m_pComboBox);
+ }
+
+ g_object_unref(m_pComboBuilder);
+ }
+};
+
+#endif
+
+}
+
+void custom_cell_renderer_ensure_device(CustomCellRenderer *cellsurface, gpointer user_data)
+{
+ if (!cellsurface->device)
+ {
+ cellsurface->device = VclPtr<VirtualDevice>::Create();
+ cellsurface->device->SetBackground(COL_TRANSPARENT);
+ GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(user_data);
+ // expand the point size of the desired font to the equivalent pixel size
+ weld::SetPointFont(*cellsurface->device, pWidget->get_font());
+ }
+}
+
+Size custom_cell_renderer_get_size(VirtualDevice& rDevice, const OUString& rCellId, gpointer user_data)
+{
+ GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(user_data);
+ if (GtkInstanceTreeView* pTreeView = dynamic_cast<GtkInstanceTreeView*>(pWidget))
+ return pTreeView->call_signal_custom_get_size(rDevice, rCellId);
+ else if (GtkInstanceComboBox* pComboBox = dynamic_cast<GtkInstanceComboBox*>(pWidget))
+ return pComboBox->call_signal_custom_get_size(rDevice);
+ return Size();
+}
+
+void custom_cell_renderer_render(VirtualDevice& rDevice, const tools::Rectangle& rRect, bool bSelected, const OUString& rCellId, gpointer user_data)
+{
+ GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(user_data);
+ if (GtkInstanceTreeView* pTreeView = dynamic_cast<GtkInstanceTreeView*>(pWidget))
+ pTreeView->call_signal_custom_render(rDevice, rRect, bSelected, rCellId);
+ else if (GtkInstanceComboBox* pComboBox = dynamic_cast<GtkInstanceComboBox*>(pWidget))
+ pComboBox->call_signal_custom_render(rDevice, rRect, bSelected, rCellId);
+}
+
+namespace {
+
+class GtkInstanceEntryTreeView : public GtkInstanceContainer, public virtual weld::EntryTreeView
+{
+private:
+ GtkInstanceEntry* m_pEntry;
+ GtkInstanceTreeView* m_pTreeView;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nKeyPressSignalId;
+#endif
+ gulong m_nEntryInsertTextSignalId;
+ guint m_nAutoCompleteIdleId;
+ bool m_bAutoCompleteCaseSensitive;
+ bool m_bTreeChange;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ bool signal_key_press(GdkEventKey* pEvent)
+ {
+ if (GtkSalFrame::GetMouseModCode(pEvent->state)) // only with no modifiers held
+ return false;
+
+ if (pEvent->keyval == GDK_KEY_KP_Up || pEvent->keyval == GDK_KEY_Up || pEvent->keyval == GDK_KEY_KP_Page_Up || pEvent->keyval == GDK_KEY_Page_Up ||
+ pEvent->keyval == GDK_KEY_KP_Down || pEvent->keyval == GDK_KEY_Down || pEvent->keyval == GDK_KEY_KP_Page_Down || pEvent->keyval == GDK_KEY_Page_Down)
+ {
+ gboolean ret;
+ disable_notify_events();
+ GtkWidget* pWidget = m_pTreeView->getWidget();
+ if (m_pTreeView->get_selected_index() == -1)
+ {
+ m_pTreeView->set_cursor(0);
+ m_pTreeView->select(0);
+ m_xEntry->set_text(m_xTreeView->get_selected_text());
+ }
+ else
+ {
+ gtk_widget_grab_focus(pWidget);
+ g_signal_emit_by_name(pWidget, "key-press-event", pEvent, &ret);
+ m_xEntry->set_text(m_xTreeView->get_selected_text());
+ gtk_widget_grab_focus(m_pEntry->getWidget());
+ }
+ m_xEntry->select_region(0, -1);
+ enable_notify_events();
+ m_bTreeChange = true;
+ m_pEntry->fire_signal_changed();
+ m_bTreeChange = false;
+ return true;
+ }
+ return false;
+ }
+
+ static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
+ return pThis->signal_key_press(pEvent);
+ }
+#endif
+
+ static gboolean idleAutoComplete(gpointer widget)
+ {
+ GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
+ pThis->auto_complete();
+ return false;
+ }
+
+ void auto_complete()
+ {
+ m_nAutoCompleteIdleId = 0;
+ OUString aStartText = get_active_text();
+ int nStartPos, nEndPos;
+ get_entry_selection_bounds(nStartPos, nEndPos);
+ int nMaxSelection = std::max(nStartPos, nEndPos);
+ if (nMaxSelection != aStartText.getLength())
+ return;
+
+ disable_notify_events();
+ int nActive = get_active();
+ int nStart = nActive;
+
+ if (nStart == -1)
+ nStart = 0;
+
+ // Try match case sensitive from current position
+ int nPos = m_pTreeView->starts_with(aStartText, nStart, true);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case insensitive, but from start
+ nPos = m_pTreeView->starts_with(aStartText, 0, true);
+ }
+
+ if (!m_bAutoCompleteCaseSensitive)
+ {
+ // Try match case insensitive from current position
+ nPos = m_pTreeView->starts_with(aStartText, nStart, false);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case insensitive, but from start
+ nPos = m_pTreeView->starts_with(aStartText, 0, false);
+ }
+ }
+
+ if (nPos == -1)
+ {
+ // Try match case sensitive from current position
+ nPos = m_pTreeView->starts_with(aStartText, nStart, true);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case sensitive, but from start
+ nPos = m_pTreeView->starts_with(aStartText, 0, true);
+ }
+ }
+
+ if (nPos != -1)
+ {
+ OUString aText = get_text(nPos);
+ if (aText != aStartText)
+ set_active_text(aText);
+ select_entry_region(aText.getLength(), aStartText.getLength());
+ }
+ enable_notify_events();
+ }
+
+ void signal_entry_insert_text(GtkEntry*, const gchar*, gint, gint*)
+ {
+ // now check for autocompletes
+ if (m_nAutoCompleteIdleId)
+ g_source_remove(m_nAutoCompleteIdleId);
+ m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this);
+ }
+
+ static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
+ gint* position, gpointer widget)
+ {
+ GtkInstanceEntryTreeView* pThis = static_cast<GtkInstanceEntryTreeView*>(widget);
+ pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position);
+ }
+
+
+public:
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkInstanceEntryTreeView(GtkWidget* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership,
+ std::unique_ptr<weld::Entry> xEntry, std::unique_ptr<weld::TreeView> xTreeView)
+#else
+ GtkInstanceEntryTreeView(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership,
+ std::unique_ptr<weld::Entry> xEntry, std::unique_ptr<weld::TreeView> xTreeView)
+#endif
+ : EntryTreeView(std::move(xEntry), std::move(xTreeView))
+ , GtkInstanceContainer(pContainer, pBuilder, bTakeOwnership)
+ , m_pEntry(dynamic_cast<GtkInstanceEntry*>(m_xEntry.get()))
+ , m_pTreeView(dynamic_cast<GtkInstanceTreeView*>(m_xTreeView.get()))
+ , m_nAutoCompleteIdleId(0)
+ , m_bAutoCompleteCaseSensitive(false)
+ , m_bTreeChange(false)
+ {
+ assert(m_pEntry);
+ GtkWidget* pWidget = m_pEntry->getWidget();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_nKeyPressSignalId = g_signal_connect(pWidget, "key-press-event", G_CALLBACK(signalKeyPress), this);
+#endif
+ m_nEntryInsertTextSignalId = g_signal_connect(pWidget, "insert-text", G_CALLBACK(signalEntryInsertText), this);
+ }
+
+ virtual void insert_separator(int /*pos*/, const OUString& /*rId*/) override
+ {
+ assert(false);
+ }
+
+ virtual void make_sorted() override
+ {
+ GtkWidget* pTreeView = m_pTreeView->getWidget();
+ GtkTreeModel* pModel = gtk_tree_view_get_model(GTK_TREE_VIEW(pTreeView));
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(pModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, 1, GTK_SORT_ASCENDING);
+ }
+
+ virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override
+ {
+ assert(!bEnable && "not implemented yet"); (void)bEnable;
+ m_bAutoCompleteCaseSensitive = bCaseSensitive;
+ }
+
+ virtual void set_entry_placeholder_text(const OUString& rText) override
+ {
+ m_xEntry->set_placeholder_text(rText);
+ }
+
+ virtual void set_entry_editable(bool bEditable) override
+ {
+ m_xEntry->set_editable(bEditable);
+ }
+
+ virtual void cut_entry_clipboard() override
+ {
+ m_xEntry->cut_clipboard();
+ }
+
+ virtual void copy_entry_clipboard() override
+ {
+ m_xEntry->copy_clipboard();
+ }
+
+ virtual void paste_entry_clipboard() override
+ {
+ m_xEntry->paste_clipboard();
+ }
+
+ virtual void set_font(const vcl::Font&) override
+ {
+ assert(false && "not implemented");
+ }
+
+ virtual void set_entry_font(const vcl::Font& rFont) override
+ {
+ m_xEntry->set_font(rFont);
+ }
+
+ virtual vcl::Font get_entry_font() override
+ {
+ return m_xEntry->get_font();
+ }
+
+ virtual void grab_focus() override { m_xEntry->grab_focus(); }
+
+ virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
+ {
+ m_xEntry->connect_focus_in(rLink);
+ }
+
+ virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
+ {
+ m_xEntry->connect_focus_out(rLink);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ GtkWidget* pWidget = m_pEntry->getWidget();
+ g_signal_handler_block(pWidget, m_nEntryInsertTextSignalId);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_block(pWidget, m_nKeyPressSignalId);
+#endif
+ m_pTreeView->disable_notify_events();
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkWidget* pWidget = m_pEntry->getWidget();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_unblock(pWidget, m_nKeyPressSignalId);
+#endif
+ g_signal_handler_unblock(pWidget, m_nEntryInsertTextSignalId);
+ m_pTreeView->enable_notify_events();
+ GtkInstanceContainer::enable_notify_events();
+ }
+
+ virtual bool changed_by_direct_pick() const override
+ {
+ return m_bTreeChange;
+ }
+
+ virtual void set_custom_renderer(bool /*bOn*/) override
+ {
+ assert(false && "not implemented");
+ }
+
+ virtual int get_max_mru_count() const override
+ {
+ assert(false && "not implemented");
+ return 0;
+ }
+
+ virtual void set_max_mru_count(int) override
+ {
+ assert(false && "not implemented");
+ }
+
+ virtual OUString get_mru_entries() const override
+ {
+ assert(false && "not implemented");
+ return OUString();
+ }
+
+ virtual void set_mru_entries(const OUString&) override
+ {
+ assert(false && "not implemented");
+ }
+
+ virtual void set_item_menu(const OString&, weld::Menu*) override
+ {
+ assert(false && "not implemented");
+ }
+
+ VclPtr<VirtualDevice> create_render_virtual_device() const override
+ {
+ return create_virtual_device();
+ }
+
+ int get_menu_button_width() const override
+ {
+ assert(false && "not implemented");
+ return 0;
+ }
+
+ virtual ~GtkInstanceEntryTreeView() override
+ {
+ if (m_nAutoCompleteIdleId)
+ g_source_remove(m_nAutoCompleteIdleId);
+ GtkWidget* pWidget = m_pEntry->getWidget();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(pWidget, m_nKeyPressSignalId);
+#endif
+ g_signal_handler_disconnect(pWidget, m_nEntryInsertTextSignalId);
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstanceExpander : public GtkInstanceWidget, public virtual weld::Expander
+{
+private:
+ GtkExpander* m_pExpander;
+ gulong m_nSignalId;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gulong m_nButtonPressEventSignalId;
+ gulong m_nMappedSignalId;
+#endif
+
+ static void signalExpanded(GtkExpander* pExpander, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceExpander* pThis = static_cast<GtkInstanceExpander*>(widget);
+ SolarMutexGuard aGuard;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (gtk_expander_get_resize_toplevel(pExpander))
+ {
+ GtkWidget *pToplevel = widget_get_toplevel(GTK_WIDGET(pExpander));
+
+ // https://gitlab.gnome.org/GNOME/gtk/issues/70
+ // I imagine at some point a release with a fix will be available in which
+ // case this can be avoided depending on version number
+ if (pToplevel && GTK_IS_WINDOW(pToplevel) && gtk_widget_get_realized(pToplevel))
+ {
+ int nToplevelWidth, nToplevelHeight;
+ int nChildHeight;
+
+ GtkWidget* child = gtk_bin_get_child(GTK_BIN(pExpander));
+ gtk_widget_get_preferred_height(child, &nChildHeight, nullptr);
+ gtk_window_get_size(GTK_WINDOW(pToplevel), &nToplevelWidth, &nToplevelHeight);
+
+ if (pThis->get_expanded())
+ nToplevelHeight += nChildHeight;
+ else
+ nToplevelHeight -= nChildHeight;
+
+ gtk_window_resize(GTK_WINDOW(pToplevel), nToplevelWidth, nToplevelHeight);
+ }
+ }
+#else
+ (void)pExpander;
+#endif
+
+ pThis->signal_expanded();
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean signalButton(GtkWidget*, GdkEventButton*, gpointer)
+ {
+ // don't let button press get to parent window, for the case of the
+ // an expander in a sidebar where otherwise single click to expand
+ // doesn't work
+ return true;
+ }
+
+ /* tdf#141186 if the expander is initially collapsed then when mapped all its
+ children are mapped too. If they are mapped then the mnemonics of the
+ children are taken into account on shortcuts and non-visible children in a
+ collapsed expander can be triggered which is confusing.
+
+ If the expander is expanded and collapsed the child is unmapped and the
+ problem doesn't occur.
+
+ So to avoid the problem of an initially collapsed expander, listen to
+ the map event and if the expander is mapped but collapsed then unmap the
+ child of the expander.
+
+ This problem was seen in gtk3-3.24.33 and not with gtk4-4.6.4 so a gtk3
+ fix only needed.
+ */
+ static void signalMap(GtkWidget*, gpointer widget)
+ {
+ GtkInstanceExpander* pThis = static_cast<GtkInstanceExpander*>(widget);
+ if (!gtk_expander_get_expanded(pThis->m_pExpander))
+ {
+ if (GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(pThis->m_pExpander)))
+ gtk_widget_unmap(pChild);
+ }
+ }
+#endif
+
+public:
+ GtkInstanceExpander(GtkExpander* pExpander, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pExpander), pBuilder, bTakeOwnership)
+ , m_pExpander(pExpander)
+ , m_nSignalId(g_signal_connect(m_pExpander, "notify::expanded", G_CALLBACK(signalExpanded), this))
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ , m_nButtonPressEventSignalId(g_signal_connect_after(m_pExpander, "button-press-event", G_CALLBACK(signalButton), this))
+ , m_nMappedSignalId(g_signal_connect_after(m_pExpander, "map", G_CALLBACK(signalMap), this))
+#endif
+ {
+ }
+
+ virtual void set_label(const OUString& rText) override
+ {
+ ::set_label(GTK_LABEL(gtk_expander_get_label_widget(m_pExpander)), rText);
+ }
+
+ virtual OUString get_label() const override
+ {
+ return ::get_label(GTK_LABEL(gtk_expander_get_label_widget(m_pExpander)));
+ }
+
+ virtual bool get_expanded() const override
+ {
+ return gtk_expander_get_expanded(m_pExpander);
+ }
+
+ virtual void set_expanded(bool bExpand) override
+ {
+ gtk_expander_set_expanded(m_pExpander, bExpand);
+ }
+
+ virtual ~GtkInstanceExpander() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_handler_disconnect(m_pExpander, m_nMappedSignalId);
+ g_signal_handler_disconnect(m_pExpander, m_nButtonPressEventSignalId);
+#endif
+ g_signal_handler_disconnect(m_pExpander, m_nSignalId);
+ }
+};
+
+}
+
+namespace {
+
+class GtkInstancePopover : public GtkInstanceContainer, public virtual weld::Popover
+{
+private:
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ //popover cannot escape dialog under X so we might need to stick up own window instead
+ GtkWindow* m_pMenuHack;
+ bool m_bMenuPoppedUp;
+ bool m_nButtonPressSeen;
+#endif
+ GtkPopover* m_pPopover;
+ gulong m_nSignalId;
+ ImplSVEvent* m_pClosedEvent;
+
+ static void signalClosed(GtkPopover*, gpointer widget)
+ {
+ GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+ // call signal-closed async so the closed callback isn't called
+ // while the GtkPopover handler is still in-execution
+ pThis->launch_signal_closed();
+ }
+
+ DECL_LINK(async_signal_closed, void*, void);
+
+ void launch_signal_closed()
+ {
+ if (m_pClosedEvent)
+ Application::RemoveUserEvent(m_pClosedEvent);
+ m_pClosedEvent = Application::PostUserEvent(LINK(this, GtkInstancePopover, async_signal_closed));
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ static gboolean keyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+ return pThis->key_press(pEvent);
+ }
+
+ bool key_press(const GdkEventKey* pEvent)
+ {
+ if (pEvent->keyval == GDK_KEY_Escape)
+ {
+ popdown();
+ return true;
+ }
+ return false;
+ }
+
+ static gboolean signalButtonPress(GtkWidget* /*pWidget*/, GdkEventButton* /*pEvent*/, gpointer widget)
+ {
+ GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+ pThis->m_nButtonPressSeen = true;
+ return false;
+ }
+
+ static gboolean signalButtonRelease(GtkWidget* /*pWidget*/, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+ if (pThis->m_nButtonPressSeen && button_event_is_outside(GTK_WIDGET(pThis->m_pMenuHack), pEvent))
+ pThis->popdown();
+ return false;
+ }
+
+ bool forward_event_if_popup_under_mouse(GdkEvent* pEvent)
+ {
+ GtkWidget* pEventWidget = gtk_get_event_widget(pEvent);
+ GtkWidget* pTopLevel = widget_get_toplevel(pEventWidget);
+
+ if (pTopLevel == GTK_WIDGET(m_pMenuHack))
+ return false;
+
+ GdkSurface* pSurface = widget_get_surface(pTopLevel);
+ void* pMouseEnteredAnotherPopup = g_object_get_data(G_OBJECT(pSurface), "g-lo-InstancePopup");
+ if (!pMouseEnteredAnotherPopup)
+ return false;
+
+ return gtk_widget_event(pEventWidget, pEvent);
+ }
+
+ static gboolean signalButtonCrossing(GtkWidget*, GdkEvent* pEvent, gpointer widget)
+ {
+ GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+ return pThis->forward_event_if_popup_under_mouse(pEvent);
+ }
+
+ static gboolean signalMotion(GtkWidget*, GdkEvent* pEvent, gpointer widget)
+ {
+ GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+ return pThis->forward_event_if_popup_under_mouse(pEvent);
+ }
+
+ static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
+ {
+ GtkInstancePopover* pThis = static_cast<GtkInstancePopover*>(widget);
+ pThis->grab_broken(pEvent);
+ }
+
+ void grab_broken(const GdkEventGrabBroken *event)
+ {
+ if (event->grab_window == nullptr)
+ {
+ popdown();
+ }
+ else if (!g_object_get_data(G_OBJECT(event->grab_window), "g-lo-InstancePopup")) // another LibreOffice popover took a grab
+ {
+ //try and regrab, so when we lose the grab to the menu of the color palette
+ //combobox we regain it so the color palette doesn't itself disappear on next
+ //click on the color palette combobox
+ do_grab(GTK_WIDGET(m_pMenuHack));
+ }
+ }
+
+#endif
+
+public:
+ GtkInstancePopover(GtkPopover* pPopover, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ : GtkInstanceContainer(GTK_CONTAINER(pPopover), pBuilder, bTakeOwnership)
+ , m_pMenuHack(nullptr)
+ , m_bMenuPoppedUp(false)
+ , m_nButtonPressSeen(false)
+#else
+ : GtkInstanceContainer(GTK_WIDGET(pPopover), pBuilder, bTakeOwnership)
+#endif
+ , m_pPopover(pPopover)
+ , m_nSignalId(g_signal_connect(m_pPopover, "closed", G_CALLBACK(signalClosed), this))
+ , m_pClosedEvent(nullptr)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ //under wayland a Popover will work to "escape" the parent dialog, not
+ //so under X, so come up with this hack to use a raw GtkWindow
+ GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pPopover));
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ m_pMenuHack = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
+ gtk_window_set_type_hint(m_pMenuHack, GDK_WINDOW_TYPE_HINT_COMBO);
+ gtk_window_set_resizable(m_pMenuHack, false);
+ g_signal_connect(m_pMenuHack, "key-press-event", G_CALLBACK(keyPress), this);
+ g_signal_connect(m_pMenuHack, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
+ g_signal_connect(m_pMenuHack, "button-press-event", G_CALLBACK(signalButtonPress), this);
+ g_signal_connect(m_pMenuHack, "button-release-event", G_CALLBACK(signalButtonRelease), this);
+ // to emulate a modeless popover we forward the leave/enter/motion events to the widgets
+ // they would have gone to a if we were really modeless
+ if (!gtk_popover_get_modal(m_pPopover))
+ {
+ g_signal_connect(m_pMenuHack, "leave-notify-event", G_CALLBACK(signalButtonCrossing), this);
+ g_signal_connect(m_pMenuHack, "enter-notify-event", G_CALLBACK(signalButtonCrossing), this);
+ g_signal_connect(m_pMenuHack, "motion-notify-event", G_CALLBACK(signalMotion), this);
+ }
+ }
+#endif
+ }
+
+ virtual void popup_at_rect(weld::Widget* pParent, const tools::Rectangle& rRect, weld::Placement ePlace) override
+ {
+ GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
+ assert(pGtkWidget);
+
+ GtkWidget* pWidget = pGtkWidget->getWidget();
+
+ GdkRectangle aRect;
+ pWidget = getPopupRect(pWidget, rRect, aRect);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_set_parent(GTK_WIDGET(m_pPopover), pWidget);
+#else
+ gtk_popover_set_relative_to(m_pPopover, pWidget);
+#endif
+ gtk_popover_set_pointing_to(m_pPopover, &aRect);
+
+ if (ePlace == weld::Placement::Under)
+ gtk_popover_set_position(m_pPopover, GTK_POS_BOTTOM);
+ else
+ {
+ if (::SwapForRTL(pWidget))
+ gtk_popover_set_position(m_pPopover, GTK_POS_LEFT);
+ else
+ gtk_popover_set_position(m_pPopover, GTK_POS_RIGHT);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ //under wayland a Popover will work to "escape" the parent dialog, not
+ //so under X, so come up with this hack to use a raw GtkWindow
+ GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pPopover));
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ if (!m_bMenuPoppedUp)
+ {
+ MovePopoverContentsToWindow(GTK_WIDGET(m_pPopover), m_pMenuHack, pWidget, aRect, ePlace);
+ m_bMenuPoppedUp = true;
+ }
+ return;
+ }
+#endif
+
+ gtk_popover_popup(m_pPopover);
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ virtual bool get_visible() const override
+ {
+ if (m_pMenuHack)
+ return gtk_widget_get_visible(GTK_WIDGET(m_pMenuHack));
+ return gtk_widget_get_visible(m_pWidget);
+ }
+
+ virtual void ensureMouseEventWidget() override
+ {
+ if (!m_pMouseEventBox && m_pMenuHack)
+ {
+ m_pMouseEventBox = GTK_WIDGET(m_pMenuHack);
+ return;
+ }
+ GtkInstanceContainer::ensureMouseEventWidget();
+ }
+#endif
+
+ virtual void popdown() override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ //under wayland a Popover will work to "escape" the parent dialog, not
+ //so under X, so come up with this hack to use a raw GtkWindow
+ GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pPopover));
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ if (m_bMenuPoppedUp)
+ {
+ m_nButtonPressSeen = false;
+ MoveWindowContentsToPopover(m_pMenuHack, GTK_WIDGET(m_pPopover), gtk_popover_get_relative_to(m_pPopover));
+ m_bMenuPoppedUp = false;
+ signal_closed();
+ }
+ return;
+ }
+#endif
+
+ gtk_popover_popdown(m_pPopover);
+ }
+
+ void PopdownAndFlushClosedSignal()
+ {
+ if (get_visible())
+ popdown();
+ if (m_pClosedEvent)
+ {
+ Application::RemoveUserEvent(m_pClosedEvent);
+ async_signal_closed(nullptr);
+ }
+ }
+
+ virtual void resize_to_request() override
+ {
+ // resizing to request is what gtk does automatically
+ }
+
+ virtual ~GtkInstancePopover() override
+ {
+ PopdownAndFlushClosedSignal();
+ DisconnectMouseEvents();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (m_pMenuHack)
+ gtk_widget_destroy(GTK_WIDGET(m_pMenuHack));
+#endif
+ g_signal_handler_disconnect(m_pPopover, m_nSignalId);
+ }
+};
+
+IMPL_LINK_NOARG(GtkInstancePopover, async_signal_closed, void*, void)
+{
+ m_pClosedEvent = nullptr;
+ signal_closed();
+}
+
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+
+namespace
+{
+
+AtkObject* drawing_area_get_accessible(GtkWidget *pWidget)
+{
+ AtkObject* pDefaultAccessible = default_drawing_area_get_accessible(pWidget);
+ void* pData = g_object_get_data(G_OBJECT(pWidget), "g-lo-GtkInstanceDrawingArea");
+ GtkInstanceDrawingArea* pDrawingArea = static_cast<GtkInstanceDrawingArea*>(pData);
+ AtkObject *pAtkObj = pDrawingArea ? pDrawingArea->GetAtkObject(pDefaultAccessible) : nullptr;
+ if (pAtkObj)
+ return pAtkObj;
+ return pDefaultAccessible;
+}
+
+void ensure_intercept_drawing_area_accessibility()
+{
+ static bool bDone;
+ if (!bDone)
+ {
+ gpointer pClass = g_type_class_ref(GTK_TYPE_DRAWING_AREA);
+ GtkWidgetClass* pWidgetClass = GTK_WIDGET_CLASS(pClass);
+ default_drawing_area_get_accessible = pWidgetClass->get_accessible;
+ pWidgetClass->get_accessible = drawing_area_get_accessible;
+ g_type_class_unref(pClass);
+ bDone = true;
+ }
+}
+
+void ensure_disable_ctrl_page_up_down(GType eType)
+{
+ gpointer pClass = g_type_class_ref(eType);
+ GtkWidgetClass* pWidgetClass = GTK_WIDGET_CLASS(pClass);
+ GtkBindingSet* pBindingSet = gtk_binding_set_by_class(pWidgetClass);
+ gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Up, GDK_CONTROL_MASK);
+ gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Up, static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK));
+ gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Down, GDK_CONTROL_MASK);
+ gtk_binding_entry_remove(pBindingSet, GDK_KEY_Page_Down, static_cast<GdkModifierType>(GDK_SHIFT_MASK|GDK_CONTROL_MASK));
+ g_type_class_unref(pClass);
+}
+
+// tdf#130400 disable ctrl+page_up and ctrl+page_down bindings so the
+// keystrokes are consumed by the surrounding notebook bindings instead
+void ensure_disable_ctrl_page_up_down_bindings()
+{
+ static bool bDone;
+ if (!bDone)
+ {
+ ensure_disable_ctrl_page_up_down(GTK_TYPE_TREE_VIEW);
+ ensure_disable_ctrl_page_up_down(GTK_TYPE_SPIN_BUTTON);
+ bDone = true;
+ }
+}
+
+}
+#endif
+
+namespace {
+
+bool IsAllowedBuiltInIcon(std::u16string_view iconName)
+{
+ // limit the named icons to those known by VclBuilder
+ return VclBuilder::mapStockToSymbol(iconName) != SymbolType::DONTKNOW;
+}
+
+}
+
+namespace {
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void silence_gwarning(const gchar* /*log_domain*/,
+ GLogLevelFlags /*log_level*/,
+ const gchar* /*message*/,
+ gpointer /*user_data*/)
+{
+}
+#endif
+
+void load_ui_file(GtkBuilder* pBuilder, const OUString& rUri)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ builder_add_from_gtk3_file(pBuilder, rUri);
+#else
+ guint nLogHandlerId = 0;
+ GLogLevelFlags nFatalMask(static_cast<GLogLevelFlags>(G_LOG_FLAG_RECURSION|G_LOG_LEVEL_ERROR));
+ if (rUri.endsWith("sfx/ui/tabbarcontents.ui"))
+ {
+ // gtk unhelpfully has a bogus warning for the accelerator in this .ui because it assumes menus with accelerators
+ // if attached to something are attached to a MenuShell, but it's a MenuButton in this case. Turn off warnings, and
+ // in the case of fatal-warnings temp disable fatal warnings, for this case.
+ nLogHandlerId = g_log_set_handler("GLib-GObject",
+ static_cast<GLogLevelFlags>(G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION),
+ silence_gwarning, nullptr);
+ nFatalMask = g_log_set_always_fatal(nFatalMask);
+ }
+
+ OUString aPath;
+ osl::FileBase::getSystemPathFromFileURL(rUri, aPath);
+ GError *err = nullptr;
+ auto rc = gtk_builder_add_from_file(pBuilder, OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr(), &err);
+
+ if (nLogHandlerId)
+ {
+ g_log_remove_handler("GLib-GObject", nLogHandlerId);
+ g_log_set_always_fatal(nFatalMask);
+ }
+
+ if (!rc)
+ {
+ SAL_WARN( "vcl.gtk", "GtkInstanceBuilder: error when calling gtk_builder_add_from_file: " << err->message);
+ g_error_free(err);
+ }
+ assert(rc && "could not load UI file");
+#endif
+}
+
+class GtkInstanceBuilder : public weld::Builder
+{
+private:
+ ResHookProc m_pStringReplace;
+ OString m_aUtf8HelpRoot;
+ OUString m_aIconTheme;
+ OUString m_aUILang;
+ GtkBuilder* m_pBuilder;
+ GSList* m_pObjectList;
+ GtkWidget* m_pParentWidget;
+ gulong m_nNotifySignalId;
+ std::vector<GtkButton*> m_aMnemonicButtons;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ std::vector<GtkCheckButton*> m_aMnemonicCheckButtons;
+#endif
+ std::vector<GtkLabel*> m_aMnemonicLabels;
+
+ VclPtr<SystemChildWindow> m_xInterimGlue;
+ bool m_bAllowCycleFocusOut;
+
+ void postprocess_widget(GtkWidget* pWidget)
+ {
+ const bool bHideHelp = comphelper::LibreOfficeKit::isActive() &&
+ officecfg::Office::Common::Help::HelpRootURL::get().isEmpty();
+
+ //fixup icons
+ //wanted: better way to do this, e.g. make gtk use gio for
+ //loading from a filename and provide gio protocol handler
+ //for our image in a zip urls
+ //
+ //unpack the images and keep them as dirs and just
+ //add the paths to the gtk icon theme dir
+ if (GTK_IS_IMAGE(pWidget))
+ {
+ GtkImage* pImage = GTK_IMAGE(pWidget);
+ if (const gchar* icon_name = image_get_icon_name(pImage))
+ {
+ OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
+ if (!IsAllowedBuiltInIcon(aIconName))
+ image_set_from_icon_name_theme_lang(pImage, aIconName, m_aIconTheme, m_aUILang);
+ }
+ }
+#if GTK_CHECK_VERSION(4, 0, 0)
+ else if (GTK_IS_PICTURE(pWidget))
+ {
+ GtkPicture* pPicture = GTK_PICTURE(pWidget);
+ if (GFile* icon_file = gtk_picture_get_file(pPicture))
+ {
+ char* icon_name = g_file_get_uri(icon_file);
+ OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
+ g_free(icon_name);
+ assert(aIconName.startsWith("private:///graphicrepository/"));
+ aIconName.startsWith("private:///graphicrepository/", &aIconName);
+ picture_set_from_icon_name_theme_lang(GTK_PICTURE(pWidget), aIconName, m_aIconTheme, m_aUILang);
+ }
+ }
+#endif
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ else if (GTK_IS_TOOL_BUTTON(pWidget))
+ {
+ GtkToolButton* pToolButton = GTK_TOOL_BUTTON(pWidget);
+ if (const gchar* icon_name = gtk_tool_button_get_icon_name(pToolButton))
+ {
+ OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
+ if (!IsAllowedBuiltInIcon(aIconName))
+ {
+ if (GtkWidget* pImage = image_new_from_icon_name_theme_lang(aIconName, m_aIconTheme, m_aUILang))
+ {
+ gtk_tool_button_set_icon_widget(pToolButton, pImage);
+ gtk_widget_show(pImage);
+ }
+ }
+ }
+
+ // if no tooltip reuse the label as default tooltip
+ if (!gtk_widget_get_tooltip_text(pWidget))
+ {
+ if (const gchar* label = gtk_tool_button_get_label(pToolButton))
+ gtk_widget_set_tooltip_text(pWidget, label);
+ }
+ }
+#else
+ else if (GTK_IS_BUTTON(pWidget))
+ {
+ GtkButton* pButton = GTK_BUTTON(pWidget);
+ if (const gchar* icon_name = gtk_button_get_icon_name(pButton))
+ {
+ OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
+ if (!IsAllowedBuiltInIcon(aIconName))
+ {
+ if (GtkWidget* pImage = image_new_from_icon_name_theme_lang(aIconName, m_aIconTheme, m_aUILang))
+ {
+ gtk_widget_set_halign(pImage, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign(pImage, GTK_ALIGN_CENTER);
+ gtk_button_set_child(pButton, pImage);
+ gtk_widget_show(pImage);
+ }
+ }
+ }
+ }
+ else if (GTK_IS_MENU_BUTTON(pWidget))
+ {
+ GtkMenuButton* pButton = GTK_MENU_BUTTON(pWidget);
+ if (const gchar* icon_name = gtk_menu_button_get_icon_name(pButton))
+ {
+ OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
+ if (!IsAllowedBuiltInIcon(aIconName))
+ {
+ if (GtkWidget* pImage = image_new_from_icon_name_theme_lang(aIconName, m_aIconTheme, m_aUILang))
+ {
+ gtk_widget_set_halign(pImage, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign(pImage, GTK_ALIGN_CENTER);
+ // TODO after gtk 4.6 is released require that version and drop this
+ static auto menu_button_set_child = reinterpret_cast<void (*) (GtkMenuButton*, GtkWidget*)>(dlsym(nullptr, "gtk_menu_button_set_child"));
+ if (menu_button_set_child)
+ menu_button_set_child(pButton, pImage);
+ gtk_widget_show(pImage);
+ }
+ }
+ }
+ }
+#endif
+
+ //set helpids
+ OString sBuildableName = ::get_buildable_id(GTK_BUILDABLE(pWidget));
+ if (!sBuildableName.isEmpty())
+ {
+ OString sHelpId = m_aUtf8HelpRoot + sBuildableName;
+ set_help_id(pWidget, sHelpId);
+ //hook up for extended help
+ const ImplSVHelpData& aHelpData = ImplGetSVHelpData();
+ if (aHelpData.mbBalloonHelp && !GTK_IS_DIALOG(pWidget) && !GTK_IS_ASSISTANT(pWidget))
+ {
+ gtk_widget_set_has_tooltip(pWidget, true);
+ g_signal_connect(pWidget, "query-tooltip", G_CALLBACK(signalTooltipQuery), nullptr);
+ }
+
+ if (bHideHelp && sBuildableName == "help")
+ gtk_widget_hide(pWidget);
+ }
+
+ if (m_pStringReplace)
+ {
+ // tdf#136498 %PRODUCTNAME shown in tool tips
+ const char* pTooltip = gtk_widget_get_tooltip_text(pWidget);
+ if (pTooltip && pTooltip[0])
+ {
+ OUString aTooltip(pTooltip, strlen(pTooltip), RTL_TEXTENCODING_UTF8);
+ aTooltip = (*m_pStringReplace)(aTooltip);
+ gtk_widget_set_tooltip_text(pWidget, OUStringToOString(aTooltip, RTL_TEXTENCODING_UTF8).getStr());
+ }
+ }
+
+ // expand placeholder and collect potentially missing mnemonics
+ if (GTK_IS_BUTTON(pWidget))
+ {
+ GtkButton* pButton = GTK_BUTTON(pWidget);
+ if (m_pStringReplace)
+ {
+ OUString aLabel(button_get_label(pButton));
+ if (!aLabel.isEmpty())
+ button_set_label(pButton, (*m_pStringReplace)(aLabel));
+ }
+ if (gtk_button_get_use_underline(pButton))
+ m_aMnemonicButtons.push_back(pButton);
+ }
+#if GTK_CHECK_VERSION(4, 0, 0)
+ else if (GTK_IS_CHECK_BUTTON(pWidget))
+ {
+ GtkCheckButton* pButton = GTK_CHECK_BUTTON(pWidget);
+ if (m_pStringReplace)
+ {
+ OUString aLabel(get_label(pButton));
+ if (!aLabel.isEmpty())
+ set_label(pButton, (*m_pStringReplace)(aLabel));
+ }
+ if (gtk_check_button_get_use_underline(pButton))
+ m_aMnemonicCheckButtons.push_back(pButton);
+ }
+#endif
+ else if (GTK_IS_LABEL(pWidget))
+ {
+ GtkLabel* pLabel = GTK_LABEL(pWidget);
+ if (m_pStringReplace)
+ {
+ OUString aLabel(get_label(pLabel));
+ if (!aLabel.isEmpty())
+ set_label(pLabel, (*m_pStringReplace)(aLabel));
+ }
+ if (gtk_label_get_use_underline(pLabel))
+ m_aMnemonicLabels.push_back(pLabel);
+ }
+ else if (GTK_IS_TEXT_VIEW(pWidget))
+ {
+ GtkTextView* pTextView = GTK_TEXT_VIEW(pWidget);
+ if (m_pStringReplace)
+ {
+ GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(pTextView);
+ GtkTextIter start, end;
+ gtk_text_buffer_get_bounds(pBuffer, &start, &end);
+ char* pTextStr = gtk_text_buffer_get_text(pBuffer, &start, &end, true);
+ int nTextLen = pTextStr ? strlen(pTextStr) : 0;
+ if (nTextLen)
+ {
+ OUString sOldText(pTextStr, nTextLen, RTL_TEXTENCODING_UTF8);
+ OString sText(OUStringToOString((*m_pStringReplace)(sOldText), RTL_TEXTENCODING_UTF8));
+ gtk_text_buffer_set_text(pBuffer, sText.getStr(), sText.getLength());
+ }
+ g_free(pTextStr);
+ }
+ }
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ else if (GTK_IS_ENTRY(pWidget))
+ {
+ g_signal_connect(pWidget, "key-press-event", G_CALLBACK(signalEntryInsertSpecialCharKeyPress), nullptr);
+ }
+#endif
+ else if (GTK_IS_WINDOW(pWidget))
+ {
+ if (m_pStringReplace)
+ {
+ GtkWindow* pWindow = GTK_WINDOW(pWidget);
+ set_title(pWindow, (*m_pStringReplace)(get_title(pWindow)));
+ if (GTK_IS_MESSAGE_DIALOG(pWindow))
+ {
+ GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(pWindow);
+ set_primary_text(pMessageDialog, (*m_pStringReplace)(get_primary_text(pMessageDialog)));
+ set_secondary_text(pMessageDialog, (*m_pStringReplace)(get_secondary_text(pMessageDialog)));
+ }
+ }
+ }
+ }
+
+ //GtkBuilder sets translation domain during parse, and unsets it again afterwards.
+ //In order for GtkBuilder to find the translations bindtextdomain has to be called
+ //for the domain. So here on the first setting of "domain" we call Translate::Create
+ //to make sure that happens. Without this, if some other part of LibreOffice has
+ //used the translation machinery for this domain it will still work, but if it
+ //hasn't, e.g. tdf#119929, then the translation fails
+ void translation_domain_set()
+ {
+ Translate::Create(gtk_builder_get_translation_domain(m_pBuilder), LanguageTag(m_aUILang));
+ g_signal_handler_disconnect(m_pBuilder, m_nNotifySignalId);
+ }
+
+ static void signalNotify(GObject*, GParamSpec *pSpec, gpointer pData)
+ {
+ g_return_if_fail(pSpec != nullptr);
+ if (strcmp(pSpec->name, "translation-domain") == 0)
+ {
+ GtkInstanceBuilder* pBuilder = static_cast<GtkInstanceBuilder*>(pData);
+ pBuilder->translation_domain_set();
+ }
+ }
+
+ static void postprocess(gpointer data, gpointer user_data)
+ {
+ GObject* pObject = static_cast<GObject*>(data);
+ if (!GTK_IS_WIDGET(pObject))
+ return;
+ GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
+ pThis->postprocess_widget(GTK_WIDGET(pObject));
+ }
+
+ void DisallowCycleFocusOut()
+ {
+ assert(!m_bAllowCycleFocusOut); // we only expect this to be called when this holds
+
+ GtkWidget* pTopLevel = widget_get_toplevel(m_pParentWidget);
+ assert(pTopLevel);
+ GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pTopLevel);
+ assert(pFrame);
+ // unhook handler and let gtk cycle its own way through this widget's
+ // children because it has no non-gtk siblings
+ pFrame->DisallowCycleFocusOut();
+ }
+
+ static void signalMap(GtkWidget*, gpointer user_data)
+ {
+ GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
+ // tdf#138047 wait until map to do this because the final SalFrame may
+ // not be the same as at ctor time
+ pThis->DisallowCycleFocusOut();
+ }
+
+ void AllowCycleFocusOut()
+ {
+ assert(!m_bAllowCycleFocusOut); // we only expect this to be called when this holds
+
+ GtkWidget* pTopLevel = widget_get_toplevel(m_pParentWidget);
+ assert(pTopLevel);
+ GtkSalFrame* pFrame = GtkSalFrame::getFromWindow(pTopLevel);
+ assert(pFrame);
+ // rehook handler and let vcl cycle its own way through this widget's
+ // children
+ pFrame->AllowCycleFocusOut();
+
+ // tdf#145567 if the focus is in this hierarchy then, now that we are tearing down,
+ // move focus to the usual focus candidate for the frame
+ GtkWindow* pFocusWin = get_active_window();
+ GtkWidget* pFocus = pFocusWin ? gtk_window_get_focus(pFocusWin) : nullptr;
+ bool bHasFocus = pFocus && gtk_widget_is_ancestor(pFocus, pTopLevel);
+ if (bHasFocus)
+ pFrame->GrabFocus();
+ }
+
+ static void signalUnmap(GtkWidget*, gpointer user_data)
+ {
+ GtkInstanceBuilder* pThis = static_cast<GtkInstanceBuilder*>(user_data);
+ pThis->AllowCycleFocusOut();
+ }
+
+public:
+ GtkInstanceBuilder(GtkWidget* pParent, std::u16string_view rUIRoot, const OUString& rUIFile,
+ SystemChildWindow* pInterimGlue, bool bAllowCycleFocusOut)
+ : weld::Builder()
+ , m_pStringReplace(Translate::GetReadStringHook())
+ , m_pParentWidget(pParent)
+ , m_nNotifySignalId(0)
+ , m_xInterimGlue(pInterimGlue)
+ , m_bAllowCycleFocusOut(bAllowCycleFocusOut)
+ {
+ OUString sHelpRoot(rUIFile);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ ensure_intercept_drawing_area_accessibility();
+ ensure_disable_ctrl_page_up_down_bindings();
+#endif
+
+ sal_Int32 nIdx = sHelpRoot.lastIndexOf('.');
+ if (nIdx != -1)
+ sHelpRoot = sHelpRoot.copy(0, nIdx);
+ sHelpRoot += "/";
+ m_aUtf8HelpRoot = OUStringToOString(sHelpRoot, RTL_TEXTENCODING_UTF8);
+ m_aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ m_aUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
+
+ OUString aUri(rUIRoot + rUIFile);
+
+ m_pBuilder = gtk_builder_new();
+ m_nNotifySignalId = g_signal_connect_data(G_OBJECT(m_pBuilder), "notify", G_CALLBACK(signalNotify), this, nullptr, G_CONNECT_AFTER);
+
+ load_ui_file(m_pBuilder, aUri);
+
+ m_pObjectList = gtk_builder_get_objects(m_pBuilder);
+ g_slist_foreach(m_pObjectList, postprocess, this);
+
+ GenerateMissingMnemonics();
+
+ if (m_xInterimGlue)
+ {
+ assert(m_pParentWidget);
+ g_object_set_data(G_OBJECT(m_pParentWidget), "InterimWindowGlue", m_xInterimGlue.get());
+
+ if (!m_bAllowCycleFocusOut)
+ {
+ g_signal_connect(G_OBJECT(m_pParentWidget), "map", G_CALLBACK(signalMap), this);
+ g_signal_connect(G_OBJECT(m_pParentWidget), "unmap", G_CALLBACK(signalUnmap), this);
+ }
+ }
+ }
+
+ void GenerateMissingMnemonics()
+ {
+ MnemonicGenerator aMnemonicGenerator('_');
+ for (const auto a : m_aMnemonicButtons)
+ aMnemonicGenerator.RegisterMnemonic(button_get_label(a));
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (const auto a : m_aMnemonicCheckButtons)
+ aMnemonicGenerator.RegisterMnemonic(get_label(a));
+#endif
+ for (const auto a : m_aMnemonicLabels)
+ aMnemonicGenerator.RegisterMnemonic(get_label(a));
+
+ for (const auto a : m_aMnemonicButtons)
+ {
+ OUString aLabel(button_get_label(a));
+ OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
+ if (aLabel == aNewLabel)
+ continue;
+ button_set_label(a, aNewLabel);
+ }
+#if GTK_CHECK_VERSION(4, 0, 0)
+ for (const auto a : m_aMnemonicCheckButtons)
+ {
+ OUString aLabel(get_label(a));
+ OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
+ if (aLabel == aNewLabel)
+ continue;
+ set_label(a, aNewLabel);
+ }
+#endif
+ for (const auto a : m_aMnemonicLabels)
+ {
+ OUString aLabel(get_label(a));
+ OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
+ if (aLabel == aNewLabel)
+ continue;
+ set_label(a, aNewLabel);
+ }
+
+ m_aMnemonicLabels.clear();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ m_aMnemonicCheckButtons.clear();
+#endif
+ m_aMnemonicButtons.clear();
+ }
+
+ OString get_current_page_help_id()
+ {
+ OString sPageHelpId;
+ // check to see if there is a notebook called tabcontrol and get the
+ // helpid for the current page of that
+ std::unique_ptr<weld::Notebook> xNotebook(weld_notebook("tabcontrol"));
+ if (xNotebook)
+ {
+ if (GtkInstanceContainer* pPage = dynamic_cast<GtkInstanceContainer*>(xNotebook->get_page(xNotebook->get_current_page_ident())))
+ {
+ GtkWidget* pContainer = pPage->getWidget();
+ if (GtkWidget* pPageWidget = widget_get_first_child(pContainer))
+ sPageHelpId = ::get_help_id(pPageWidget);
+ }
+ }
+ return sPageHelpId;
+ }
+
+ virtual ~GtkInstanceBuilder() override
+ {
+ g_slist_free(m_pObjectList);
+ g_object_unref(m_pBuilder);
+
+ if (m_xInterimGlue && !m_bAllowCycleFocusOut)
+ AllowCycleFocusOut();
+
+ m_xInterimGlue.disposeAndClear();
+ }
+
+ //ideally we would have/use weld::Container add and explicitly
+ //call add when we want to do this, but in the vcl impl the
+ //parent has to be set when the child is created, so for the
+ //gtk impl emulate this by doing this implicitly at weld time
+ void auto_add_parentless_widgets_to_container(GtkWidget* pWidget)
+ {
+ if (GTK_IS_POPOVER(pWidget))
+ return;
+ if (GTK_IS_WINDOW(pWidget))
+ return;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (!gtk_widget_get_parent(pWidget))
+ gtk_widget_set_parent(pWidget, m_pParentWidget);
+#else
+ if (widget_get_toplevel(pWidget) == pWidget)
+ gtk_container_add(GTK_CONTAINER(m_pParentWidget), pWidget);
+#endif
+ }
+
+ virtual std::unique_ptr<weld::MessageDialog> weld_message_dialog(const OString &id) override
+ {
+ GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pMessageDialog)
+ return nullptr;
+ gtk_window_set_transient_for(GTK_WINDOW(pMessageDialog), GTK_WINDOW(widget_get_toplevel(m_pParentWidget)));
+ return std::make_unique<GtkInstanceMessageDialog>(pMessageDialog, this, true);
+ }
+
+ virtual std::unique_ptr<weld::Assistant> weld_assistant(const OString &id) override
+ {
+ GtkAssistant* pAssistant = GTK_ASSISTANT(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pAssistant)
+ return nullptr;
+ if (m_pParentWidget)
+ gtk_window_set_transient_for(GTK_WINDOW(pAssistant), GTK_WINDOW(widget_get_toplevel(m_pParentWidget)));
+ return std::make_unique<GtkInstanceAssistant>(pAssistant, this, true);
+ }
+
+ virtual std::unique_ptr<weld::Dialog> weld_dialog(const OString &id) override
+ {
+ GtkWindow* pDialog = GTK_WINDOW(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pDialog)
+ return nullptr;
+ if (m_pParentWidget)
+ gtk_window_set_transient_for(pDialog, GTK_WINDOW(widget_get_toplevel(m_pParentWidget)));
+ return std::make_unique<GtkInstanceDialog>(pDialog, this, true);
+ }
+
+ virtual std::unique_ptr<weld::Window> create_screenshot_window() override
+ {
+ GtkWidget* pTopLevel = nullptr;
+
+ for (GSList* l = m_pObjectList; l; l = g_slist_next(l))
+ {
+ GObject* pObj = static_cast<GObject*>(l->data);
+
+ if (!GTK_IS_WIDGET(pObj) || gtk_widget_get_parent(GTK_WIDGET(pObj)))
+ continue;
+
+ if (!pTopLevel)
+ pTopLevel = GTK_WIDGET(pObj);
+ else if (GTK_IS_WINDOW(pObj))
+ pTopLevel = GTK_WIDGET(pObj);
+ }
+
+ if (!pTopLevel)
+ return nullptr;
+
+ GtkWindow* pDialog;
+ if (GTK_IS_WINDOW(pTopLevel))
+ pDialog = GTK_WINDOW(pTopLevel);
+ else
+ {
+ pDialog = GTK_WINDOW(gtk_dialog_new());
+ ::set_help_id(GTK_WIDGET(pDialog), ::get_help_id(pTopLevel));
+
+ GtkWidget* pContentArea = gtk_dialog_get_content_area(GTK_DIALOG(pDialog));
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(pContentArea), pTopLevel);
+ gtk_widget_show_all(pTopLevel);
+#else
+ gtk_box_append(GTK_BOX(pContentArea), pTopLevel);
+ gtk_widget_show(pTopLevel);
+#endif
+ }
+
+ if (m_pParentWidget)
+ gtk_window_set_transient_for(pDialog, GTK_WINDOW(widget_get_toplevel(m_pParentWidget)));
+ return std::make_unique<GtkInstanceDialog>(pDialog, this, true);
+ }
+
+ virtual std::unique_ptr<weld::Widget> weld_widget(const OString &id) override
+ {
+ GtkWidget* pWidget = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pWidget)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(pWidget);
+ return std::make_unique<GtkInstanceWidget>(pWidget, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Container> weld_container(const OString &id) override
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, id.getStr()));
+#else
+ GtkWidget* pContainer = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, id.getStr()));
+#endif
+ if (!pContainer)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
+ return std::make_unique<GtkInstanceContainer>(pContainer, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Box> weld_box(const OString &id) override
+ {
+ GtkBox* pBox = GTK_BOX(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pBox)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pBox));
+ return std::make_unique<GtkInstanceBox>(pBox, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Paned> weld_paned(const OString &id) override
+ {
+ GtkPaned* pPaned = GTK_PANED(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pPaned)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pPaned));
+ return std::make_unique<GtkInstancePaned>(pPaned, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Frame> weld_frame(const OString &id) override
+ {
+ GtkFrame* pFrame = GTK_FRAME(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pFrame)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pFrame));
+ return std::make_unique<GtkInstanceFrame>(pFrame, this, false);
+ }
+
+ virtual std::unique_ptr<weld::ScrolledWindow> weld_scrolled_window(const OString &id, bool bUserManagedScrolling = false) override
+ {
+ GtkScrolledWindow* pScrolledWindow = GTK_SCROLLED_WINDOW(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pScrolledWindow)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pScrolledWindow));
+ return std::make_unique<GtkInstanceScrolledWindow>(pScrolledWindow, this, false, bUserManagedScrolling);
+ }
+
+ virtual std::unique_ptr<weld::Notebook> weld_notebook(const OString &id) override
+ {
+ GtkNotebook* pNotebook = GTK_NOTEBOOK(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pNotebook)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pNotebook));
+ return std::make_unique<GtkInstanceNotebook>(pNotebook, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Button> weld_button(const OString &id) override
+ {
+ GtkButton* pButton = GTK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
+ return std::make_unique<GtkInstanceButton>(pButton, this, false);
+ }
+
+ virtual std::unique_ptr<weld::MenuButton> weld_menu_button(const OString &id) override
+ {
+ GtkMenuButton* pButton = GTK_MENU_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
+ return std::make_unique<GtkInstanceMenuButton>(pButton, nullptr, this, false);
+ }
+
+ virtual std::unique_ptr<weld::MenuToggleButton> weld_menu_toggle_button(const OString &id) override
+ {
+ GtkMenuButton* pButton = GTK_MENU_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
+ // gtk doesn't come with exactly the same concept
+ GtkBuilder* pMenuToggleButton = makeMenuToggleButtonBuilder();
+ return std::make_unique<GtkInstanceMenuToggleButton>(pMenuToggleButton, pButton, this, false);
+ }
+
+ virtual std::unique_ptr<weld::LinkButton> weld_link_button(const OString &id) override
+ {
+ GtkLinkButton* pButton = GTK_LINK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pButton));
+ return std::make_unique<GtkInstanceLinkButton>(pButton, this, false);
+ }
+
+ virtual std::unique_ptr<weld::ToggleButton> weld_toggle_button(const OString &id) override
+ {
+ GtkToggleButton* pToggleButton = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pToggleButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pToggleButton));
+ return std::make_unique<GtkInstanceToggleButton>(pToggleButton, this, false);
+ }
+
+ virtual std::unique_ptr<weld::RadioButton> weld_radio_button(const OString &id) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkCheckButton* pRadioButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+#else
+ GtkRadioButton* pRadioButton = GTK_RADIO_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+#endif
+ if (!pRadioButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pRadioButton));
+ return std::make_unique<GtkInstanceRadioButton>(pRadioButton, this, false);
+ }
+
+ virtual std::unique_ptr<weld::CheckButton> weld_check_button(const OString &id) override
+ {
+ GtkCheckButton* pCheckButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pCheckButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pCheckButton));
+ return std::make_unique<GtkInstanceCheckButton>(pCheckButton, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Scale> weld_scale(const OString &id) override
+ {
+ GtkScale* pScale = GTK_SCALE(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pScale)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pScale));
+ return std::make_unique<GtkInstanceScale>(pScale, this, false);
+ }
+
+ virtual std::unique_ptr<weld::ProgressBar> weld_progress_bar(const OString &id) override
+ {
+ GtkProgressBar* pProgressBar = GTK_PROGRESS_BAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pProgressBar)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pProgressBar));
+ return std::make_unique<GtkInstanceProgressBar>(pProgressBar, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Spinner> weld_spinner(const OString &id) override
+ {
+ GtkSpinner* pSpinner = GTK_SPINNER(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pSpinner)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinner));
+ return std::make_unique<GtkInstanceSpinner>(pSpinner, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Image> weld_image(const OString &id) override
+ {
+ GtkWidget* pWidget = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pWidget)
+ return nullptr;
+ if (GTK_IS_IMAGE(pWidget))
+ {
+ auto_add_parentless_widgets_to_container(pWidget);
+ return std::make_unique<GtkInstanceImage>(GTK_IMAGE(pWidget), this, false);
+ }
+#if GTK_CHECK_VERSION(4, 0, 0)
+ if (GTK_IS_PICTURE(pWidget))
+ {
+ auto_add_parentless_widgets_to_container(pWidget);
+ return std::make_unique<GtkInstancePicture>(GTK_PICTURE(pWidget), this, false);
+ }
+#endif
+ return nullptr;
+ }
+
+ virtual std::unique_ptr<weld::Calendar> weld_calendar(const OString &id) override
+ {
+ GtkCalendar* pCalendar = GTK_CALENDAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pCalendar)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pCalendar));
+ return std::make_unique<GtkInstanceCalendar>(pCalendar, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Entry> weld_entry(const OString &id) override
+ {
+ GtkEntry* pEntry = GTK_ENTRY(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pEntry)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pEntry));
+ return std::make_unique<GtkInstanceEntry>(pEntry, this, false);
+ }
+
+ virtual std::unique_ptr<weld::SpinButton> weld_spin_button(const OString &id) override
+ {
+ GtkSpinButton* pSpinButton = GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pSpinButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton));
+ return std::make_unique<GtkInstanceSpinButton>(pSpinButton, this, false);
+ }
+
+ virtual std::unique_ptr<weld::MetricSpinButton> weld_metric_spin_button(const OString& id, FieldUnit eUnit) override
+ {
+ return std::make_unique<weld::MetricSpinButton>(weld_spin_button(id), eUnit);
+ }
+
+ virtual std::unique_ptr<weld::FormattedSpinButton> weld_formatted_spin_button(const OString &id) override
+ {
+ GtkSpinButton* pSpinButton = GTK_SPIN_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pSpinButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinButton));
+ return std::make_unique<GtkInstanceFormattedSpinButton>(pSpinButton, this, false);
+ }
+
+ virtual std::unique_ptr<weld::ComboBox> weld_combo_box(const OString &id) override
+ {
+ GtkComboBox* pComboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pComboBox)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pComboBox));
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return std::make_unique<GtkInstanceComboBox>(pComboBox, this, false);
+#else
+ /* we replace GtkComboBox because of difficulties with too tall menus
+
+ 1) https://gitlab.gnome.org/GNOME/gtk/issues/1910
+ has_entry long menus take forever to appear (tdf#125388)
+
+ on measuring each row, the GtkComboBox GtkTreeMenu will call
+ its area_apply_attributes_cb function on the row, but that calls
+ gtk_tree_menu_get_path_item which then loops through each child of the
+ menu looking for the widget of the row, so performance drops to useless.
+
+ All area_apply_attributes_cb does it set menu item sensitivity, so block it from running
+ with fragile hackery which assumes that the unwanted callback is the only one with a
+
+ 2) https://gitlab.gnome.org/GNOME/gtk/issues/94
+ when a super tall combobox menu is activated, and the selected
+ entry is sufficiently far down the list, then the menu doesn't
+ appear under wayland
+
+ 3) https://gitlab.gnome.org/GNOME/gtk/issues/310
+ no typeahead support
+
+ 4) we want to be able to control the width of the button, but have a drop down menu which
+ is not limited to the width of the button
+
+ 5) https://bugs.documentfoundation.org/show_bug.cgi?id=131120
+ super tall menu doesn't appear under X sometimes
+ */
+ GtkBuilder* pComboBuilder = makeComboBoxBuilder();
+ return std::make_unique<GtkInstanceComboBox>(pComboBuilder, pComboBox, this, false);
+#endif
+ }
+
+ virtual std::unique_ptr<weld::TreeView> weld_tree_view(const OString &id) override
+ {
+ GtkTreeView* pTreeView = GTK_TREE_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pTreeView)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pTreeView));
+ return std::make_unique<GtkInstanceTreeView>(pTreeView, this, false);
+ }
+
+ virtual std::unique_ptr<weld::IconView> weld_icon_view(const OString &id) override
+ {
+ GtkIconView* pIconView = GTK_ICON_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pIconView)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pIconView));
+ return std::make_unique<GtkInstanceIconView>(pIconView, this, false);
+ }
+
+ virtual std::unique_ptr<weld::EntryTreeView> weld_entry_tree_view(const OString& containerid, const OString& entryid, const OString& treeviewid) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pContainer = GTK_WIDGET(gtk_builder_get_object(m_pBuilder, containerid.getStr()));
+#else
+ GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, containerid.getStr()));
+#endif
+ if (!pContainer)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
+ return std::make_unique<GtkInstanceEntryTreeView>(pContainer, this, false,
+ weld_entry(entryid),
+ weld_tree_view(treeviewid));
+ }
+
+ virtual std::unique_ptr<weld::Label> weld_label(const OString &id) override
+ {
+ GtkLabel* pLabel = GTK_LABEL(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pLabel)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pLabel));
+ return std::make_unique<GtkInstanceLabel>(pLabel, this, false);
+ }
+
+ virtual std::unique_ptr<weld::TextView> weld_text_view(const OString &id) override
+ {
+ GtkTextView* pTextView = GTK_TEXT_VIEW(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pTextView)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pTextView));
+ return std::make_unique<GtkInstanceTextView>(pTextView, this, false);
+ }
+
+ virtual std::unique_ptr<weld::Expander> weld_expander(const OString &id) override
+ {
+ GtkExpander* pExpander = GTK_EXPANDER(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pExpander)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pExpander));
+ return std::make_unique<GtkInstanceExpander>(pExpander, this, false);
+ }
+
+ virtual std::unique_ptr<weld::DrawingArea> weld_drawing_area(const OString &id, const a11yref& rA11y,
+ FactoryFunction /*pUITestFactoryFunction*/, void* /*pUserData*/) override
+ {
+ GtkDrawingArea* pDrawingArea = GTK_DRAWING_AREA(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pDrawingArea)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pDrawingArea));
+ return std::make_unique<GtkInstanceDrawingArea>(pDrawingArea, this, rA11y, false);
+ }
+
+ virtual std::unique_ptr<weld::Menu> weld_menu(const OString &id) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkPopoverMenu* pMenu = GTK_POPOVER_MENU(gtk_builder_get_object(m_pBuilder, id.getStr()));
+#else
+ GtkMenu* pMenu = GTK_MENU(gtk_builder_get_object(m_pBuilder, id.getStr()));
+#endif
+ if (!pMenu)
+ return nullptr;
+ return std::make_unique<GtkInstanceMenu>(pMenu, true);
+ }
+
+ virtual std::unique_ptr<weld::Popover> weld_popover(const OString &id) override
+ {
+ GtkPopover* pPopover = GTK_POPOVER(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pPopover)
+ return nullptr;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return std::make_unique<GtkInstancePopover>(pPopover, this, false);
+#else
+ return std::make_unique<GtkInstancePopover>(pPopover, this, true);
+#endif
+ }
+
+ virtual std::unique_ptr<weld::Toolbar> weld_toolbar(const OString &id) override
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkBox* pToolbar = GTK_BOX(gtk_builder_get_object(m_pBuilder, id.getStr()));
+#else
+ GtkToolbar* pToolbar = GTK_TOOLBAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
+#endif
+ if (!pToolbar)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pToolbar));
+ return std::make_unique<GtkInstanceToolbar>(pToolbar, this, false);
+ }
+
+ virtual std::unique_ptr<weld::SizeGroup> create_size_group() override
+ {
+ return std::make_unique<GtkInstanceSizeGroup>();
+ }
+};
+
+}
+
+void GtkInstanceWindow::help()
+{
+ //show help for widget with keyboard focus
+ GtkWidget* pWidget = gtk_window_get_focus(m_pWindow);
+ if (!pWidget)
+ pWidget = GTK_WIDGET(m_pWindow);
+ OString sHelpId = ::get_help_id(pWidget);
+ while (sHelpId.isEmpty())
+ {
+ pWidget = gtk_widget_get_parent(pWidget);
+ if (!pWidget)
+ break;
+ sHelpId = ::get_help_id(pWidget);
+ }
+ std::unique_ptr<weld::Widget> xTemp(pWidget != m_pWidget ? new GtkInstanceWidget(pWidget, m_pBuilder, false) : nullptr);
+ weld::Widget* pSource = xTemp ? xTemp.get() : this;
+ bool bRunNormalHelpRequest = !m_aHelpRequestHdl.IsSet() || m_aHelpRequestHdl.Call(*pSource);
+ Help* pHelp = bRunNormalHelpRequest ? Application::GetHelp() : nullptr;
+ if (!pHelp)
+ return;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // tdf#126007, there's a nice fallback route for offline help where
+ // the current page of a notebook will get checked when the help
+ // button is pressed and there was no help for the dialog found.
+ //
+ // But for online help that route doesn't get taken, so bodge this here
+ // by using the page help id if available and if the help button itself
+ // was the original id
+ if (m_pBuilder && sHelpId.endsWith("/help"))
+ {
+ OString sPageId = m_pBuilder->get_current_page_help_id();
+ if (!sPageId.isEmpty())
+ sHelpId = sPageId;
+ else
+ {
+ // tdf#129068 likewise the help for the wrapping dialog is less
+ // helpful than the help for the content area could be
+ GtkContainer* pContainer = nullptr;
+ if (GTK_IS_DIALOG(m_pWindow))
+ pContainer = GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pWindow)));
+ else if (GTK_IS_ASSISTANT(m_pWindow))
+ {
+ GtkAssistant* pAssistant = GTK_ASSISTANT(m_pWindow);
+ pContainer = GTK_CONTAINER(gtk_assistant_get_nth_page(pAssistant, gtk_assistant_get_current_page(pAssistant)));
+ }
+ if (pContainer)
+ {
+ GtkWidget* pContentWidget = widget_get_first_child(GTK_WIDGET(pContainer));
+ if (pContentWidget)
+ sHelpId = ::get_help_id(pContentWidget);
+ }
+ }
+ }
+#endif
+ pHelp->Start(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), pSource);
+}
+
+//iterate upwards through the hierarchy from this widgets through its parents
+//calling func with their helpid until func returns true or we run out of parents
+void GtkInstanceWidget::help_hierarchy_foreach(const std::function<bool(const OString&)>& func)
+{
+ GtkWidget* pParent = m_pWidget;
+ while ((pParent = gtk_widget_get_parent(pParent)))
+ {
+ if (func(::get_help_id(pParent)))
+ return;
+ }
+}
+
+std::unique_ptr<weld::Builder> GtkInstance::CreateBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile)
+{
+ GtkInstanceWidget* pParentWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
+ GtkWidget* pBuilderParent = pParentWidget ? pParentWidget->getWidget() : nullptr;
+ return std::make_unique<GtkInstanceBuilder>(pBuilderParent, rUIRoot, rUIFile, nullptr, true);
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+// tdf#135965 for the case of native widgets inside a GtkSalFrame and F1 pressed, run help
+// on gtk widget help ids until we hit a vcl parent and then use vcl window help ids
+gboolean GtkSalFrame::NativeWidgetHelpPressed(GtkAccelGroup*, GObject*, guint, GdkModifierType, gpointer pFrame)
+{
+ Help* pHelp = Application::GetHelp();
+ if (!pHelp)
+ return true;
+
+ GtkWindow* pWindow = static_cast<GtkWindow*>(pFrame);
+
+ vcl::Window* pChildWindow = nullptr;
+
+ //show help for widget with keyboard focus
+ GtkWidget* pWidget = gtk_window_get_focus(pWindow);
+ if (!pWidget)
+ pWidget = GTK_WIDGET(pWindow);
+ OString sHelpId = ::get_help_id(pWidget);
+ while (sHelpId.isEmpty())
+ {
+ pWidget = gtk_widget_get_parent(pWidget);
+ if (!pWidget)
+ break;
+ pChildWindow = static_cast<vcl::Window*>(g_object_get_data(G_OBJECT(pWidget), "InterimWindowGlue"));
+ if (pChildWindow)
+ {
+ sHelpId = pChildWindow->GetHelpId();
+ break;
+ }
+ sHelpId = ::get_help_id(pWidget);
+ }
+
+ if (pChildWindow)
+ {
+ while (sHelpId.isEmpty())
+ {
+ pChildWindow = pChildWindow->GetParent();
+ if (!pChildWindow)
+ break;
+ sHelpId = pChildWindow->GetHelpId();
+ }
+ if (!pChildWindow)
+ return true;
+ pHelp->Start(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), pChildWindow);
+ return true;
+ }
+
+ if (!pWidget)
+ return true;
+ std::unique_ptr<weld::Widget> xTemp(new GtkInstanceWidget(pWidget, nullptr, false));
+ pHelp->Start(OStringToOUString(sHelpId, RTL_TEXTENCODING_UTF8), xTemp.get());
+ return true;
+}
+#endif
+
+std::unique_ptr<weld::Builder> GtkInstance::CreateInterimBuilder(vcl::Window* pParent, const OUString& rUIRoot, const OUString& rUIFile,
+ bool bAllowCycleFocusOut, sal_uInt64)
+{
+ // Create a foreign window which we know is a GtkGrid and make the native widgets a child of that, so we can
+ // support GtkWidgets within a vcl::Window
+ SystemWindowData winData = {};
+ winData.bClipUsingNativeWidget = true;
+ auto xEmbedWindow = VclPtr<SystemChildWindow>::Create(pParent, 0, &winData, false);
+ xEmbedWindow->Show(true, ShowFlags::NoActivate);
+ xEmbedWindow->set_expand(true);
+
+ const SystemEnvData* pEnvData = xEmbedWindow->GetSystemData();
+ if (!pEnvData)
+ return nullptr;
+
+ GtkWidget *pWindow = static_cast<GtkWidget*>(pEnvData->pWidget);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_show_all(pWindow);
+#else
+ gtk_widget_show(pWindow);
+#endif
+
+ // build the widget tree as a child of the GtkEventBox GtkGrid parent
+ return std::make_unique<GtkInstanceBuilder>(pWindow, rUIRoot, rUIFile, xEmbedWindow.get(), bAllowCycleFocusOut);
+}
+
+weld::MessageDialog* GtkInstance::CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType, VclButtonsType eButtonsType, const OUString &rPrimaryMessage)
+{
+ GtkInstanceWidget* pParentInstance = dynamic_cast<GtkInstanceWidget*>(pParent);
+ GtkWindow* pParentWindow = pParentInstance ? pParentInstance->getWindow() : nullptr;
+ GtkMessageDialog* pMessageDialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(pParentWindow, GTK_DIALOG_MODAL,
+ VclToGtk(eMessageType), VclToGtk(eButtonsType), "%s",
+ OUStringToOString(rPrimaryMessage, RTL_TEXTENCODING_UTF8).getStr()));
+ return new GtkInstanceMessageDialog(pMessageDialog, nullptr, true);
+}
+
+weld::Window* GtkInstance::GetFrameWeld(const css::uno::Reference<css::awt::XWindow>& rWindow)
+{
+ if (SalGtkXWindow* pGtkXWindow = dynamic_cast<SalGtkXWindow*>(rWindow.get()))
+ return pGtkXWindow->getFrameWeld();
+ return SalInstance::GetFrameWeld(rWindow);
+}
+
+weld::Window* GtkSalFrame::GetFrameWeld() const
+{
+ if (!m_xFrameWeld)
+ m_xFrameWeld.reset(new GtkInstanceWindow(GTK_WINDOW(widget_get_toplevel(getWindow())), nullptr, false));
+ return m_xFrameWeld.get();
+}
+
+void* GtkInstance::CreateGStreamerSink(const SystemChildWindow *pWindow)
+{
+#if ENABLE_GSTREAMER_1_0
+ auto aSymbol = gstElementFactoryNameSymbol();
+ if (!aSymbol)
+ return nullptr;
+
+ const SystemEnvData* pEnvData = pWindow->GetSystemData();
+ if (!pEnvData)
+ return nullptr;
+
+ GstElement* pVideosink = aSymbol("gtksink", "gtksink");
+ if (!pVideosink)
+ return nullptr;
+
+ GtkWidget *pGstWidget;
+ g_object_get(pVideosink, "widget", &pGstWidget, nullptr);
+ gtk_widget_set_vexpand(pGstWidget, true);
+ gtk_widget_set_hexpand(pGstWidget, true);
+
+ GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(pParent), pGstWidget);
+#endif
+ g_object_unref(pGstWidget);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_show_all(pParent);
+#else
+ gtk_widget_show(pParent);
+#endif
+
+ return pVideosink;
+#else
+ (void)pWindow;
+ return nullptr;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtkobject.cxx b/vcl/unx/gtk3/gtkobject.cxx
new file mode 100644
index 000000000..ca01e7795
--- /dev/null
+++ b/vcl/unx/gtk3/gtkobject.cxx
@@ -0,0 +1,617 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifdef AIX
+#define _LINUX_SOURCE_COMPAT
+#include <sys/timer.h>
+#undef _LINUX_SOURCE_COMPAT
+#endif
+
+#include <unx/gtk/gtkbackend.hxx>
+#include <unx/gtk/gtkobject.hxx>
+#include <unx/gtk/gtkframe.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <vcl/event.hxx>
+
+GtkSalObjectBase::GtkSalObjectBase(GtkSalFrame* pParent)
+ : m_pSocket(nullptr)
+ , m_pParent(pParent)
+ , m_pRegion(nullptr)
+{
+}
+
+GtkSalObject::GtkSalObject(GtkSalFrame* pParent, bool bShow)
+ : GtkSalObjectBase(pParent)
+{
+ if (!m_pParent)
+ return;
+
+ // our plug window
+ m_pSocket = gtk_grid_new();
+ Show( bShow );
+ // insert into container
+ gtk_fixed_put( pParent->getFixedContainer(),
+ m_pSocket,
+ 0, 0 );
+
+ Init();
+
+ g_signal_connect( G_OBJECT(m_pSocket), "destroy", G_CALLBACK(signalDestroy), this );
+
+ // #i59255# necessary due to sync effects with java child windows
+ pParent->Flush();
+}
+
+void GtkSalObjectBase::Init()
+{
+ // realize so we can get a window id
+ gtk_widget_realize( m_pSocket );
+
+ // system data
+ // tdf#139609 deliberately defer using m_pParent->GetNativeWindowHandle(m_pSocket)) to set m_aSystemData.aWindow
+ // unless its explicitly needed
+ m_aSystemData.aShellWindow = reinterpret_cast<sal_IntPtr>(this);
+ m_aSystemData.pSalFrame = nullptr;
+ m_aSystemData.pWidget = m_pSocket;
+ m_aSystemData.nScreen = m_pParent->getXScreenNumber().getXScreen();
+ m_aSystemData.toolkit = SystemEnvData::Toolkit::Gtk;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkScreen* pScreen = gtk_widget_get_screen(m_pParent->getWindow());
+ GdkVisual* pVisual = gdk_screen_get_system_visual(pScreen);
+
+#if defined(GDK_WINDOWING_X11)
+ GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ m_aSystemData.pDisplay = gdk_x11_display_get_xdisplay(pDisplay);
+ m_aSystemData.pVisual = gdk_x11_visual_get_xvisual(pVisual);
+ m_aSystemData.platform = SystemEnvData::Platform::Xcb;
+ }
+#endif
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
+ {
+ m_aSystemData.pDisplay = gdk_wayland_display_get_wl_display(pDisplay);
+ m_aSystemData.platform = SystemEnvData::Platform::Wayland;
+ }
+#endif
+
+ g_signal_connect( G_OBJECT(m_pSocket), "button-press-event", G_CALLBACK(signalButton), this );
+ g_signal_connect( G_OBJECT(m_pSocket), "button-release-event", G_CALLBACK(signalButton), this );
+ g_signal_connect( G_OBJECT(m_pSocket), "focus-in-event", G_CALLBACK(signalFocus), this );
+ g_signal_connect( G_OBJECT(m_pSocket), "focus-out-event", G_CALLBACK(signalFocus), this );
+#endif
+}
+
+GtkSalObjectBase::~GtkSalObjectBase()
+{
+ if( m_pRegion )
+ {
+ cairo_region_destroy( m_pRegion );
+ }
+}
+
+GtkSalObject::~GtkSalObject()
+{
+ if( !m_pSocket )
+ return;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_unparent(m_pSocket);
+#else
+ // remove socket from parent frame's fixed container
+ gtk_container_remove( GTK_CONTAINER(gtk_widget_get_parent(m_pSocket)),
+ m_pSocket );
+ // get rid of the socket
+ // actually the gtk_container_remove should let the ref count
+ // of the socket sink to 0 and destroy it (see signalDestroy)
+ // this is just a sanity check
+ if( m_pSocket )
+ gtk_widget_destroy( m_pSocket );
+#endif
+}
+
+void GtkSalObject::ResetClipRegion()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( m_pSocket )
+ gdk_window_shape_combine_region( widget_get_surface(m_pSocket), nullptr, 0, 0 );
+#endif
+}
+
+void GtkSalObjectBase::BeginSetClipRegion( sal_uInt32 )
+{
+ if (m_pRegion)
+ cairo_region_destroy(m_pRegion);
+ m_pRegion = cairo_region_create();
+}
+
+void GtkSalObjectBase::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ GdkRectangle aRect;
+ aRect.x = nX;
+ aRect.y = nY;
+ aRect.width = nWidth;
+ aRect.height = nHeight;
+
+ cairo_region_union_rectangle( m_pRegion, &aRect );
+}
+
+void GtkSalObject::EndSetClipRegion()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( m_pSocket )
+ gdk_window_shape_combine_region( widget_get_surface(m_pSocket), m_pRegion, 0, 0 );
+#endif
+}
+
+void GtkSalObject::SetPosSize(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight)
+{
+ if (m_pSocket)
+ {
+ GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pSocket));
+ gtk_fixed_move( pContainer, m_pSocket, nX, nY );
+ gtk_widget_set_size_request( m_pSocket, nWidth, nHeight );
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_pParent->nopaint_container_resize_children(GTK_CONTAINER(pContainer));
+#endif
+ }
+}
+
+void GtkSalObject::Reparent(SalFrame* pFrame)
+{
+ GtkSalFrame* pNewParent = static_cast<GtkSalFrame*>(pFrame);
+ if (m_pSocket)
+ {
+ GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pSocket));
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gint nX(0), nY(0);
+ gtk_container_child_get(GTK_CONTAINER(pContainer), m_pSocket,
+ "x", &nX,
+ "y", &nY,
+ nullptr);
+#else
+ double nX(0), nY(0);
+ gtk_fixed_get_child_position(pContainer, m_pSocket, &nX, &nY);
+#endif
+
+ g_object_ref(m_pSocket);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_remove(GTK_CONTAINER(pContainer), m_pSocket);
+#else
+ gtk_fixed_remove(pContainer, m_pSocket);
+#endif
+
+ gtk_fixed_put(pNewParent->getFixedContainer(),
+ m_pSocket,
+ nX, nY);
+
+ g_object_unref(m_pSocket);
+ }
+ m_pParent = pNewParent;
+}
+
+void GtkSalObject::Show( bool bVisible )
+{
+ if( m_pSocket )
+ {
+ if( bVisible )
+ gtk_widget_show(m_pSocket);
+ else
+ gtk_widget_hide(m_pSocket);
+ }
+}
+
+Size GtkSalObjectBase::GetOptimalSize() const
+{
+ if (m_pSocket)
+ {
+ bool bVisible = gtk_widget_get_visible(m_pSocket);
+ if (!bVisible)
+ gtk_widget_set_visible(m_pSocket, true);
+
+ // Undo SetPosSize before getting its preferred size
+ gint width(-1), height(-1);
+ gtk_widget_get_size_request(m_pSocket, &width, &height);
+ gtk_widget_set_size_request(m_pSocket, -1, -1);
+
+ GtkRequisition size;
+ gtk_widget_get_preferred_size(m_pSocket, nullptr, &size);
+
+ // Restore SetPosSize size
+ gtk_widget_set_size_request(m_pSocket, width, height);
+
+ if (!bVisible)
+ gtk_widget_set_visible(m_pSocket, false);
+ return Size(size.width, size.height);
+ }
+ return Size();
+}
+
+const SystemEnvData* GtkSalObjectBase::GetSystemData() const
+{
+ return &m_aSystemData;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalObjectBase::signalButton( GtkWidget*, GdkEventButton* pEvent, gpointer object )
+{
+ GtkSalObjectBase* pThis = static_cast<GtkSalObject*>(object);
+
+ if( pEvent->type == GDK_BUTTON_PRESS )
+ {
+ pThis->CallCallback( SalObjEvent::ToTop );
+ }
+
+ return FALSE;
+}
+
+gboolean GtkSalObjectBase::signalFocus( GtkWidget*, GdkEventFocus* pEvent, gpointer object )
+{
+ GtkSalObjectBase* pThis = static_cast<GtkSalObject*>(object);
+
+ pThis->CallCallback( pEvent->in ? SalObjEvent::GetFocus : SalObjEvent::LoseFocus );
+
+ return FALSE;
+}
+#endif
+
+void GtkSalObject::signalDestroy( GtkWidget* pObj, gpointer object )
+{
+ GtkSalObject* pThis = static_cast<GtkSalObject*>(object);
+ if( pObj == pThis->m_pSocket )
+ {
+ pThis->m_pSocket = nullptr;
+ }
+}
+
+void GtkSalObjectBase::SetForwardKey( bool bEnable )
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if( bEnable )
+ gtk_widget_add_events( GTK_WIDGET( m_pSocket ), GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK );
+ else
+ gtk_widget_set_events( GTK_WIDGET( m_pSocket ), ~(GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK) & gtk_widget_get_events( GTK_WIDGET( m_pSocket ) ) );
+#else
+ (void)bEnable;
+#endif
+}
+
+GtkSalObjectWidgetClip::GtkSalObjectWidgetClip(GtkSalFrame* pParent, bool bShow)
+ : GtkSalObjectBase(pParent)
+ , m_pScrolledWindow(nullptr)
+ , m_pViewPort(nullptr)
+ , m_pBgCssProvider(nullptr)
+{
+ if( !pParent )
+ return;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_pScrolledWindow = gtk_scrolled_window_new(nullptr, nullptr);
+ g_signal_connect(m_pScrolledWindow, "scroll-event", G_CALLBACK(signalScroll), this);
+#else
+ m_pScrolledWindow = gtk_scrolled_window_new();
+ GtkEventController* pScrollController = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
+ g_signal_connect(pScrollController, "scroll", G_CALLBACK(signalScroll), this);
+ gtk_widget_add_controller(m_pScrolledWindow, pScrollController);
+#endif
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(m_pScrolledWindow),
+ GTK_POLICY_EXTERNAL, GTK_POLICY_EXTERNAL);
+
+ // insert into container
+ gtk_fixed_put( pParent->getFixedContainer(),
+ m_pScrolledWindow,
+ 0, 0 );
+
+ // deliberately without adjustments to avoid gtk's auto adjustment on changing focus
+ m_pViewPort = gtk_viewport_new(nullptr, nullptr);
+
+ // force in a fake background of a suitable color
+ SetViewPortBackground();
+
+ ImplGetDefaultWindow()->AddEventListener(LINK(this, GtkSalObjectWidgetClip, SettingsChangedHdl));
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), m_pViewPort);
+#else
+ gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(m_pScrolledWindow), m_pViewPort);
+#endif
+ gtk_widget_show(m_pViewPort);
+
+ // our plug window
+ m_pSocket = gtk_grid_new();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(m_pViewPort), m_pSocket);
+#else
+ gtk_viewport_set_child(GTK_VIEWPORT(m_pViewPort), m_pSocket);
+#endif
+ gtk_widget_show(m_pSocket);
+
+ Show(bShow);
+
+ Init();
+
+ g_signal_connect( G_OBJECT(m_pSocket), "destroy", G_CALLBACK(signalDestroy), this );
+}
+
+// force in a fake background of a suitable color
+void GtkSalObjectWidgetClip::SetViewPortBackground()
+{
+ GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(m_pViewPort);
+ if (m_pBgCssProvider)
+ gtk_style_context_remove_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider));
+ m_pBgCssProvider = gtk_css_provider_new();
+ OUString sColor = Application::GetSettings().GetStyleSettings().GetDialogColor().AsRGBHexString();
+ 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);
+}
+
+IMPL_LINK(GtkSalObjectWidgetClip, SettingsChangedHdl, VclWindowEvent&, rEvent, void)
+{
+ if (rEvent.GetId() != VclEventId::WindowDataChanged)
+ return;
+
+ DataChangedEvent* pData = static_cast<DataChangedEvent*>(rEvent.GetData());
+ if (pData->GetType() == DataChangedEventType::SETTINGS)
+ SetViewPortBackground();
+}
+
+GtkSalObjectWidgetClip::~GtkSalObjectWidgetClip()
+{
+ ImplGetDefaultWindow()->RemoveEventListener(LINK(this, GtkSalObjectWidgetClip, SettingsChangedHdl));
+
+ if( !m_pSocket )
+ return;
+
+ // remove socket from parent frame's fixed container
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_remove( GTK_CONTAINER(gtk_widget_get_parent(m_pScrolledWindow)),
+ m_pScrolledWindow );
+
+ // get rid of the socket
+ // actually the gtk_container_remove should let the ref count
+ // of the socket sink to 0 and destroy it (see signalDestroy)
+ // this is just a sanity check
+ if( m_pScrolledWindow )
+ gtk_widget_destroy( m_pScrolledWindow );
+#else
+ gtk_fixed_remove(GTK_FIXED(gtk_widget_get_parent(m_pScrolledWindow)),
+ m_pScrolledWindow);
+#endif
+}
+
+void GtkSalObjectWidgetClip::ResetClipRegion()
+{
+ m_aClipRect = tools::Rectangle();
+ ApplyClipRegion();
+}
+
+void GtkSalObjectWidgetClip::EndSetClipRegion()
+{
+ int nRects = cairo_region_num_rectangles(m_pRegion);
+ assert(nRects == 0 || nRects == 1);
+ if (nRects == 0)
+ m_aClipRect = tools::Rectangle();
+ else
+ {
+ cairo_rectangle_int_t rectangle;
+ cairo_region_get_rectangle(m_pRegion, 0, &rectangle);
+ m_aClipRect = tools::Rectangle(Point(rectangle.x, rectangle.y), Size(rectangle.width, rectangle.height));
+ }
+ ApplyClipRegion();
+}
+
+void GtkSalObjectWidgetClip::ApplyClipRegion()
+{
+ if( !m_pSocket )
+ return;
+
+ GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pScrolledWindow));
+
+ GtkAllocation allocation;
+ allocation.x = m_aRect.Left() + m_aClipRect.Left();
+ allocation.y = m_aRect.Top() + m_aClipRect.Top();
+ if (m_aClipRect.IsEmpty())
+ {
+ allocation.width = m_aRect.GetWidth();
+ allocation.height = m_aRect.GetHeight();
+ }
+ else
+ {
+ allocation.width = m_aClipRect.GetWidth();
+ allocation.height = m_aClipRect.GetHeight();
+ }
+
+ if (AllSettings::GetLayoutRTL())
+ {
+ GtkAllocation aParentAllocation;
+ gtk_widget_get_allocation(GTK_WIDGET(pContainer), &aParentAllocation);
+ gtk_fixed_move(pContainer, m_pScrolledWindow, aParentAllocation.width - allocation.width - 1 - allocation.x, allocation.y);
+ }
+ else
+ gtk_fixed_move(pContainer, m_pScrolledWindow, allocation.x, allocation.y);
+ gtk_widget_set_size_request(m_pScrolledWindow, allocation.width, allocation.height);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_size_allocate(m_pScrolledWindow, &allocation);
+#else
+ gtk_widget_size_allocate(m_pScrolledWindow, &allocation, 0);
+#endif
+
+ gtk_adjustment_set_value(gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(m_pScrolledWindow)), m_aClipRect.Left());
+ gtk_adjustment_set_value(gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(m_pScrolledWindow)), m_aClipRect.Top());
+}
+
+void GtkSalObjectWidgetClip::SetPosSize(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight)
+{
+ m_aRect = tools::Rectangle(Point(nX, nY), Size(nWidth, nHeight));
+ if (m_pSocket)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pScrolledWindow));
+#endif
+ gtk_widget_set_size_request(m_pSocket, nWidth, nHeight);
+ ApplyClipRegion();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ m_pParent->nopaint_container_resize_children(GTK_CONTAINER(pContainer));
+#endif
+ }
+}
+
+void GtkSalObjectWidgetClip::Reparent(SalFrame* pFrame)
+{
+ GtkSalFrame* pNewParent = static_cast<GtkSalFrame*>(pFrame);
+ if (m_pSocket)
+ {
+ GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pScrolledWindow));
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gint nX(0), nY(0);
+ gtk_container_child_get(GTK_CONTAINER(pContainer), m_pScrolledWindow,
+ "x", &nX,
+ "y", &nY,
+ nullptr);
+#else
+ double nX(0), nY(0);
+ gtk_fixed_get_child_position(pContainer, m_pScrolledWindow, &nX, &nY);
+#endif
+
+ g_object_ref(m_pScrolledWindow);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_remove(GTK_CONTAINER(pContainer), m_pScrolledWindow);
+#else
+ gtk_fixed_remove(pContainer, m_pScrolledWindow);
+#endif
+
+ gtk_fixed_put(pNewParent->getFixedContainer(),
+ m_pScrolledWindow,
+ nX, nY);
+
+ g_object_unref(m_pScrolledWindow);
+ }
+ m_pParent = pNewParent;
+}
+
+void GtkSalObjectWidgetClip::Show( bool bVisible )
+{
+ if (!m_pSocket)
+ return;
+ bool bCurrentVis = gtk_widget_get_visible(m_pScrolledWindow);
+ if (bVisible == bCurrentVis)
+ return;
+ if( bVisible )
+ {
+ gtk_widget_show(m_pScrolledWindow);
+ // tdf#146641 allocations attempted while hidden are discarded by gtk,
+ // so on transition to visible ApplyClipRegion needs to be called
+ ApplyClipRegion();
+ }
+ else
+ {
+ // on hiding the widget, if a child has focus gtk will want to move the focus out of the widget
+ // but we want to keep the focus where it is, e.g. writer's comments in margin feature put
+ // cursor in a sidebar comment and scroll the page so the comment is invisible, we want the focus
+ // to stay in the invisible widget, so its there when we scroll back or on a keypress the widget
+ // gets the keystroke and scrolls back to make it visible again
+ GtkWidget* pTopLevel = widget_get_toplevel(m_pScrolledWindow);
+ GtkWidget* pOldFocus = GTK_IS_WINDOW(pTopLevel) ? gtk_window_get_focus(GTK_WINDOW(pTopLevel)) : nullptr;
+
+ g_object_set_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange", GINT_TO_POINTER(true) );
+
+ gtk_widget_hide(m_pScrolledWindow);
+
+ GtkWidget* pNewFocus = GTK_IS_WINDOW(pTopLevel) ? gtk_window_get_focus(GTK_WINDOW(pTopLevel)) : nullptr;
+ if (pOldFocus && pOldFocus != pNewFocus)
+ gtk_widget_grab_focus(pOldFocus);
+
+ g_object_set_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange", GINT_TO_POINTER(false) );
+ }
+}
+
+void GtkSalObjectWidgetClip::signalDestroy( GtkWidget* pObj, gpointer object )
+{
+ GtkSalObjectWidgetClip* pThis = static_cast<GtkSalObjectWidgetClip*>(object);
+ if( pObj == pThis->m_pSocket )
+ {
+ pThis->m_pSocket = nullptr;
+ pThis->m_pScrolledWindow = nullptr;
+ }
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalObjectWidgetClip::signalScroll(GtkWidget* pScrolledWindow, GdkEvent* pEvent, gpointer object)
+{
+ GtkSalObjectWidgetClip* pThis = static_cast<GtkSalObjectWidgetClip*>(object);
+ return pThis->signal_scroll(pScrolledWindow, pEvent);
+}
+#else
+gboolean GtkSalObjectWidgetClip::signalScroll(GtkEventControllerScroll* pController, double delta_x, double delta_y, gpointer object)
+{
+ GtkSalObjectWidgetClip* pThis = static_cast<GtkSalObjectWidgetClip*>(object);
+ return pThis->signal_scroll(pController, delta_x, delta_y);
+}
+#endif
+
+// forward the wheel scroll events onto the main window instead
+#if !GTK_CHECK_VERSION(4, 0, 0)
+bool GtkSalObjectWidgetClip::signal_scroll(GtkWidget*, GdkEvent* pEvent)
+{
+ GtkWidget* pEventWidget = gtk_get_event_widget(pEvent);
+
+ GtkWidget* pMouseEventWidget = m_pParent->getMouseEventWidget();
+
+ gtk_coord dest_x, dest_y;
+ gtk_widget_translate_coordinates(pEventWidget,
+ pMouseEventWidget,
+ pEvent->scroll.x,
+ pEvent->scroll.y,
+ &dest_x,
+ &dest_y);
+ pEvent->scroll.x = dest_x;
+ pEvent->scroll.y = dest_y;
+
+ GtkSalFrame::signalScroll(pMouseEventWidget, pEvent, m_pParent);
+ return true;
+}
+#else
+bool GtkSalObjectWidgetClip::signal_scroll(GtkEventControllerScroll* pController, double delta_x, double delta_y)
+{
+ GtkWidget* pEventWidget = m_pScrolledWindow;
+
+ GtkWidget* pMouseEventWidget = m_pParent->getMouseEventWidget();
+
+ gtk_coord dest_x, dest_y;
+ gtk_widget_translate_coordinates(pEventWidget,
+ pMouseEventWidget,
+ delta_x,
+ delta_y,
+ &dest_x,
+ &dest_y);
+ delta_x = dest_x;
+ delta_y = dest_y;
+
+ GtkSalFrame::signalScroll(pController, delta_x, delta_y, m_pParent);
+ return true;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtksalmenu.cxx b/vcl/unx/gtk3/gtksalmenu.cxx
new file mode 100644
index 000000000..c2677d612
--- /dev/null
+++ b/vcl/unx/gtk3/gtksalmenu.cxx
@@ -0,0 +1,1643 @@
+/* -*- 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 <unx/gtk/gtksalmenu.hxx>
+
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/glomenu.h>
+#include <unx/gtk/gloactiongroup.h>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/pngwrite.hxx>
+#include <vcl/pdfwriter.hxx> // for escapeStringXML
+
+#include <o3tl/string_view.hxx>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <window.h>
+#include <strings.hrc>
+
+static bool bUnityMode = false;
+
+/*
+ * This function generates a unique command name for each menu item
+ */
+static gchar* GetCommandForItem(GtkSalMenu* pParentMenu, sal_uInt16 nItemId)
+{
+ OString aCommand = "window-" +
+ OString::number(reinterpret_cast<unsigned long>(pParentMenu)) +
+ "-" + OString::number(nItemId);
+ return g_strdup(aCommand.getStr());
+}
+
+static gchar* GetCommandForItem(GtkSalMenuItem* pSalMenuItem)
+{
+ return GetCommandForItem(pSalMenuItem->mpParentMenu,
+ pSalMenuItem->mnId);
+}
+
+bool GtkSalMenu::PrepUpdate() const
+{
+ return mpMenuModel && mpActionGroup;
+}
+
+/*
+ * Menu updating methods
+ */
+
+static void RemoveSpareItemsFromNativeMenu( GLOMenu* pMenu, GList** pOldCommandList, unsigned nSection, unsigned nValidItems )
+{
+ sal_Int32 nSectionItems = g_lo_menu_get_n_items_from_section( pMenu, nSection );
+
+ while ( nSectionItems > static_cast<sal_Int32>(nValidItems) )
+ {
+ gchar* aCommand = g_lo_menu_get_command_from_item_in_section( pMenu, nSection, --nSectionItems );
+
+ if ( aCommand != nullptr && pOldCommandList != nullptr )
+ *pOldCommandList = g_list_append( *pOldCommandList, g_strdup( aCommand ) );
+
+ g_free( aCommand );
+
+ g_lo_menu_remove_from_section( pMenu, nSection, nSectionItems );
+ }
+}
+
+typedef std::pair<GtkSalMenu*, sal_uInt16> MenuAndId;
+
+namespace
+{
+ MenuAndId decode_command(const gchar *action_name)
+ {
+ std::string_view sCommand(action_name);
+
+ sal_Int32 nIndex = 0;
+ std::string_view sWindow = o3tl::getToken(sCommand, 0, '-', nIndex);
+ std::string_view sGtkSalMenu = o3tl::getToken(sCommand, 0, '-', nIndex);
+ std::string_view sItemId = o3tl::getToken(sCommand, 0, '-', nIndex);
+
+ GtkSalMenu* pSalSubMenu = reinterpret_cast<GtkSalMenu*>(o3tl::toInt64(sGtkSalMenu));
+
+ assert(sWindow == "window" && pSalSubMenu);
+ (void) sWindow;
+
+ return MenuAndId(pSalSubMenu, o3tl::toInt32(sItemId));
+ }
+}
+
+static void RemoveDisabledItemsFromNativeMenu(GLOMenu* pMenu, GList** pOldCommandList,
+ sal_Int32 nSection, GActionGroup* pActionGroup)
+{
+ while (nSection >= 0)
+ {
+ sal_Int32 nSectionItems = g_lo_menu_get_n_items_from_section( pMenu, nSection );
+ while (nSectionItems--)
+ {
+ gchar* pCommand = g_lo_menu_get_command_from_item_in_section(pMenu, nSection, nSectionItems);
+ // remove disabled entries
+ bool bRemove = !g_action_group_get_action_enabled(pActionGroup, pCommand);
+ if (!bRemove)
+ {
+ //also remove any empty submenus
+ GLOMenu* pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section(pMenu, nSection, nSectionItems);
+ if (pSubMenuModel)
+ {
+ gint nSubMenuSections = g_menu_model_get_n_items(G_MENU_MODEL(pSubMenuModel));
+ if (nSubMenuSections == 0)
+ bRemove = true;
+ else if (nSubMenuSections == 1)
+ {
+ gint nItems = g_lo_menu_get_n_items_from_section(pSubMenuModel, 0);
+ if (nItems == 0)
+ bRemove = true;
+ else if (nItems == 1)
+ {
+ //If the only entry is the "No Selection Possible" entry, then we are allowed
+ //to removed it
+ gchar* pSubCommand = g_lo_menu_get_command_from_item_in_section(pSubMenuModel, 0, 0);
+ MenuAndId aMenuAndId(decode_command(pSubCommand));
+ bRemove = aMenuAndId.second == 0xFFFF;
+ g_free(pSubCommand);
+ }
+ }
+ }
+ }
+
+ if (bRemove)
+ {
+ //but tdf#86850 Always display clipboard functions
+ bRemove = g_strcmp0(pCommand, ".uno:Cut") &&
+ g_strcmp0(pCommand, ".uno:Copy") &&
+ g_strcmp0(pCommand, ".uno:Paste");
+ }
+
+ if (bRemove)
+ {
+ if (pCommand != nullptr && pOldCommandList != nullptr)
+ *pOldCommandList = g_list_append(*pOldCommandList, g_strdup(pCommand));
+ g_lo_menu_remove_from_section(pMenu, nSection, nSectionItems);
+ }
+
+ g_free(pCommand);
+ }
+ --nSection;
+ }
+}
+
+static void RemoveSpareSectionsFromNativeMenu( GLOMenu* pMenu, GList** pOldCommandList, sal_Int32 nLastSection )
+{
+ if ( pMenu == nullptr || pOldCommandList == nullptr )
+ return;
+
+ sal_Int32 n = g_menu_model_get_n_items( G_MENU_MODEL( pMenu ) ) - 1;
+
+ for ( ; n > nLastSection; n--)
+ {
+ RemoveSpareItemsFromNativeMenu( pMenu, pOldCommandList, n, 0 );
+ g_lo_menu_remove( pMenu, n );
+ }
+}
+
+static gint CompareStr( gpointer str1, gpointer str2 )
+{
+ return g_strcmp0( static_cast<const gchar*>(str1), static_cast<const gchar*>(str2) );
+}
+
+static void RemoveUnusedCommands( GLOActionGroup* pActionGroup, GList* pOldCommandList, GList* pNewCommandList )
+{
+ if ( pActionGroup == nullptr || pOldCommandList == nullptr )
+ {
+ g_list_free_full( pOldCommandList, g_free );
+ g_list_free_full( pNewCommandList, g_free );
+ return;
+ }
+
+ while ( pNewCommandList != nullptr )
+ {
+ GList* pNewCommand = g_list_first( pNewCommandList );
+ pNewCommandList = g_list_remove_link( pNewCommandList, pNewCommand );
+
+ gpointer aCommand = g_list_nth_data( pNewCommand, 0 );
+
+ GList* pOldCommand = g_list_find_custom( pOldCommandList, aCommand, reinterpret_cast<GCompareFunc>(CompareStr) );
+
+ if ( pOldCommand != nullptr )
+ {
+ pOldCommandList = g_list_remove_link( pOldCommandList, pOldCommand );
+ g_list_free_full( pOldCommand, g_free );
+ }
+
+ g_list_free_full( pNewCommand, g_free );
+ }
+
+ while ( pOldCommandList != nullptr )
+ {
+ GList* pCommand = g_list_first( pOldCommandList );
+ pOldCommandList = g_list_remove_link( pOldCommandList, pCommand );
+
+ gchar* aCommand = static_cast<gchar*>(g_list_nth_data( pCommand, 0 ));
+
+ g_lo_action_group_remove( pActionGroup, aCommand );
+
+ g_list_free_full( pCommand, g_free );
+ }
+}
+
+void GtkSalMenu::ImplUpdate(bool bRecurse, bool bRemoveDisabledEntries)
+{
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.unity", "ImplUpdate pre PrepUpdate");
+ if( !PrepUpdate() )
+ return;
+
+ if (mbNeedsUpdate)
+ {
+ mbNeedsUpdate = false;
+ if (mbMenuBar && maUpdateMenuBarIdle.IsActive())
+ {
+ maUpdateMenuBarIdle.Stop();
+ // tdf#124391 Prevent doubled menus in global menu
+ if (!bUnityMode)
+ {
+ maUpdateMenuBarIdle.Invoke();
+ return;
+ }
+ }
+ }
+
+ Menu* pVCLMenu = mpVCLMenu;
+ GLOMenu* pLOMenu = G_LO_MENU( mpMenuModel );
+ GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
+ SAL_INFO("vcl.unity", "Syncing vcl menu " << pVCLMenu << " to menu model " << pLOMenu << " and action group " << pActionGroup);
+ GList *pOldCommandList = nullptr;
+ GList *pNewCommandList = nullptr;
+
+ sal_uInt16 nLOMenuSize = g_menu_model_get_n_items( G_MENU_MODEL( pLOMenu ) );
+
+ if ( nLOMenuSize == 0 )
+ g_lo_menu_new_section( pLOMenu, 0, nullptr );
+
+ sal_Int32 nSection = 0;
+ sal_Int32 nItemPos = 0;
+ sal_Int32 validItems = 0;
+ sal_Int32 nItem;
+
+ for ( nItem = 0; nItem < static_cast<sal_Int32>(GetItemCount()); nItem++ ) {
+ if ( !IsItemVisible( nItem ) )
+ continue;
+
+ GtkSalMenuItem *pSalMenuItem = GetItemAtPos( nItem );
+ sal_uInt16 nId = pSalMenuItem->mnId;
+
+ // PopupMenu::ImplExecute might add <No Selection Possible> entry to top-level
+ // popup menu, but we have our own implementation below, so skip that one.
+ if ( nId == 0xFFFF )
+ continue;
+
+ if ( pSalMenuItem->mnType == MenuItemType::SEPARATOR )
+ {
+ // Delete extra items from current section.
+ RemoveSpareItemsFromNativeMenu( pLOMenu, &pOldCommandList, nSection, validItems );
+
+ nSection++;
+ nItemPos = 0;
+ validItems = 0;
+
+ if ( nLOMenuSize <= nSection )
+ {
+ g_lo_menu_new_section( pLOMenu, nSection, nullptr );
+ nLOMenuSize++;
+ }
+
+ continue;
+ }
+
+ if ( nItemPos >= g_lo_menu_get_n_items_from_section( pLOMenu, nSection ) )
+ g_lo_menu_insert_in_section( pLOMenu, nSection, nItemPos, "EMPTY STRING" );
+
+ // Get internal menu item values.
+ OUString aText = pVCLMenu->GetItemText( nId );
+ Image aImage = pVCLMenu->GetItemImage( nId );
+ bool bEnabled = pVCLMenu->IsItemEnabled( nId );
+ vcl::KeyCode nAccelKey = pVCLMenu->GetAccelKey( nId );
+ bool bChecked = pVCLMenu->IsItemChecked( nId );
+ MenuItemBits itemBits = pVCLMenu->GetItemBits( nId );
+
+ // Store current item command in command list.
+ gchar *aCurrentCommand = g_lo_menu_get_command_from_item_in_section( pLOMenu, nSection, nItemPos );
+
+ if ( aCurrentCommand != nullptr )
+ pOldCommandList = g_list_append( pOldCommandList, aCurrentCommand );
+
+ // Get the new command for the item.
+ gchar* aNativeCommand = GetCommandForItem(pSalMenuItem);
+
+ // Force updating of native menu labels.
+ NativeSetItemText( nSection, nItemPos, aText );
+ NativeSetItemIcon( nSection, nItemPos, aImage );
+ NativeSetAccelerator(nSection, nItemPos, nAccelKey, nAccelKey.GetName());
+
+ if ( g_strcmp0( aNativeCommand, "" ) != 0 && pSalMenuItem->mpSubMenu == nullptr )
+ {
+ NativeSetItemCommand( nSection, nItemPos, nId, aNativeCommand, itemBits, bChecked, false );
+ NativeCheckItem( nSection, nItemPos, itemBits, bChecked );
+ NativeSetEnableItem( aNativeCommand, bEnabled );
+
+ pNewCommandList = g_list_append( pNewCommandList, g_strdup( aNativeCommand ) );
+ }
+
+ GtkSalMenu* pSubmenu = pSalMenuItem->mpSubMenu;
+
+ if ( pSubmenu && pSubmenu->GetMenu() )
+ {
+ bool bNonMenuChangedToMenu = NativeSetItemCommand( nSection, nItemPos, nId, aNativeCommand, itemBits, false, true );
+ pNewCommandList = g_list_append( pNewCommandList, g_strdup( aNativeCommand ) );
+
+ GLOMenu* pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section( pLOMenu, nSection, nItemPos );
+
+ if ( pSubMenuModel == nullptr )
+ {
+ g_lo_menu_new_submenu_in_item_in_section( pLOMenu, nSection, nItemPos );
+ pSubMenuModel = g_lo_menu_get_submenu_from_item_in_section( pLOMenu, nSection, nItemPos );
+ }
+
+ assert(pSubMenuModel);
+
+ if (bRecurse || bNonMenuChangedToMenu)
+ {
+ SAL_INFO("vcl.unity", "preparing submenu " << pSubMenuModel << " to menu model " << G_MENU_MODEL(pSubMenuModel) << " and action group " << G_ACTION_GROUP(pActionGroup));
+ pSubmenu->SetMenuModel( G_MENU_MODEL( pSubMenuModel ) );
+ pSubmenu->SetActionGroup( G_ACTION_GROUP( pActionGroup ) );
+ pSubmenu->ImplUpdate(true, bRemoveDisabledEntries);
+ }
+
+ g_object_unref( pSubMenuModel );
+ }
+
+ g_free( aNativeCommand );
+
+ ++nItemPos;
+ ++validItems;
+ }
+
+ if (bRemoveDisabledEntries)
+ {
+ // Delete disabled items in last section.
+ RemoveDisabledItemsFromNativeMenu(pLOMenu, &pOldCommandList, nSection, G_ACTION_GROUP(pActionGroup));
+ }
+
+ // Delete extra items in last section.
+ RemoveSpareItemsFromNativeMenu( pLOMenu, &pOldCommandList, nSection, validItems );
+
+ // Delete extra sections.
+ RemoveSpareSectionsFromNativeMenu( pLOMenu, &pOldCommandList, nSection );
+
+ // Delete unused commands.
+ RemoveUnusedCommands( pActionGroup, pOldCommandList, pNewCommandList );
+
+ // Resolves: tdf#103166 if the menu is empty, add a disabled
+ // <No Selection Possible> placeholder.
+ sal_Int32 nSectionsCount = g_menu_model_get_n_items(G_MENU_MODEL(pLOMenu));
+ gint nItemsCount = 0;
+ for (nSection = 0; nSection < nSectionsCount; ++nSection)
+ {
+ nItemsCount += g_lo_menu_get_n_items_from_section(pLOMenu, nSection);
+ if (nItemsCount)
+ break;
+ }
+ if (!nItemsCount)
+ {
+ gchar* aNativeCommand = GetCommandForItem(this, 0xFFFF);
+ OUString aPlaceholderText(VclResId(SV_RESID_STRING_NOSELECTIONPOSSIBLE));
+ g_lo_menu_insert_in_section(pLOMenu, nSection-1, 0,
+ OUStringToOString(aPlaceholderText, RTL_TEXTENCODING_UTF8).getStr());
+ NativeSetItemCommand(nSection-1, 0, 0xFFFF, aNativeCommand, MenuItemBits::NONE, false, false);
+ NativeSetEnableItem(aNativeCommand, false);
+ g_free(aNativeCommand);
+ }
+}
+
+void GtkSalMenu::Update()
+{
+ //find out if top level is a menubar or not, if not, then it's a popup menu
+ //hierarchy and in those we hide (most) disabled entries
+ const GtkSalMenu* pMenu = this;
+ while (pMenu->mpParentSalMenu)
+ pMenu = pMenu->mpParentSalMenu;
+
+ bool bAlwaysShowDisabledEntries;
+ if (pMenu->mbMenuBar)
+ bAlwaysShowDisabledEntries = true;
+ else
+ bAlwaysShowDisabledEntries = bool(mpVCLMenu->GetMenuFlags() & MenuFlags::AlwaysShowDisabledEntries);
+
+ ImplUpdate(false, !bAlwaysShowDisabledEntries);
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+static void MenuPositionFunc(GtkMenu* menu, gint* x, gint* y, gboolean* push_in, gpointer user_data)
+{
+ Point *pPos = static_cast<Point*>(user_data);
+ *x = pPos->X();
+ if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL)
+ {
+ GtkRequisition natural_size;
+ gtk_widget_get_preferred_size(GTK_WIDGET(menu), nullptr, &natural_size);
+ *x -= natural_size.width;
+ }
+ *y = pPos->Y();
+ *push_in = false;
+}
+#endif
+
+static void MenuClosed(GtkPopover* pWidget, GMainLoop* pLoop)
+{
+ // gtk4 4.4.0: click on an entry in a submenu of a menu crashes without this workaround
+ gtk_widget_grab_focus(gtk_widget_get_parent(GTK_WIDGET(pWidget)));
+ g_main_loop_quit(pLoop);
+}
+
+bool GtkSalMenu::ShowNativePopupMenu(FloatingWindow* pWin, const tools::Rectangle& rRect,
+ FloatWinPopupFlags nFlags)
+{
+ VclPtr<vcl::Window> xParent = pWin->ImplGetWindowImpl()->mpRealParent;
+ mpFrame = static_cast<GtkSalFrame*>(xParent->ImplGetFrame());
+
+ GLOActionGroup* pActionGroup = g_lo_action_group_new();
+ mpActionGroup = G_ACTION_GROUP(pActionGroup);
+ mpMenuModel = G_MENU_MODEL(g_lo_menu_new());
+ // Generate the main menu structure, populates mpMenuModel
+ UpdateFull();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mpMenuWidget = gtk_menu_new_from_model(mpMenuModel);
+ gtk_menu_attach_to_widget(GTK_MENU(mpMenuWidget), mpFrame->getMouseEventWidget(), nullptr);
+#else
+ mpMenuWidget = gtk_popover_menu_new_from_model(mpMenuModel);
+ gtk_widget_set_parent(mpMenuWidget, mpFrame->getMouseEventWidget());
+ gtk_popover_set_has_arrow(GTK_POPOVER(mpMenuWidget), false);
+#endif
+ gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", mpActionGroup);
+
+ //run in a sub main loop because we need to keep vcl PopupMenu alive to use
+ //it during DispatchCommand, returning now to the outer loop causes the
+ //launching PopupMenu to be destroyed, instead run the subloop here
+ //until the gtk menu is destroyed
+ GMainLoop* pLoop = g_main_loop_new(nullptr, true);
+#if GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_connect(G_OBJECT(mpMenuWidget), "closed", G_CALLBACK(MenuClosed), pLoop);
+#else
+ g_signal_connect(G_OBJECT(mpMenuWidget), "deactivate", G_CALLBACK(MenuClosed), pLoop);
+#endif
+
+
+ // tdf#120764 It isn't allowed under wayland to have two visible popups that share
+ // the same top level parent. The problem is that since gtk 3.24 tooltips are also
+ // implemented as popups, which means that we cannot show any popup if there is a
+ // visible tooltip.
+ // hide any current tooltip
+ mpFrame->HideTooltip();
+ // don't allow any more to appear until menu is dismissed
+ mpFrame->BlockTooltip();
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect);
+ aFloatRect.Move(-mpFrame->maGeometry.nX, -mpFrame->maGeometry.nY);
+ GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
+ static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
+
+ gtk_popover_set_pointing_to(GTK_POPOVER(mpMenuWidget), &rect);
+
+ if (nFlags & FloatWinPopupFlags::Left)
+ gtk_popover_set_position(GTK_POPOVER(mpMenuWidget), GTK_POS_LEFT);
+ else if (nFlags & FloatWinPopupFlags::Up)
+ gtk_popover_set_position(GTK_POPOVER(mpMenuWidget), GTK_POS_TOP);
+ else if (nFlags & FloatWinPopupFlags::Right)
+ gtk_popover_set_position(GTK_POPOVER(mpMenuWidget), GTK_POS_RIGHT);
+ else
+ gtk_popover_set_position(GTK_POPOVER(mpMenuWidget), GTK_POS_BOTTOM);
+
+ gtk_popover_popup(GTK_POPOVER(mpMenuWidget));
+#else
+#if GTK_CHECK_VERSION(3,22,0)
+ if (gtk_check_version(3, 22, 0) == nullptr)
+ {
+ tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(xParent, rRect);
+ aFloatRect.Move(-mpFrame->maGeometry.nX, -mpFrame->maGeometry.nY);
+ GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
+ static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
+
+ GdkGravity rect_anchor = GDK_GRAVITY_SOUTH_WEST, menu_anchor = GDK_GRAVITY_NORTH_WEST;
+
+ if (nFlags & FloatWinPopupFlags::Left)
+ {
+ rect_anchor = GDK_GRAVITY_NORTH_WEST;
+ menu_anchor = GDK_GRAVITY_NORTH_EAST;
+ }
+ else if (nFlags & FloatWinPopupFlags::Up)
+ {
+ rect_anchor = GDK_GRAVITY_NORTH_WEST;
+ menu_anchor = GDK_GRAVITY_SOUTH_WEST;
+ }
+ else if (nFlags & FloatWinPopupFlags::Right)
+ {
+ rect_anchor = GDK_GRAVITY_NORTH_EAST;
+ }
+
+ GdkSurface* gdkWindow = widget_get_surface(mpFrame->getMouseEventWidget());
+ gtk_menu_popup_at_rect(GTK_MENU(mpMenuWidget), gdkWindow, &rect, rect_anchor, menu_anchor, nullptr);
+ }
+ else
+#endif
+ {
+ guint nButton;
+ guint32 nTime;
+
+ //typically there is an event, and we can then distinguish if this was
+ //launched from the keyboard (gets auto-mnemoniced) or the mouse (which
+ //doesn't)
+ GdkEvent *pEvent = gtk_get_current_event();
+ if (pEvent)
+ {
+ gdk_event_get_button(pEvent, &nButton);
+ nTime = gdk_event_get_time(pEvent);
+ }
+ else
+ {
+ nButton = 0;
+ nTime = GtkSalFrame::GetLastInputEventTime();
+ }
+
+ // do the same strange semantics as vcl popup windows to arrive at a frame geometry
+ // in mirrored UI case; best done by actually executing the same code
+ sal_uInt16 nArrangeIndex;
+ Point aPos = FloatingWindow::ImplCalcPos(pWin, rRect, nFlags, nArrangeIndex);
+ aPos = FloatingWindow::ImplConvertToAbsPos(xParent, aPos);
+
+ gtk_menu_popup(GTK_MENU(mpMenuWidget), nullptr, nullptr, MenuPositionFunc,
+ &aPos, nButton, nTime);
+ }
+#endif
+
+ if (g_main_loop_is_running(pLoop))
+ main_loop_run(pLoop);
+
+ g_main_loop_unref(pLoop);
+
+ mpVCLMenu->Deactivate();
+
+ g_object_unref(mpActionGroup);
+ ClearActionGroupAndMenuModel();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(mpMenuWidget);
+#else
+ gtk_widget_unparent(mpMenuWidget);
+#endif
+ mpMenuWidget = nullptr;
+
+ gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", nullptr);
+
+ // undo tooltip blocking
+ mpFrame->UnblockTooltip();
+
+ mpFrame = nullptr;
+
+ return true;
+}
+
+/*
+ * GtkSalMenu
+ */
+
+GtkSalMenu::GtkSalMenu( bool bMenuBar ) :
+ maUpdateMenuBarIdle("Native Gtk Menu Update Idle"),
+ mbInActivateCallback( false ),
+ mbMenuBar( bMenuBar ),
+ mbNeedsUpdate( false ),
+ mbReturnFocusToDocument( false ),
+ mbAddedGrab( false ),
+ mpMenuBarContainerWidget( nullptr ),
+ mpMenuAllowShrinkWidget( nullptr ),
+ mpMenuBarWidget( nullptr ),
+ mpMenuWidget( nullptr ),
+ mpMenuBarContainerProvider( nullptr ),
+ mpMenuBarProvider( nullptr ),
+ mpCloseButton( nullptr ),
+ mpVCLMenu( nullptr ),
+ mpParentSalMenu( nullptr ),
+ mpFrame( nullptr ),
+ mpMenuModel( nullptr ),
+ mpActionGroup( nullptr )
+{
+ //typically this only gets called after the menu has been customized on the
+ //next idle slot, in the normal case of a new menubar SetFrame is called
+ //directly long before this idle would get called.
+ maUpdateMenuBarIdle.SetPriority(TaskPriority::HIGHEST);
+ maUpdateMenuBarIdle.SetInvokeHandler(LINK(this, GtkSalMenu, MenuBarHierarchyChangeHandler));
+}
+
+IMPL_LINK_NOARG(GtkSalMenu, MenuBarHierarchyChangeHandler, Timer *, void)
+{
+ SAL_WARN_IF(!mpFrame, "vcl.gtk", "MenuBar layout changed, but no frame for some reason!");
+ if (!mpFrame)
+ return;
+ SetFrame(mpFrame);
+}
+
+void GtkSalMenu::SetNeedsUpdate()
+{
+ GtkSalMenu* pMenu = this;
+ // start that the menu and its parents are in need of an update
+ // on the next activation
+ while (pMenu && !pMenu->mbNeedsUpdate)
+ {
+ pMenu->mbNeedsUpdate = true;
+ pMenu = pMenu->mpParentSalMenu;
+ }
+ // only if a menubar is directly updated do we force in a full
+ // structure update
+ if (mbMenuBar && !maUpdateMenuBarIdle.IsActive())
+ maUpdateMenuBarIdle.Start();
+}
+
+void GtkSalMenu::SetMenuModel(GMenuModel* pMenuModel)
+{
+ if (mpMenuModel)
+ g_object_unref(mpMenuModel);
+ mpMenuModel = pMenuModel;
+ if (mpMenuModel)
+ g_object_ref(mpMenuModel);
+}
+
+GtkSalMenu::~GtkSalMenu()
+{
+ SolarMutexGuard aGuard;
+
+ // tdf#140225 we expect all items to be removed by Menu::dispose
+ // before this dtor is called
+ assert(maItems.empty());
+
+ DestroyMenuBarWidget();
+
+ if (mpMenuModel)
+ g_object_unref(mpMenuModel);
+
+ if (mpFrame)
+ mpFrame->SetMenu(nullptr);
+}
+
+bool GtkSalMenu::VisibleMenuBar()
+{
+ return mbMenuBar && (bUnityMode || mpMenuBarContainerWidget);
+}
+
+void GtkSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos )
+{
+ SolarMutexGuard aGuard;
+ GtkSalMenuItem *pItem = static_cast<GtkSalMenuItem*>( pSalMenuItem );
+
+ if ( nPos == MENU_APPEND )
+ maItems.push_back( pItem );
+ else
+ maItems.insert( maItems.begin() + nPos, pItem );
+
+ pItem->mpParentMenu = this;
+
+ SetNeedsUpdate();
+}
+
+void GtkSalMenu::RemoveItem( unsigned nPos )
+{
+ SolarMutexGuard aGuard;
+
+ // tdf#140225 clear associated action when the item is removed
+ if (mpActionGroup)
+ {
+ GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP(mpActionGroup);
+ gchar* pCommand = GetCommandForItem(maItems[nPos]);
+ g_lo_action_group_remove(pActionGroup, pCommand);
+ g_free(pCommand);
+ }
+
+ maItems.erase( maItems.begin() + nPos );
+ SetNeedsUpdate();
+}
+
+void GtkSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned )
+{
+ SolarMutexGuard aGuard;
+ GtkSalMenuItem *pItem = static_cast< GtkSalMenuItem* >( pSalMenuItem );
+ GtkSalMenu *pGtkSubMenu = static_cast< GtkSalMenu* >( pSubMenu );
+
+ if ( pGtkSubMenu == nullptr )
+ return;
+
+ pGtkSubMenu->mpParentSalMenu = this;
+ pItem->mpSubMenu = pGtkSubMenu;
+
+ SetNeedsUpdate();
+}
+
+static void CloseMenuBar(GtkWidget *, gpointer pMenu)
+{
+ Application::PostUserEvent(static_cast<MenuBar*>(pMenu)->GetCloseButtonClickHdl());
+}
+
+GtkWidget* GtkSalMenu::AddButton(GtkWidget *pImage)
+{
+ GtkWidget* pButton = gtk_button_new();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_button_set_relief(GTK_BUTTON(pButton), GTK_RELIEF_NONE);
+ gtk_button_set_focus_on_click(GTK_BUTTON(pButton), false);
+#else
+ gtk_button_set_has_frame(GTK_BUTTON(pButton), false);
+ gtk_widget_set_focus_on_click(pButton, false);
+#endif
+
+ gtk_widget_set_can_focus(pButton, false);
+
+ GtkStyleContext *pButtonContext = gtk_widget_get_style_context(GTK_WIDGET(pButton));
+
+ gtk_style_context_add_class(pButtonContext, "flat");
+ gtk_style_context_add_class(pButtonContext, "small-button");
+
+ gtk_widget_show(pImage);
+
+ gtk_widget_set_valign(pButton, GTK_ALIGN_CENTER);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(pButton), pImage);
+ gtk_widget_show_all(pButton);
+#else
+ gtk_button_set_child(GTK_BUTTON(pButton), pImage);
+#endif
+ return pButton;
+}
+
+void GtkSalMenu::ShowCloseButton(bool bShow)
+{
+ assert(mbMenuBar);
+ if (!mpMenuBarContainerWidget)
+ return;
+
+ if (!bShow)
+ {
+ if (mpCloseButton)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(mpCloseButton);
+#else
+ g_clear_pointer(&mpCloseButton, gtk_widget_unparent);
+#endif
+ mpCloseButton = nullptr;
+ }
+ return;
+ }
+
+ if (mpCloseButton)
+ return;
+
+ GIcon* pIcon = g_themed_icon_new_with_default_fallbacks("window-close-symbolic");
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pImage = gtk_image_new_from_gicon(pIcon, GTK_ICON_SIZE_MENU);
+#else
+ GtkWidget* pImage = gtk_image_new_from_gicon(pIcon);
+#endif
+ g_object_unref(pIcon);
+
+ mpCloseButton = AddButton(pImage);
+
+ gtk_widget_set_margin_end(mpCloseButton, 8);
+
+ OUString sToolTip(VclResId(SV_HELPTEXT_CLOSEDOCUMENT));
+ gtk_widget_set_tooltip_text(mpCloseButton, sToolTip.toUtf8().getStr());
+
+ MenuBar *pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
+ g_signal_connect(mpCloseButton, "clicked", G_CALLBACK(CloseMenuBar), pVclMenuBar);
+
+ gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), mpCloseButton, 1, 0, 1, 1);
+}
+
+namespace
+{
+ void DestroyMemoryStream(gpointer data)
+ {
+ SvMemoryStream* pMemStm = static_cast<SvMemoryStream*>(data);
+ delete pMemStm;
+ }
+}
+
+static void MenuButtonClicked(GtkWidget* pWidget, gpointer pMenu)
+{
+ OString aId(get_buildable_id(GTK_BUILDABLE(pWidget)));
+ static_cast<MenuBar*>(pMenu)->HandleMenuButtonEvent(aId.toUInt32());
+}
+
+bool GtkSalMenu::AddMenuBarButton(const SalMenuButtonItem& rNewItem)
+{
+ if (!mbMenuBar)
+ return false;
+
+ if (!mpMenuBarContainerWidget)
+ return false;
+
+ GtkWidget* pImage = nullptr;
+ if (!!rNewItem.maImage)
+ {
+ SvMemoryStream* pMemStm = new SvMemoryStream;
+ vcl::PNGWriter aWriter(rNewItem.maImage.GetBitmapEx());
+ aWriter.Write(*pMemStm);
+
+ GBytes *pBytes = g_bytes_new_with_free_func(pMemStm->GetData(),
+ pMemStm->TellEnd(),
+ DestroyMemoryStream,
+ pMemStm);
+
+ GIcon *pIcon = g_bytes_icon_new(pBytes);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ pImage = gtk_image_new_from_gicon(pIcon, GTK_ICON_SIZE_MENU);
+#else
+ pImage = gtk_image_new_from_gicon(pIcon);
+#endif
+ g_object_unref(pIcon);
+ }
+
+ GtkWidget* pButton = AddButton(pImage);
+
+ maExtraButtons.emplace_back(rNewItem.mnId, pButton);
+
+ set_buildable_id(GTK_BUILDABLE(pButton), OString::number(rNewItem.mnId).getStr());
+
+ gtk_widget_set_tooltip_text(pButton, rNewItem.maToolTipText.toUtf8().getStr());
+
+ MenuBar *pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
+ g_signal_connect(pButton, "clicked", G_CALLBACK(MenuButtonClicked), pVclMenuBar);
+
+ if (mpCloseButton)
+ {
+ gtk_grid_insert_next_to(GTK_GRID(mpMenuBarContainerWidget), mpCloseButton, GTK_POS_LEFT);
+ gtk_grid_attach_next_to(GTK_GRID(mpMenuBarContainerWidget), pButton, mpCloseButton,
+ GTK_POS_LEFT, 1, 1);
+ }
+ else
+ gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), pButton, 1, 0, 1, 1);
+
+ return true;
+}
+
+void GtkSalMenu::RemoveMenuBarButton( sal_uInt16 nId )
+{
+ const auto it = std::find_if(maExtraButtons.begin(), maExtraButtons.end(), [&nId](const auto &item) {
+ return item.first == nId; });
+ if (it == maExtraButtons.end())
+ return;
+
+ gint nAttach(0);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_child_get(GTK_CONTAINER(mpMenuBarContainerWidget), it->second, "left-attach", &nAttach, nullptr);
+ gtk_widget_destroy(it->second);
+#else
+ gtk_grid_query_child(GTK_GRID(mpMenuBarContainerWidget), it->second, &nAttach, nullptr, nullptr, nullptr);
+ g_clear_pointer(&(it->second), gtk_widget_unparent);
+#endif
+ gtk_grid_remove_column(GTK_GRID(mpMenuBarContainerWidget), nAttach);
+ maExtraButtons.erase(it);
+}
+
+tools::Rectangle GtkSalMenu::GetMenuBarButtonRectPixel(sal_uInt16 nId, SalFrame* pReferenceFrame)
+{
+ if (!pReferenceFrame)
+ return tools::Rectangle();
+
+ const auto it = std::find_if(maExtraButtons.begin(), maExtraButtons.end(), [&nId](const auto &item) {
+ return item.first == nId; });
+ if (it == maExtraButtons.end())
+ return tools::Rectangle();
+
+ GtkWidget* pButton = it->second;
+
+ GtkSalFrame* pFrame = static_cast<GtkSalFrame*>(pReferenceFrame);
+
+ gtk_coord x, y;
+ if (!gtk_widget_translate_coordinates(pButton, GTK_WIDGET(pFrame->getMouseEventWidget()), 0, 0, &x, &y))
+ return tools::Rectangle();
+
+ return tools::Rectangle(Point(x, y), Size(gtk_widget_get_allocated_width(pButton),
+ gtk_widget_get_allocated_height(pButton)));
+}
+
+//Typically when the menubar is deactivated we want the focus to return
+//to where it came from. If the menubar was activated because of F6
+//moving focus into the associated VCL menubar then on pressing ESC
+//or any other normal reason for deactivation we want focus to return
+//to the document, definitely not still stuck in the associated
+//VCL menubar. But if F6 is pressed while the menubar is activated
+//we want to pass that F6 back to the VCL menubar which will move
+//focus to the next pane by itself.
+void GtkSalMenu::ReturnFocus()
+{
+ if (mbAddedGrab)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_grab_remove(mpMenuBarWidget);
+#endif
+ mbAddedGrab = false;
+ }
+ if (!mbReturnFocusToDocument)
+ gtk_widget_grab_focus(mpFrame->getMouseEventWidget());
+ else
+ mpFrame->GetWindow()->GrabFocusToDocument();
+ mbReturnFocusToDocument = false;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+gboolean GtkSalMenu::SignalKey(GdkEventKey const * pEvent)
+{
+ if (pEvent->keyval == GDK_KEY_F6)
+ {
+ mbReturnFocusToDocument = false;
+ gtk_menu_shell_cancel(GTK_MENU_SHELL(mpMenuBarWidget));
+ //because we return false here, the keypress will continue
+ //to propagate and in the case that vcl focus is in
+ //the vcl menubar then that will also process F6 and move
+ //to the next pane
+ }
+ return false;
+}
+#endif
+
+//The GtkSalMenu is owned by a Vcl Menu/MenuBar. In the menubar
+//case the vcl menubar is present and "visible", but with a 0 height
+//so it not apparent. Normally it acts as though it is not there when
+//a Native menubar is active. If we return true here, then for keyboard
+//activation and traversal with F6 through panes then the vcl menubar
+//acts as though it *is* present and we translate its take focus and F6
+//traversal key events into the gtk menubar equivalents.
+bool GtkSalMenu::CanGetFocus() const
+{
+ return mpMenuBarWidget != nullptr;
+}
+
+bool GtkSalMenu::TakeFocus()
+{
+ if (!mpMenuBarWidget)
+ return false;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ //Send a keyboard event to the gtk menubar to let it know it has been
+ //activated via the keyboard. Doesn't do anything except cause the gtk
+ //menubar "keyboard_mode" member to get set to true, so typically mnemonics
+ //are shown which will serve as indication that the menubar has focus
+ //(given that we want to show it with no menus popped down)
+ GdkEvent *event = GtkSalFrame::makeFakeKeyPress(mpMenuBarWidget);
+ gtk_widget_event(mpMenuBarWidget, event);
+ gdk_event_free(event);
+
+ //this pairing results in a menubar with keyboard focus with no menus
+ //auto-popped down
+ gtk_grab_add(mpMenuBarWidget);
+
+ mbAddedGrab = true;
+ gtk_menu_shell_select_first(GTK_MENU_SHELL(mpMenuBarWidget), false);
+ gtk_menu_shell_deselect(GTK_MENU_SHELL(mpMenuBarWidget));
+#endif
+ mbReturnFocusToDocument = true;
+ return true;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+static void MenuBarReturnFocus(GtkMenuShell*, gpointer menu)
+{
+ GtkSalFrame::UpdateLastInputEventTime(gtk_get_current_event_time());
+ GtkSalMenu* pMenu = static_cast<GtkSalMenu*>(menu);
+ pMenu->ReturnFocus();
+}
+
+static gboolean MenuBarSignalKey(GtkWidget*, GdkEventKey* pEvent, gpointer menu)
+{
+ GtkSalMenu* pMenu = static_cast<GtkSalMenu*>(menu);
+ return pMenu->SignalKey(pEvent);
+}
+#endif
+
+void GtkSalMenu::CreateMenuBarWidget()
+{
+ if (mpMenuBarContainerWidget)
+ return;
+
+ GtkGrid* pGrid = mpFrame->getTopLevelGridWidget();
+ mpMenuBarContainerWidget = gtk_grid_new();
+
+ gtk_widget_set_hexpand(GTK_WIDGET(mpMenuBarContainerWidget), true);
+ gtk_grid_insert_row(pGrid, 0);
+ gtk_grid_attach(pGrid, mpMenuBarContainerWidget, 0, 0, 1, 1);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mpMenuAllowShrinkWidget = gtk_scrolled_window_new(nullptr, nullptr);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_SHADOW_NONE);
+ // tdf#129634 don't allow this scrolled window as a candidate to tab into
+ gtk_widget_set_can_focus(GTK_WIDGET(mpMenuAllowShrinkWidget), false);
+#else
+ mpMenuAllowShrinkWidget = gtk_scrolled_window_new();
+ gtk_scrolled_window_set_has_frame(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), false);
+#endif
+ // tdf#116290 external policy on scrolledwindow will not show a scrollbar,
+ // but still allow scrolled window to not be sized to the child content.
+ // So the menubar can be shrunk past its nominal smallest width.
+ // Unlike a hack using GtkFixed/GtkLayout the correct placement of the menubar occurs under RTL
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_POLICY_EXTERNAL, GTK_POLICY_NEVER);
+ gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), mpMenuAllowShrinkWidget, 0, 0, 1, 1);
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mpMenuBarWidget = gtk_menu_bar_new_from_model(mpMenuModel);
+#else
+ mpMenuBarWidget = gtk_popover_menu_bar_new_from_model(mpMenuModel);
+#endif
+
+ gtk_widget_insert_action_group(mpMenuBarWidget, "win", mpActionGroup);
+ gtk_widget_set_hexpand(GTK_WIDGET(mpMenuBarWidget), true);
+ gtk_widget_set_hexpand(mpMenuAllowShrinkWidget, true);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_container_add(GTK_CONTAINER(mpMenuAllowShrinkWidget), mpMenuBarWidget);
+#else
+ gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), mpMenuBarWidget);
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_signal_connect(G_OBJECT(mpMenuBarWidget), "deactivate", G_CALLBACK(MenuBarReturnFocus), this);
+ g_signal_connect(G_OBJECT(mpMenuBarWidget), "key-press-event", G_CALLBACK(MenuBarSignalKey), this);
+#endif
+
+ gtk_widget_show(mpMenuBarWidget);
+ gtk_widget_show(mpMenuAllowShrinkWidget);
+ gtk_widget_show(mpMenuBarContainerWidget);
+
+ ShowCloseButton( static_cast<MenuBar*>(mpVCLMenu.get())->HasCloseButton() );
+
+ ApplyPersona();
+}
+
+void GtkSalMenu::ApplyPersona()
+{
+ if (!mpMenuBarContainerWidget)
+ return;
+ assert(mbMenuBar);
+ // I'm dubious about the persona theming feature, but as it exists, lets try and support
+ // it, apply the image to the mpMenuBarContainerWidget
+ const BitmapEx& rPersonaBitmap = Application::GetSettings().GetStyleSettings().GetPersonaHeader();
+
+ GtkStyleContext *pMenuBarContainerContext = gtk_widget_get_style_context(GTK_WIDGET(mpMenuBarContainerWidget));
+ if (mpMenuBarContainerProvider)
+ {
+ gtk_style_context_remove_provider(pMenuBarContainerContext, GTK_STYLE_PROVIDER(mpMenuBarContainerProvider));
+ mpMenuBarContainerProvider = nullptr;
+ }
+ GtkStyleContext *pMenuBarContext = gtk_widget_get_style_context(GTK_WIDGET(mpMenuBarWidget));
+ if (mpMenuBarProvider)
+ {
+ gtk_style_context_remove_provider(pMenuBarContext, GTK_STYLE_PROVIDER(mpMenuBarProvider));
+ mpMenuBarProvider = nullptr;
+ }
+
+ if (!rPersonaBitmap.IsEmpty())
+ {
+ if (maPersonaBitmap != rPersonaBitmap)
+ {
+ vcl::PNGWriter aPNGWriter(rPersonaBitmap);
+ mxPersonaImage.reset(new utl::TempFile);
+ mxPersonaImage->EnableKillingFile(true);
+ SvStream* pStream = mxPersonaImage->GetStream(StreamMode::WRITE);
+ aPNGWriter.Write(*pStream);
+ mxPersonaImage->CloseStream();
+ }
+
+ mpMenuBarContainerProvider = gtk_css_provider_new();
+ OUString aBuffer = "* { background-image: url(\"" + mxPersonaImage->GetURL() + "\"); background-position: top right; }";
+ OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
+ css_provider_load_from_data(mpMenuBarContainerProvider, aResult.getStr(), aResult.getLength());
+ gtk_style_context_add_provider(pMenuBarContainerContext, GTK_STYLE_PROVIDER(mpMenuBarContainerProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+
+ // force the menubar to be transparent when persona is active otherwise for
+ // me the menubar becomes gray when its in the backdrop
+ mpMenuBarProvider = gtk_css_provider_new();
+ static const gchar data[] = "* { "
+ "background-image: none;"
+ "background-color: transparent;"
+ "}";
+ css_provider_load_from_data(mpMenuBarProvider, data, -1);
+ gtk_style_context_add_provider(pMenuBarContext,
+ GTK_STYLE_PROVIDER(mpMenuBarProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ maPersonaBitmap = rPersonaBitmap;
+}
+
+void GtkSalMenu::DestroyMenuBarWidget()
+{
+ if (!mpMenuBarContainerWidget)
+ return;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // tdf#140225 call cancel before destroying it in case there are some
+ // active menus popped open
+ gtk_menu_shell_cancel(GTK_MENU_SHELL(mpMenuBarWidget));
+
+ gtk_widget_destroy(mpMenuBarContainerWidget);
+#else
+ g_clear_pointer(&mpMenuBarContainerWidget, gtk_widget_unparent);
+#endif
+ mpMenuBarContainerWidget = nullptr;
+ mpMenuBarWidget = nullptr;
+ mpCloseButton = nullptr;
+}
+
+void GtkSalMenu::SetFrame(const SalFrame* pFrame)
+{
+ SolarMutexGuard aGuard;
+ assert(mbMenuBar);
+ SAL_INFO("vcl.unity", "GtkSalMenu set to frame");
+ mpFrame = const_cast<GtkSalFrame*>(static_cast<const GtkSalFrame*>(pFrame));
+
+ // if we had a menu on the GtkSalMenu we have to free it as we generate a
+ // full menu anyway and we might need to reuse an existing model and
+ // actiongroup
+ mpFrame->SetMenu( this );
+ mpFrame->EnsureAppMenuWatch();
+
+ // Clean menu model and action group if needed.
+ GtkWidget* pWidget = mpFrame->getWindow();
+ GdkSurface* gdkWindow = widget_get_surface(pWidget);
+
+ GLOMenu* pMenuModel = G_LO_MENU( g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) );
+ GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-action-group" ) );
+ SAL_INFO("vcl.unity", "Found menu model: " << pMenuModel << " and action group: " << pActionGroup);
+
+ if ( pMenuModel )
+ {
+ if ( g_menu_model_get_n_items( G_MENU_MODEL( pMenuModel ) ) > 0 )
+ g_lo_menu_remove( pMenuModel, 0 );
+
+ mpMenuModel = G_MENU_MODEL( g_lo_menu_new() );
+ }
+
+ if ( pActionGroup )
+ {
+ g_lo_action_group_clear( pActionGroup );
+ mpActionGroup = G_ACTION_GROUP( pActionGroup );
+ }
+
+ // Generate the main menu structure.
+ if ( PrepUpdate() )
+ UpdateFull();
+
+ g_lo_menu_insert_section( pMenuModel, 0, nullptr, mpMenuModel );
+
+ if (!bUnityMode && static_cast<MenuBar*>(mpVCLMenu.get())->IsDisplayable())
+ {
+ DestroyMenuBarWidget();
+ CreateMenuBarWidget();
+ }
+}
+
+const GtkSalFrame* GtkSalMenu::GetFrame() const
+{
+ SolarMutexGuard aGuard;
+ const GtkSalMenu* pMenu = this;
+ while( pMenu && ! pMenu->mpFrame )
+ pMenu = pMenu->mpParentSalMenu;
+ return pMenu ? pMenu->mpFrame : nullptr;
+}
+
+void GtkSalMenu::NativeCheckItem( unsigned nSection, unsigned nItemPos, MenuItemBits bits, gboolean bCheck )
+{
+ SolarMutexGuard aGuard;
+
+ if ( mpActionGroup == nullptr )
+ return;
+
+ gchar* aCommand = g_lo_menu_get_command_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );
+
+ if ( aCommand != nullptr || g_strcmp0( aCommand, "" ) != 0 )
+ {
+ GVariant *pCheckValue = nullptr;
+ GVariant *pCurrentState = g_action_group_get_action_state( mpActionGroup, aCommand );
+
+ if ( bits & MenuItemBits::RADIOCHECK )
+ pCheckValue = bCheck ? g_variant_new_string( aCommand ) : g_variant_new_string( "" );
+ else
+ {
+ // By default, all checked items are checkmark buttons.
+ if (bCheck || pCurrentState != nullptr)
+ pCheckValue = g_variant_new_boolean( bCheck );
+ }
+
+ if ( pCheckValue != nullptr )
+ {
+ if ( pCurrentState == nullptr || g_variant_equal( pCurrentState, pCheckValue ) == FALSE )
+ {
+ g_action_group_change_action_state( mpActionGroup, aCommand, pCheckValue );
+ }
+ else
+ {
+ g_variant_unref (pCheckValue);
+ }
+ }
+
+ if ( pCurrentState != nullptr )
+ g_variant_unref( pCurrentState );
+ }
+
+ if ( aCommand )
+ g_free( aCommand );
+}
+
+void GtkSalMenu::NativeSetEnableItem( gchar const * aCommand, gboolean bEnable )
+{
+ SolarMutexGuard aGuard;
+ GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
+
+ if ( g_action_group_get_action_enabled( G_ACTION_GROUP( pActionGroup ), aCommand ) != bEnable )
+ g_lo_action_group_set_action_enabled( pActionGroup, aCommand, bEnable );
+}
+
+void GtkSalMenu::NativeSetItemText( unsigned nSection, unsigned nItemPos, const OUString& rText )
+{
+ SolarMutexGuard aGuard;
+ // Escape all underscores so that they don't get interpreted as hotkeys
+ OUString aText = rText.replaceAll( "_", "__" );
+ // Replace the LibreOffice hotkey identifier with an underscore
+ aText = aText.replace( '~', '_' );
+ OString aConvertedText = OUStringToOString( aText, RTL_TEXTENCODING_UTF8 );
+
+ // Update item text only when necessary.
+ gchar* aLabel = g_lo_menu_get_label_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );
+
+ if ( aLabel == nullptr || g_strcmp0( aLabel, aConvertedText.getStr() ) != 0 )
+ g_lo_menu_set_label_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, aConvertedText.getStr() );
+
+ if ( aLabel )
+ g_free( aLabel );
+}
+
+void GtkSalMenu::NativeSetItemIcon( unsigned nSection, unsigned nItemPos, const Image& rImage )
+{
+#if GLIB_CHECK_VERSION(2,38,0)
+ if (!rImage && mbHasNullItemIcon)
+ return;
+
+ SolarMutexGuard aGuard;
+
+ if (!!rImage)
+ {
+ SvMemoryStream* pMemStm = new SvMemoryStream;
+ vcl::PNGWriter aWriter(rImage.GetBitmapEx());
+ aWriter.Write(*pMemStm);
+
+ GBytes *pBytes = g_bytes_new_with_free_func(pMemStm->GetData(),
+ pMemStm->TellEnd(),
+ DestroyMemoryStream,
+ pMemStm);
+
+ GIcon *pIcon = g_bytes_icon_new(pBytes);
+
+ g_lo_menu_set_icon_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, pIcon );
+ g_object_unref(pIcon);
+ g_bytes_unref(pBytes);
+ mbHasNullItemIcon = false;
+ }
+ else
+ {
+ g_lo_menu_set_icon_to_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos, nullptr );
+ mbHasNullItemIcon = true;
+ }
+#else
+ (void)nSection;
+ (void)nItemPos;
+ (void)rImage;
+#endif
+}
+
+void GtkSalMenu::NativeSetAccelerator( unsigned nSection, unsigned nItemPos, const vcl::KeyCode& rKeyCode, std::u16string_view rKeyName )
+{
+ SolarMutexGuard aGuard;
+
+ if ( rKeyName.empty() )
+ return;
+
+ guint nKeyCode;
+ GdkModifierType nModifiers;
+ GtkSalFrame::KeyCodeToGdkKey(rKeyCode, &nKeyCode, &nModifiers);
+
+ gchar* aAccelerator = gtk_accelerator_name( nKeyCode, nModifiers );
+
+ gchar* aCurrentAccel = g_lo_menu_get_accelerator_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItemPos );
+
+ if ( aCurrentAccel == nullptr && g_strcmp0( aCurrentAccel, aAccelerator ) != 0 )
+ g_lo_menu_set_accelerator_to_item_in_section ( G_LO_MENU( mpMenuModel ), nSection, nItemPos, aAccelerator );
+
+ g_free( aAccelerator );
+ g_free( aCurrentAccel );
+}
+
+bool GtkSalMenu::NativeSetItemCommand( unsigned nSection,
+ unsigned nItemPos,
+ sal_uInt16 nId,
+ const gchar* aCommand,
+ MenuItemBits nBits,
+ bool bChecked,
+ bool bIsSubmenu )
+{
+ bool bSubMenuAddedOrRemoved = false;
+
+ SolarMutexGuard aGuard;
+ GLOActionGroup* pActionGroup = G_LO_ACTION_GROUP( mpActionGroup );
+
+ GVariant *pTarget = nullptr;
+
+ if (g_action_group_has_action(mpActionGroup, aCommand))
+ g_lo_action_group_remove(pActionGroup, aCommand);
+
+ if ( ( nBits & MenuItemBits::CHECKABLE ) || bIsSubmenu )
+ {
+ // Item is a checkmark button.
+ GVariantType* pStateType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_BOOLEAN) );
+ GVariant* pState = g_variant_new_boolean( bChecked );
+
+ g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, bIsSubmenu, nullptr, pStateType, nullptr, pState );
+ }
+ else if ( nBits & MenuItemBits::RADIOCHECK )
+ {
+ // Item is a radio button.
+ GVariantType* pParameterType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_STRING) );
+ GVariantType* pStateType = g_variant_type_new( reinterpret_cast<gchar const *>(G_VARIANT_TYPE_STRING) );
+ GVariant* pState = g_variant_new_string( "" );
+ pTarget = g_variant_new_string( aCommand );
+
+ g_lo_action_group_insert_stateful( pActionGroup, aCommand, nId, FALSE, pParameterType, pStateType, nullptr, pState );
+ }
+ else
+ {
+ // Item is not special, so insert a stateless action.
+ g_lo_action_group_insert( pActionGroup, aCommand, nId, FALSE );
+ }
+
+ GLOMenu* pMenu = G_LO_MENU( mpMenuModel );
+
+ // Menu item is not updated unless it's necessary.
+ gchar* aCurrentCommand = g_lo_menu_get_command_from_item_in_section( pMenu, nSection, nItemPos );
+
+ if ( aCurrentCommand == nullptr || g_strcmp0( aCurrentCommand, aCommand ) != 0 )
+ {
+ bool bOldHasSubmenu = g_lo_menu_get_submenu_from_item_in_section(pMenu, nSection, nItemPos) != nullptr;
+ bSubMenuAddedOrRemoved = bOldHasSubmenu != bIsSubmenu;
+ if (bSubMenuAddedOrRemoved)
+ {
+ //tdf#98636 it's not good enough to unset the "submenu-action" attribute to change something
+ //from a submenu to a non-submenu item, so remove the old one entirely and re-add it to
+ //support achieving that
+ gchar* pLabel = g_lo_menu_get_label_from_item_in_section(pMenu, nSection, nItemPos);
+ g_lo_menu_remove_from_section(pMenu, nSection, nItemPos);
+ g_lo_menu_insert_in_section(pMenu, nSection, nItemPos, pLabel);
+ g_free(pLabel);
+ }
+
+ g_lo_menu_set_command_to_item_in_section( pMenu, nSection, nItemPos, aCommand );
+
+ gchar* aItemCommand = g_strconcat("win.", aCommand, nullptr );
+
+ if ( bIsSubmenu )
+ g_lo_menu_set_submenu_action_to_item_in_section( pMenu, nSection, nItemPos, aItemCommand );
+ else
+ {
+ g_lo_menu_set_action_and_target_value_to_item_in_section( pMenu, nSection, nItemPos, aItemCommand, pTarget );
+ pTarget = nullptr;
+ }
+
+ g_free( aItemCommand );
+ }
+
+ if ( aCurrentCommand )
+ g_free( aCurrentCommand );
+
+ if (pTarget)
+ g_variant_unref(pTarget);
+
+ return bSubMenuAddedOrRemoved;
+}
+
+GtkSalMenu* GtkSalMenu::GetTopLevel()
+{
+ GtkSalMenu *pMenu = this;
+ while (pMenu->mpParentSalMenu)
+ pMenu = pMenu->mpParentSalMenu;
+ return pMenu;
+}
+
+void GtkSalMenu::DispatchCommand(const gchar *pCommand)
+{
+ SolarMutexGuard aGuard;
+ MenuAndId aMenuAndId = decode_command(pCommand);
+ GtkSalMenu* pSalSubMenu = aMenuAndId.first;
+ GtkSalMenu* pTopLevel = pSalSubMenu->GetTopLevel();
+
+ // tdf#125803 spacebar will toggle radios and checkbuttons without automatically
+ // closing the menu. To handle this properly I imagine we need to set groups for the
+ // radiobuttons so the others visually untoggle when the active one is toggled and
+ // we would further need to teach vcl that the state can change more than once.
+ //
+ // or we could unconditionally deactivate the menus if regardless of what particular
+ // type of menu item got activated
+ if (pTopLevel->mpMenuBarWidget)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_menu_shell_deactivate(GTK_MENU_SHELL(pTopLevel->mpMenuBarWidget));
+#endif
+ }
+ if (pTopLevel->mpMenuWidget)
+ {
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_popover_popdown(GTK_POPOVER(pTopLevel->mpMenuWidget));
+#else
+ gtk_menu_shell_deactivate(GTK_MENU_SHELL(pTopLevel->mpMenuWidget));
+#endif
+ }
+
+ pTopLevel->GetMenu()->HandleMenuCommandEvent(pSalSubMenu->GetMenu(), aMenuAndId.second);
+}
+
+void GtkSalMenu::ActivateAllSubmenus(Menu* pMenuBar)
+{
+ // We can re-enter this method via the new event loop that gets created
+ // in GtkClipboardTransferable::getTransferDataFlavorsAsVector, so use the InActivateCallback
+ // flag to detect that and skip some startup work.
+ if (mbInActivateCallback)
+ return;
+
+ mbInActivateCallback = true;
+ pMenuBar->HandleMenuActivateEvent(GetMenu());
+ mbInActivateCallback = false;
+ for (GtkSalMenuItem* pSalItem : maItems)
+ {
+ if ( pSalItem->mpSubMenu != nullptr )
+ {
+ pSalItem->mpSubMenu->ActivateAllSubmenus(pMenuBar);
+ }
+ }
+ Update();
+ pMenuBar->HandleMenuDeActivateEvent(GetMenu());
+}
+
+void GtkSalMenu::ClearActionGroupAndMenuModel()
+{
+ SetMenuModel(nullptr);
+ mpActionGroup = nullptr;
+ for (GtkSalMenuItem* pSalItem : maItems)
+ {
+ if ( pSalItem->mpSubMenu != nullptr )
+ {
+ pSalItem->mpSubMenu->ClearActionGroupAndMenuModel();
+ }
+ }
+}
+
+void GtkSalMenu::Activate(const gchar* pCommand)
+{
+ MenuAndId aMenuAndId = decode_command(pCommand);
+ GtkSalMenu* pSalMenu = aMenuAndId.first;
+ Menu* pVclMenu = pSalMenu->GetMenu();
+ if (pVclMenu->isDisposed())
+ return;
+ GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
+ Menu* pVclSubMenu = pVclMenu->GetPopupMenu(aMenuAndId.second);
+ GtkSalMenu* pSubMenu = pSalMenu->GetItemAtPos(pVclMenu->GetItemPos(aMenuAndId.second))->mpSubMenu;
+
+ pSubMenu->mbInActivateCallback = true;
+ pTopLevel->GetMenu()->HandleMenuActivateEvent(pVclSubMenu);
+ pSubMenu->mbInActivateCallback = false;
+ pVclSubMenu->UpdateNativeMenu();
+}
+
+void GtkSalMenu::Deactivate(const gchar* pCommand)
+{
+ MenuAndId aMenuAndId = decode_command(pCommand);
+ GtkSalMenu* pSalMenu = aMenuAndId.first;
+ Menu* pVclMenu = pSalMenu->GetMenu();
+ if (pVclMenu->isDisposed())
+ return;
+ GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
+ Menu* pVclSubMenu = pVclMenu->GetPopupMenu(aMenuAndId.second);
+ pTopLevel->GetMenu()->HandleMenuDeActivateEvent(pVclSubMenu);
+}
+
+void GtkSalMenu::EnableUnity(bool bEnable)
+{
+ bUnityMode = bEnable;
+
+ MenuBar* pMenuBar(static_cast<MenuBar*>(mpVCLMenu.get()));
+ bool bDisplayable(pMenuBar->IsDisplayable());
+
+ if (bEnable)
+ {
+ DestroyMenuBarWidget();
+ UpdateFull();
+ if (!bDisplayable)
+ ShowMenuBar(false);
+ }
+ else
+ {
+ Update();
+ ShowMenuBar(bDisplayable);
+ }
+
+ pMenuBar->LayoutChanged();
+}
+
+void GtkSalMenu::ShowMenuBar( bool bVisible )
+{
+ // Unity tdf#106271: Can't hide global menu, so empty it instead when user wants to hide menubar,
+ if (bUnityMode)
+ {
+ if (bVisible)
+ Update();
+ else if (mpMenuModel && g_menu_model_get_n_items(G_MENU_MODEL(mpMenuModel)) > 0)
+ g_lo_menu_remove(G_LO_MENU(mpMenuModel), 0);
+ }
+ else if (bVisible)
+ CreateMenuBarWidget();
+ else
+ DestroyMenuBarWidget();
+}
+
+bool GtkSalMenu::IsItemVisible( unsigned nPos )
+{
+ SolarMutexGuard aGuard;
+ bool bVisible = false;
+
+ if ( nPos < maItems.size() )
+ bVisible = maItems[ nPos ]->mbVisible;
+
+ return bVisible;
+}
+
+void GtkSalMenu::CheckItem( unsigned, bool )
+{
+}
+
+void GtkSalMenu::EnableItem( unsigned nPos, bool bEnable )
+{
+ SolarMutexGuard aGuard;
+ if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar && ( nPos < maItems.size() ) )
+ {
+ gchar* pCommand = GetCommandForItem( GetItemAtPos( nPos ) );
+ NativeSetEnableItem( pCommand, bEnable );
+ g_free( pCommand );
+ }
+}
+
+void GtkSalMenu::ShowItem( unsigned nPos, bool bShow )
+{
+ SolarMutexGuard aGuard;
+ if ( nPos < maItems.size() )
+ {
+ maItems[ nPos ]->mbVisible = bShow;
+ if ( bUnityMode && !mbInActivateCallback && !mbNeedsUpdate && GetTopLevel()->mbMenuBar )
+ Update();
+ }
+}
+
+void GtkSalMenu::SetItemText( unsigned nPos, SalMenuItem* pSalMenuItem, const OUString& rText )
+{
+ SolarMutexGuard aGuard;
+ if ( !bUnityMode || mbInActivateCallback || mbNeedsUpdate || !GetTopLevel()->mbMenuBar || ( nPos >= maItems.size() ) )
+ return;
+
+ gchar* pCommand = GetCommandForItem( static_cast< GtkSalMenuItem* >( pSalMenuItem ) );
+
+ gint nSectionsCount = g_menu_model_get_n_items( mpMenuModel );
+ for ( gint nSection = 0; nSection < nSectionsCount; ++nSection )
+ {
+ gint nItemsCount = g_lo_menu_get_n_items_from_section( G_LO_MENU( mpMenuModel ), nSection );
+ for ( gint nItem = 0; nItem < nItemsCount; ++nItem )
+ {
+ gchar* pCommandFromModel = g_lo_menu_get_command_from_item_in_section( G_LO_MENU( mpMenuModel ), nSection, nItem );
+
+ if ( !g_strcmp0( pCommandFromModel, pCommand ) )
+ {
+ NativeSetItemText( nSection, nItem, rText );
+ g_free( pCommandFromModel );
+ g_free( pCommand );
+ return;
+ }
+
+ g_free( pCommandFromModel );
+ }
+ }
+
+ g_free( pCommand );
+}
+
+void GtkSalMenu::SetItemImage( unsigned, SalMenuItem*, const Image& )
+{
+}
+
+void GtkSalMenu::SetAccelerator( unsigned, SalMenuItem*, const vcl::KeyCode&, const OUString& )
+{
+}
+
+void GtkSalMenu::GetSystemMenuData( SystemMenuData* )
+{
+}
+
+int GtkSalMenu::GetMenuBarHeight() const
+{
+ return mpMenuBarWidget ? gtk_widget_get_allocated_height(mpMenuBarWidget) : 0;
+}
+
+/*
+ * GtkSalMenuItem
+ */
+
+GtkSalMenuItem::GtkSalMenuItem( const SalItemParams* pItemData ) :
+ mpParentMenu( nullptr ),
+ mpSubMenu( nullptr ),
+ mnType( pItemData->eType ),
+ mnId( pItemData->nId ),
+ mbVisible( true )
+{
+}
+
+GtkSalMenuItem::~GtkSalMenuItem()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtksys.cxx b/vcl/unx/gtk3/gtksys.cxx
new file mode 100644
index 000000000..37e32b28b
--- /dev/null
+++ b/vcl/unx/gtk3/gtksys.cxx
@@ -0,0 +1,339 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <gtk/gtk.h>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include <unx/gtk/gtksys.hxx>
+#include <unx/gtk/gtkbackend.hxx>
+#include <osl/module.h>
+
+GtkSalSystem *GtkSalSystem::GetSingleton()
+{
+ static GtkSalSystem *pSingleton = new GtkSalSystem();
+ return pSingleton;
+}
+
+SalSystem *GtkInstance::CreateSalSystem()
+{
+ return GtkSalSystem::GetSingleton();
+}
+
+GtkSalSystem::GtkSalSystem()
+{
+ mpDisplay = gdk_display_get_default();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ countScreenMonitors();
+#endif
+ // rhbz#1285356, native look will be gtk2, which crashes
+ // when gtk3 is already loaded. Until there is a solution
+ // java-side force look and feel to something that doesn't
+ // crash when we are using gtk3
+ setenv("STOC_FORCE_SYSTEM_LAF", "true", 1);
+}
+
+GtkSalSystem::~GtkSalSystem()
+{
+}
+
+namespace
+{
+
+struct GdkRectangleCoincidentLess
+{
+ // fdo#78799 - detect and elide overlaying monitors of different sizes
+ bool operator()(GdkRectangle const& rLeft, GdkRectangle const& rRight)
+ {
+ return
+ rLeft.x < rRight.x
+ || rLeft.y < rRight.y
+ ;
+ }
+};
+struct GdkRectangleCoincident
+{
+ // fdo#78799 - detect and elide overlaying monitors of different sizes
+ bool operator()(GdkRectangle const& rLeft, GdkRectangle const& rRight)
+ {
+ return
+ rLeft.x == rRight.x
+ && rLeft.y == rRight.y
+ ;
+ }
+};
+
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+/**
+ * GtkSalSystem::countScreenMonitors()
+ *
+ * This method builds the vector which allows us to map from VCL's
+ * idea of linear integer ScreenNumber to gtk+'s rather more
+ * complicated screen + monitor concept.
+ */
+void
+GtkSalSystem::countScreenMonitors()
+{
+ maScreenMonitors.clear();
+ for (gint i = 0; i < gdk_display_get_n_screens(mpDisplay); i++)
+ {
+ GdkScreen* const pScreen(gdk_display_get_screen(mpDisplay, i));
+ gint nMonitors(pScreen ? gdk_screen_get_n_monitors(pScreen) : 0);
+ if (nMonitors > 1)
+ {
+ std::vector<GdkRectangle> aGeometries;
+ aGeometries.reserve(nMonitors);
+ for (gint j(0); j != nMonitors; ++j)
+ {
+ GdkRectangle aGeometry;
+ gdk_screen_get_monitor_geometry(pScreen, j, &aGeometry);
+ aGeometries.push_back(aGeometry);
+ }
+ std::sort(aGeometries.begin(), aGeometries.end(),
+ GdkRectangleCoincidentLess());
+ const std::vector<GdkRectangle>::iterator aUniqueEnd(
+ std::unique(aGeometries.begin(), aGeometries.end(),
+ GdkRectangleCoincident()));
+ nMonitors = std::distance(aGeometries.begin(), aUniqueEnd);
+ }
+ maScreenMonitors.emplace_back(pScreen, nMonitors);
+ }
+}
+#endif
+
+SalX11Screen
+GtkSalSystem::getXScreenFromDisplayScreen(unsigned int nScreen)
+{
+ if (!DLSYM_GDK_IS_X11_DISPLAY(mpDisplay))
+ return SalX11Screen (0);
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GdkX11Screen *pScreen = gdk_x11_display_get_screen(mpDisplay);
+ (void)nScreen;
+#else
+ gint nMonitor;
+ GdkScreen *pScreen = getScreenMonitorFromIdx (nScreen, nMonitor);
+ if (!pScreen)
+ return SalX11Screen (0);
+#endif
+ return SalX11Screen(gdk_x11_screen_get_screen_number(pScreen));
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+GdkScreen *
+GtkSalSystem::getScreenMonitorFromIdx (int nIdx, gint &nMonitor)
+{
+ GdkScreen *pScreen = nullptr;
+ for (auto const& screenMonitor : maScreenMonitors)
+ {
+ pScreen = screenMonitor.first;
+ if (!pScreen)
+ break;
+ if (nIdx >= screenMonitor.second)
+ nIdx -= screenMonitor.second;
+ else
+ break;
+ }
+ nMonitor = nIdx;
+
+ // handle invalid monitor indexes as non-existent screens
+ if (nMonitor < 0 || (pScreen && nMonitor >= gdk_screen_get_n_monitors (pScreen)))
+ pScreen = nullptr;
+
+ return pScreen;
+}
+
+int
+GtkSalSystem::getScreenIdxFromPtr (GdkScreen *pScreen)
+{
+ int nIdx = 0;
+ for (auto const& screenMonitor : maScreenMonitors)
+ {
+ if (screenMonitor.first == pScreen)
+ return nIdx;
+ nIdx += screenMonitor.second;
+ }
+ g_warning ("failed to find screen %p", pScreen);
+ return 0;
+}
+
+int GtkSalSystem::getScreenMonitorIdx (GdkScreen *pScreen,
+ int nX, int nY)
+{
+ // TODO: this will fail horribly for exotic combinations like two
+ // monitors in mirror mode and one extra. Hopefully such
+ // abominations are not used (or, even better, not possible) in
+ // practice .-)
+ return getScreenIdxFromPtr (pScreen) +
+ gdk_screen_get_monitor_at_point (pScreen, nX, nY);
+}
+#endif
+
+unsigned int GtkSalSystem::GetDisplayScreenCount()
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ return g_list_model_get_n_items(gdk_display_get_monitors(mpDisplay));
+#else
+ gint nMonitor;
+ (void)getScreenMonitorFromIdx (G_MAXINT, nMonitor);
+ return G_MAXINT - nMonitor;
+#endif
+}
+
+bool GtkSalSystem::IsUnifiedDisplay()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ return gdk_display_get_n_screens (mpDisplay) == 1;
+#else
+ return true;
+#endif
+}
+
+unsigned int GtkSalSystem::GetDisplayBuiltInScreen()
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+#if defined(GDK_WINDOWING_X11)
+ if (DLSYM_GDK_IS_X11_DISPLAY(mpDisplay))
+ {
+ GdkMonitor* pPrimary = gdk_x11_display_get_primary_monitor(mpDisplay);
+ GListModel* pList = gdk_display_get_monitors(mpDisplay);
+ int nIndex = 0;
+ while (gpointer pElem = g_list_model_get_item(pList, nIndex))
+ {
+ if (pElem == pPrimary)
+ return nIndex;
+ ++nIndex;
+ }
+ }
+#endif
+ // nothing for wayland ?, hope for the best that its at index 0
+ return 0;
+#else // !GTK_CHECK_VERSION(4, 0, 0)
+ GdkScreen *pDefault = gdk_display_get_default_screen (mpDisplay);
+ int idx = getScreenIdxFromPtr (pDefault);
+ return idx + gdk_screen_get_primary_monitor(pDefault);
+#endif
+}
+
+tools::Rectangle GtkSalSystem::GetDisplayScreenPosSizePixel(unsigned int nScreen)
+{
+ GdkRectangle aRect;
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GListModel* pList = gdk_display_get_monitors(mpDisplay);
+ GdkMonitor* pMonitor = static_cast<GdkMonitor*>(g_list_model_get_item(pList, nScreen));
+ if (!pMonitor)
+ return tools::Rectangle();
+ gdk_monitor_get_geometry(pMonitor, &aRect);
+#else
+ gint nMonitor;
+ GdkScreen *pScreen;
+ pScreen = getScreenMonitorFromIdx (nScreen, nMonitor);
+ if (!pScreen)
+ return tools::Rectangle();
+ gdk_screen_get_monitor_geometry (pScreen, nMonitor, &aRect);
+#endif
+ return tools::Rectangle (Point(aRect.x, aRect.y), Size(aRect.width, aRect.height));
+}
+
+// convert ~ to indicate mnemonic to '_'
+static OString MapToGtkAccelerator(const OUString &rStr)
+{
+ return OUStringToOString(rStr.replaceFirst("~", "_"), RTL_TEXTENCODING_UTF8);
+}
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+
+namespace
+{
+ struct DialogLoop
+ {
+ GMainLoop* pLoop = nullptr;
+ gint nResponseId = GTK_RESPONSE_NONE;
+ gulong nSignalResponseId = 0;
+ gulong nSignalCloseRequestId= 0;
+
+ static gboolean DialogClose(GtkWindow* pDialog, gpointer /*data*/)
+ {
+ gtk_dialog_response(GTK_DIALOG(pDialog), GTK_RESPONSE_CANCEL);
+ return true;
+ }
+
+ static void DialogResponse(GtkDialog* pDialog, gint nResponseId, gpointer data)
+ {
+ DialogLoop* pDialogLoop = static_cast<DialogLoop*>(data);
+ g_signal_handler_disconnect(pDialog, pDialogLoop->nSignalResponseId);
+ g_signal_handler_disconnect(pDialog, pDialogLoop->nSignalCloseRequestId);
+ pDialogLoop->nResponseId = nResponseId;
+ g_main_loop_quit(pDialogLoop->pLoop);
+ }
+
+ int run(GtkDialog *pDialog)
+ {
+ nSignalResponseId = g_signal_connect(pDialog, "response", G_CALLBACK(DialogResponse), this);
+ nSignalCloseRequestId = g_signal_connect(pDialog, "close-request", G_CALLBACK(DialogClose), this);
+ gtk_window_present(GTK_WINDOW(pDialog));
+ pLoop = g_main_loop_new(nullptr, false);
+ main_loop_run(pLoop);
+ g_main_loop_unref(pLoop);
+ return nResponseId;
+ }
+
+ };
+}
+
+gint gtk_dialog_run(GtkDialog* pDialog)
+{
+ DialogLoop aDialogLoop;
+ return aDialogLoop.run(pDialog);
+}
+
+#endif
+
+int GtkSalSystem::ShowNativeDialog (const OUString& rTitle, const OUString& rMessage,
+ const std::vector< OUString >& rButtonNames)
+{
+ OString aTitle (OUStringToOString (rTitle, RTL_TEXTENCODING_UTF8));
+ OString aMessage (OUStringToOString (rMessage, RTL_TEXTENCODING_UTF8));
+
+ GtkDialog *pDialog = GTK_DIALOG (
+ g_object_new (GTK_TYPE_MESSAGE_DIALOG,
+ "title", aTitle.getStr(),
+ "message-type", int(GTK_MESSAGE_WARNING),
+ "text", aMessage.getStr(),
+ nullptr));
+ int nButton = 0;
+ for (auto const& buttonName : rButtonNames)
+ gtk_dialog_add_button (pDialog, MapToGtkAccelerator(buttonName).getStr(), nButton++);
+ gtk_dialog_set_default_response (pDialog, 0/*nDefaultButton*/);
+
+ nButton = gtk_dialog_run (pDialog);
+ if (nButton < 0)
+ nButton = -1;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ gtk_widget_destroy(GTK_WIDGET(pDialog));
+#else
+ gtk_window_destroy(GTK_WINDOW(pDialog));
+#endif
+
+ return nButton;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/hudawareness.cxx b/vcl/unx/gtk3/hudawareness.cxx
new file mode 100644
index 000000000..ebcbaf747
--- /dev/null
+++ b/vcl/unx/gtk3/hudawareness.cxx
@@ -0,0 +1,110 @@
+/* -*- 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 <string.h>
+
+#include <unx/gtk/hudawareness.h>
+
+namespace {
+
+struct HudAwarenessHandle
+{
+ gpointer connection;
+ HudAwarenessCallback callback;
+ gpointer user_data;
+ GDestroyNotify notify;
+};
+
+}
+
+static void
+hud_awareness_method_call (GDBusConnection * /* connection */,
+ const gchar * /* sender */,
+ const gchar * /* object_path */,
+ const gchar * /* interface_name */,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ HudAwarenessHandle *handle = static_cast<HudAwarenessHandle*>(user_data);
+
+ if (g_str_equal (method_name, "HudActiveChanged"))
+ {
+ gboolean active;
+
+ g_variant_get (parameters, "(b)", &active);
+
+ (* handle->callback) (active, handle->user_data);
+ }
+
+ g_dbus_method_invocation_return_value (invocation, nullptr);
+}
+
+guint
+hud_awareness_register (GDBusConnection *connection,
+ const gchar *object_path,
+ HudAwarenessCallback callback,
+ gpointer user_data,
+ GDestroyNotify notify,
+ GError **error)
+{
+ static GDBusInterfaceInfo *iface;
+ static GDBusNodeInfo *info;
+ GDBusInterfaceVTable vtable;
+ HudAwarenessHandle *handle;
+ guint object_id;
+
+ memset (static_cast<void *>(&vtable), 0, sizeof (vtable));
+ vtable.method_call = hud_awareness_method_call;
+
+ if G_UNLIKELY (iface == nullptr)
+ {
+ GError *local_error = nullptr;
+
+ info = g_dbus_node_info_new_for_xml ("<node>"
+ "<interface name='com.canonical.hud.Awareness'>"
+ "<method name='CheckAwareness'/>"
+ "<method name='HudActiveChanged'>"
+ "<arg type='b'/>"
+ "</method>"
+ "</interface>"
+ "</node>",
+ &local_error);
+ g_assert_no_error (local_error);
+ iface = g_dbus_node_info_lookup_interface (info, "com.canonical.hud.Awareness");
+ g_assert (iface != nullptr);
+ }
+
+ handle = static_cast<HudAwarenessHandle*>(g_malloc (sizeof (HudAwarenessHandle)));
+
+ object_id = g_dbus_connection_register_object (connection, object_path, iface, &vtable, handle, &g_free, error);
+
+ if (object_id == 0)
+ {
+ g_free (handle);
+ return 0;
+ }
+
+ handle->connection = g_object_ref(connection);
+ handle->callback = callback;
+ handle->user_data = user_data;
+ handle->notify = notify;
+
+ return object_id;
+}
+
+void
+hud_awareness_unregister (GDBusConnection *connection,
+ guint subscription_id)
+{
+ g_dbus_connection_unregister_object (connection, subscription_id);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/salnativewidgets-gtk.cxx b/vcl/unx/gtk3/salnativewidgets-gtk.cxx
new file mode 100644
index 000000000..64f6357ab
--- /dev/null
+++ b/vcl/unx/gtk3/salnativewidgets-gtk.cxx
@@ -0,0 +1,3060 @@
+/* -*- 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 <sal/log.hxx>
+#include <osl/module.h>
+
+#include <config_cairo_canvas.h>
+
+#include <unx/gtk/gtkframe.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include <unx/gtk/gtkgdi.hxx>
+#include <unx/gtk/gtkbackend.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/settings.hxx>
+#include <unx/fontmanager.hxx>
+
+#include "gtkcairo.hxx"
+#include <optional>
+
+GtkStyleContext* GtkSalGraphics::mpWindowStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpLinkButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpEntryStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpTextViewStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpVScrollbarStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpVScrollbarContentsStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpVScrollbarTroughStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpVScrollbarSliderStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpVScrollbarButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpHScrollbarStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpHScrollbarContentsStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpHScrollbarTroughStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpHScrollbarSliderStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpHScrollbarButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpToolbarStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpToolButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpToolbarSeparatorStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpCheckButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpCheckButtonCheckStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpRadioButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpRadioButtonRadioStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpSpinStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpSpinUpStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpSpinDownStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpComboboxStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpComboboxBoxStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpComboboxEntryStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpComboboxButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpComboboxButtonBoxStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpComboboxButtonArrowStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpListboxStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpListboxBoxStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpListboxButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpListboxButtonBoxStyle= nullptr;
+GtkStyleContext* GtkSalGraphics::mpListboxButtonArrowStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpFrameInStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpFrameOutStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpFixedHoriLineStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpFixedVertLineStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpTreeHeaderButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpProgressBarStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpProgressBarTroughStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpProgressBarProgressStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpNotebookStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpNotebookStackStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpNotebookHeaderStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpNotebookHeaderTabsStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpNotebookHeaderTabsTabStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpNotebookHeaderTabsTabLabelStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpNotebookHeaderTabsTabActiveLabelStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpNotebookHeaderTabsTabHoverLabelStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpMenuBarStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpMenuBarItemStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpMenuWindowStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpMenuStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpMenuItemStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpMenuItemArrowStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpMenuItemLabelStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpCheckMenuItemStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpCheckMenuItemCheckStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpRadioMenuItemStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpRadioMenuItemRadioStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpSeparatorMenuItemStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpSeparatorMenuItemSeparatorStyle = nullptr;
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+static void style_context_get_margin(GtkStyleContext *pContext, GtkBorder *pMargin)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_style_context_get_margin(pContext, pMargin);
+#else
+ gtk_style_context_get_margin(pContext, gtk_style_context_get_state(pContext), pMargin);
+#endif
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+static void style_context_get_border(GtkStyleContext* pContext, GtkBorder* pBorder)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_style_context_get_border(pContext, pBorder);
+#else
+ gtk_style_context_get_border(pContext, gtk_style_context_get_state(pContext), pBorder);
+#endif
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+static void style_context_get_padding(GtkStyleContext* pContext, GtkBorder* pPadding)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_style_context_get_padding(pContext, pPadding);
+#else
+ gtk_style_context_get_padding(pContext, gtk_style_context_get_state(pContext), pPadding);
+#endif
+}
+#endif
+
+bool GtkSalGraphics::style_loaded = false;
+/************************************************************************
+ * State conversion
+ ************************************************************************/
+#if !GTK_CHECK_VERSION(4, 0, 0)
+static GtkStateFlags NWConvertVCLStateToGTKState(ControlState nVCLState)
+{
+ GtkStateFlags nGTKState = GTK_STATE_FLAG_NORMAL;
+
+ if (!( nVCLState & ControlState::ENABLED ))
+ {
+ nGTKState = GTK_STATE_FLAG_INSENSITIVE;
+ }
+
+ if ( nVCLState & ControlState::PRESSED )
+ {
+ nGTKState = static_cast<GtkStateFlags>(nGTKState | GTK_STATE_FLAG_ACTIVE);
+ }
+
+ if ( nVCLState & ControlState::ROLLOVER )
+ {
+ nGTKState = static_cast<GtkStateFlags>(nGTKState | GTK_STATE_FLAG_PRELIGHT);
+ }
+
+ if ( nVCLState & ControlState::SELECTED )
+ nGTKState = static_cast<GtkStateFlags>(nGTKState | GTK_STATE_FLAG_SELECTED);
+
+ if ( nVCLState & ControlState::FOCUSED )
+ nGTKState = static_cast<GtkStateFlags>(nGTKState | GTK_STATE_FLAG_FOCUSED);
+
+ if (AllSettings::GetLayoutRTL())
+ {
+ nGTKState = static_cast<GtkStateFlags>(nGTKState | GTK_STATE_FLAG_DIR_RTL);
+ }
+ else
+ {
+ nGTKState = static_cast<GtkStateFlags>(nGTKState | GTK_STATE_FLAG_DIR_LTR);
+ }
+
+ return nGTKState;
+}
+
+namespace {
+
+enum class RenderType {
+ BackgroundAndFrame = 1,
+ Check,
+ Background,
+ MenuSeparator,
+ ToolbarSeparator,
+ Separator,
+ Arrow,
+ Radio,
+ Scrollbar,
+ Spinbutton,
+ Combobox,
+ Expander,
+ Icon,
+ Progress,
+ TabItem,
+ Focus
+};
+
+}
+
+static void NWCalcArrowRect( const tools::Rectangle& rButton, tools::Rectangle& rArrow )
+{
+ // Size the arrow appropriately
+ Size aSize( rButton.GetWidth()/2, rButton.GetHeight()/2 );
+ rArrow.SetSize( aSize );
+
+ rArrow.SetPos( Point(
+ rButton.Left() + ( rButton.GetWidth() - rArrow.GetWidth() ) / 2,
+ rButton.Top() + ( rButton.GetHeight() - rArrow.GetHeight() ) / 2
+ ) );
+}
+
+tools::Rectangle GtkSalGraphics::NWGetSpinButtonRect( ControlPart nPart, tools::Rectangle aAreaRect)
+{
+ gint w, h;
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h);
+ gint icon_size = std::max(w, h);
+
+ GtkBorder padding, border;
+ style_context_get_padding(mpSpinUpStyle, &padding);
+ style_context_get_border(mpSpinUpStyle, &border);
+
+ gint buttonWidth = icon_size + padding.left + padding.right +
+ border.left + border.right;
+
+ tools::Rectangle buttonRect(Point(0, aAreaRect.Top()), Size(buttonWidth, 0));
+ buttonRect.setHeight(aAreaRect.GetHeight());
+ tools::Rectangle partRect(buttonRect);
+ if ( nPart == ControlPart::ButtonUp )
+ {
+ if (AllSettings::GetLayoutRTL())
+ partRect.SetPosX(aAreaRect.Left());
+ else
+ partRect.SetPosX(aAreaRect.Left() + (aAreaRect.GetWidth() - buttonRect.GetWidth()));
+ }
+ else if( nPart == ControlPart::ButtonDown )
+ {
+ if (AllSettings::GetLayoutRTL())
+ partRect.SetPosX(aAreaRect.Left() + buttonRect.GetWidth());
+ else
+ partRect.SetPosX(aAreaRect.Left() + (aAreaRect.GetWidth() - 2 * buttonRect.GetWidth()));
+ }
+ else
+ {
+ if (AllSettings::GetLayoutRTL())
+ {
+ partRect.SetRight( aAreaRect.Left() + aAreaRect.GetWidth() );
+ partRect.SetLeft( aAreaRect.Left() + (2 * buttonRect.GetWidth()) - 1 );
+ }
+ else
+ {
+ partRect.SetRight( (aAreaRect.Left() + (aAreaRect.GetWidth() - 2 * buttonRect.GetWidth())) - 1 );
+ partRect.SetLeft( aAreaRect.Left() );
+ }
+ partRect.SetTop( aAreaRect.Top() );
+ partRect.SetBottom( aAreaRect.Bottom() );
+ }
+
+ return partRect;
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+namespace
+{
+ void QuerySize(GtkStyleContext *pContext, Size &rSize)
+ {
+ GtkBorder margin, border, padding;
+
+ style_context_get_margin(pContext, &margin);
+ style_context_get_border(pContext, &border);
+ style_context_get_padding(pContext, &padding);
+
+ int nMinWidth(0), nMinHeight(0);
+ GtkStateFlags stateflags = gtk_style_context_get_state (pContext);
+ gtk_style_context_get(pContext, stateflags,
+ "min-width", &nMinWidth, "min-height", &nMinHeight, nullptr);
+ nMinWidth += margin.left + margin.right + border.left + border.right + padding.left + padding.right;
+ nMinHeight += margin.top + margin.bottom + border.top + border.bottom + padding.top + padding.bottom;
+
+ rSize = Size(std::max<tools::Long>(rSize.Width(), nMinWidth), std::max<tools::Long>(rSize.Height(), nMinHeight));
+ }
+}
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+tools::Rectangle GtkSalGraphics::NWGetScrollButtonRect( ControlPart nPart, tools::Rectangle aAreaRect )
+{
+ tools::Rectangle buttonRect;
+
+ gboolean has_forward;
+ gboolean has_forward2;
+ gboolean has_backward;
+ gboolean has_backward2;
+
+ GtkStyleContext* pScrollbarStyle = nullptr;
+ if ((nPart == ControlPart::ButtonLeft) || (nPart == ControlPart::ButtonRight))
+ pScrollbarStyle = mpHScrollbarStyle;
+ else // (nPart == ControlPart::ButtonUp) || (nPart == ControlPart::ButtonDown)
+ pScrollbarStyle = mpVScrollbarStyle;
+
+ gtk_style_context_get_style( pScrollbarStyle,
+ "has-forward-stepper", &has_forward,
+ "has-secondary-forward-stepper", &has_forward2,
+ "has-backward-stepper", &has_backward,
+ "has-secondary-backward-stepper", &has_backward2, nullptr );
+
+ gint nFirst = 0;
+ gint nSecond = 0;
+
+ if ( has_forward ) nSecond += 1;
+ if ( has_forward2 ) nFirst += 1;
+ if ( has_backward ) nFirst += 1;
+ if ( has_backward2 ) nSecond += 1;
+
+ Size aSize;
+ if (nPart == ControlPart::ButtonLeft || nPart == ControlPart::ButtonRight)
+ {
+ QuerySize(mpHScrollbarStyle, aSize);
+ QuerySize(mpHScrollbarContentsStyle, aSize);
+ QuerySize(mpHScrollbarButtonStyle, aSize);
+ }
+ else
+ {
+ QuerySize(mpVScrollbarStyle, aSize);
+ QuerySize(mpVScrollbarContentsStyle, aSize);
+ QuerySize(mpVScrollbarButtonStyle, aSize);
+ }
+
+ if (nPart == ControlPart::ButtonUp)
+ {
+ aSize.setHeight( aSize.Height() * nFirst );
+ buttonRect.SetLeft(aAreaRect.Left());
+ buttonRect.SetTop(aAreaRect.Top());
+ }
+ else if (nPart == ControlPart::ButtonLeft)
+ {
+ aSize.setWidth( aSize.Width() * nFirst );
+ buttonRect.SetLeft(aAreaRect.Left());
+ buttonRect.SetTop(aAreaRect.Top());
+ }
+ else if (nPart == ControlPart::ButtonDown)
+ {
+ aSize.setHeight( aSize.Height() * nSecond );
+ buttonRect.SetLeft(aAreaRect.Left());
+ buttonRect.SetTop(aAreaRect.Top() + aAreaRect.GetHeight() - aSize.Height());
+ }
+ else if (nPart == ControlPart::ButtonRight)
+ {
+ aSize.setWidth( aSize.Width() * nSecond );
+ buttonRect.SetLeft(aAreaRect.Left() + aAreaRect.GetWidth() - aSize.Width());
+ buttonRect.SetTop(aAreaRect.Top());
+ }
+
+ buttonRect.SetSize(aSize);
+
+ return buttonRect;
+}
+#endif
+
+static GtkWidget* gCacheWindow;
+static GtkWidget* gDumbContainer;
+#if GTK_CHECK_VERSION(4, 0, 0)
+static GtkWidget* gVScrollbar;
+static GtkWidget* gHScrollbar;
+static GtkWidget* gTextView;
+#else
+static GtkWidget* gComboBox;
+static GtkWidget* gListBox;
+static GtkWidget* gSpinBox;
+static GtkWidget* gTreeViewWidget;
+#endif
+static GtkWidget* gEntryBox;
+
+namespace
+{
+ void style_context_set_state(GtkStyleContext* context, GtkStateFlags flags)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ do
+ {
+ gtk_style_context_set_state(context, flags);
+ }
+ while ((context = gtk_style_context_get_parent(context)));
+#else
+ gtk_style_context_set_state(context, flags);
+#endif
+ }
+
+ class StyleContextSave
+ {
+ private:
+ std::vector<std::pair<GtkStyleContext*, GtkStateFlags>> m_aStates;
+ public:
+ void save(GtkStyleContext* context)
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ do
+ {
+ m_aStates.emplace_back(context, gtk_style_context_get_state(context));
+ }
+ while ((context = gtk_style_context_get_parent(context)));
+#else
+ m_aStates.emplace_back(context, gtk_style_context_get_state(context));
+#endif
+ }
+ void restore()
+ {
+ for (auto a = m_aStates.rbegin(); a != m_aStates.rend(); ++a)
+ {
+ gtk_style_context_set_state(a->first, a->second);
+ }
+ m_aStates.clear();
+ }
+ };
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ tools::Rectangle render_common(GtkStyleContext *pContext, cairo_t *cr, const tools::Rectangle &rIn, GtkStateFlags flags)
+ {
+ if (!pContext)
+ return rIn;
+
+ gtk_style_context_set_state(pContext, flags);
+
+ tools::Rectangle aRect(rIn);
+ GtkBorder margin;
+ style_context_get_margin(pContext, &margin);
+
+ aRect.AdjustLeft(margin.left );
+ aRect.AdjustTop(margin.top );
+ aRect.AdjustRight( -(margin.right) );
+ aRect.AdjustBottom( -(margin.bottom) );
+
+ gtk_render_background(pContext, cr, aRect.Left(), aRect.Top(),
+ aRect.GetWidth(), aRect.GetHeight());
+ gtk_render_frame(pContext, cr, aRect.Left(), aRect.Top(),
+ aRect.GetWidth(), aRect.GetHeight());
+
+ GtkBorder border, padding;
+ style_context_get_border(pContext, &border);
+ style_context_get_padding(pContext, &padding);
+
+ aRect.AdjustLeft(border.left + padding.left );
+ aRect.AdjustTop(border.top + padding.top );
+ aRect.AdjustRight( -(border.right + padding.right) );
+ aRect.AdjustBottom( -(border.bottom + padding.bottom) );
+
+ return aRect;
+ }
+#endif
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+void GtkSalGraphics::PaintScrollbar(GtkStyleContext *context,
+ cairo_t *cr,
+ const tools::Rectangle& rControlRectangle,
+ ControlPart nPart,
+ const ImplControlValue& rValue )
+{
+ assert(rValue.getType() == ControlType::Scrollbar);
+ const ScrollbarValue& rScrollbarVal = static_cast<const ScrollbarValue&>(rValue);
+ tools::Rectangle scrollbarRect;
+ GtkStateFlags stateFlags;
+ GtkOrientation scrollbarOrientation;
+ tools::Rectangle thumbRect = rScrollbarVal.maThumbRect;
+ tools::Rectangle button11BoundRect = rScrollbarVal.maButton1Rect; // backward
+ tools::Rectangle button22BoundRect = rScrollbarVal.maButton2Rect; // forward
+ tools::Rectangle button12BoundRect = rScrollbarVal.maButton1Rect; // secondary forward
+ tools::Rectangle button21BoundRect = rScrollbarVal.maButton2Rect; // secondary backward
+ gdouble arrow1Angle; // backward
+ gdouble arrow2Angle; // forward
+ tools::Rectangle arrowRect;
+ gint slider_width = 0;
+ gint stepper_size = 0;
+
+ // make controlvalue rectangles relative to area
+ thumbRect.Move( -rControlRectangle.Left(), -rControlRectangle.Top() );
+ button11BoundRect.Move( -rControlRectangle.Left(), -rControlRectangle.Top() );
+ button22BoundRect.Move( -rControlRectangle.Left(), -rControlRectangle.Top() );
+ button12BoundRect.Move( -rControlRectangle.Left(), -rControlRectangle.Top() );
+ button21BoundRect.Move( -rControlRectangle.Left(), -rControlRectangle.Top() );
+
+ // Find the overall bounding rect of the control
+ scrollbarRect = rControlRectangle;
+ if (scrollbarRect.IsEmpty())
+ return;
+
+ gint slider_side;
+ Size aSize;
+ if (nPart == ControlPart::DrawBackgroundHorz)
+ {
+ QuerySize(mpHScrollbarStyle, aSize);
+ QuerySize(mpHScrollbarContentsStyle, aSize);
+ QuerySize(mpHScrollbarTroughStyle, aSize);
+ QuerySize(mpHScrollbarSliderStyle, aSize);
+ slider_side = aSize.Height();
+ gtk_style_context_get(mpHScrollbarButtonStyle,
+ gtk_style_context_get_state(mpHScrollbarButtonStyle),
+ "min-height", &slider_width,
+ "min-width", &stepper_size, nullptr);
+ }
+ else
+ {
+ QuerySize(mpVScrollbarStyle, aSize);
+ QuerySize(mpVScrollbarContentsStyle, aSize);
+ QuerySize(mpVScrollbarTroughStyle, aSize);
+ QuerySize(mpVScrollbarSliderStyle, aSize);
+ slider_side = aSize.Width();
+ gtk_style_context_get(mpVScrollbarButtonStyle,
+ gtk_style_context_get_state(mpVScrollbarButtonStyle),
+ "min-width", &slider_width,
+ "min-height", &stepper_size, nullptr);
+ }
+
+ gboolean has_forward;
+ gboolean has_forward2;
+ gboolean has_backward;
+ gboolean has_backward2;
+
+ gtk_style_context_get_style( context,
+ "has-forward-stepper", &has_forward,
+ "has-secondary-forward-stepper", &has_forward2,
+ "has-backward-stepper", &has_backward,
+ "has-secondary-backward-stepper", &has_backward2, nullptr );
+
+ if ( nPart == ControlPart::DrawBackgroundHorz )
+ {
+ // Center vertically in the track
+ scrollbarRect.Move( 0, (scrollbarRect.GetHeight() - slider_side) / 2 );
+ scrollbarRect.SetSize( Size( scrollbarRect.GetWidth(), slider_side ) );
+ thumbRect.Move( 0, (scrollbarRect.GetHeight() - slider_side) / 2 );
+ thumbRect.SetSize( Size( thumbRect.GetWidth(), slider_side ) );
+
+ scrollbarOrientation = GTK_ORIENTATION_HORIZONTAL;
+ arrow1Angle = G_PI * 3 / 2;
+ arrow2Angle = G_PI / 2;
+
+ if ( has_backward )
+ {
+ button12BoundRect.Move( stepper_size,
+ (scrollbarRect.GetHeight() - slider_width) / 2 );
+ }
+
+ button11BoundRect.Move( 0, (scrollbarRect.GetHeight() - slider_width) / 2 );
+ button11BoundRect.SetSize( Size( stepper_size, slider_width ) );
+ button12BoundRect.SetSize( Size( stepper_size, slider_width ) );
+
+ if ( has_backward2 )
+ {
+ button22BoundRect.Move( stepper_size, (scrollbarRect.GetHeight() - slider_width) / 2 );
+ button21BoundRect.Move( 0, (scrollbarRect.GetHeight() - slider_width) / 2 );
+ }
+ else
+ {
+ button22BoundRect.Move( 0, (scrollbarRect.GetHeight() - slider_width) / 2 );
+ }
+
+ button21BoundRect.SetSize( Size( stepper_size, slider_width ) );
+ button22BoundRect.SetSize( Size( stepper_size, slider_width ) );
+ }
+ else
+ {
+ // Center horizontally in the track
+ scrollbarRect.Move( (scrollbarRect.GetWidth() - slider_side) / 2, 0 );
+ scrollbarRect.SetSize( Size( slider_side, scrollbarRect.GetHeight() ) );
+ thumbRect.Move( (scrollbarRect.GetWidth() - slider_side) / 2, 0 );
+ thumbRect.SetSize( Size( slider_side, thumbRect.GetHeight() ) );
+
+ scrollbarOrientation = GTK_ORIENTATION_VERTICAL;
+ arrow1Angle = 0;
+ arrow2Angle = G_PI;
+
+ if ( has_backward )
+ {
+ button12BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2,
+ stepper_size );
+ }
+ button11BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, 0 );
+ button11BoundRect.SetSize( Size( slider_width, stepper_size ) );
+ button12BoundRect.SetSize( Size( slider_width, stepper_size ) );
+
+ if ( has_backward2 )
+ {
+ button22BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, stepper_size );
+ button21BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, 0 );
+ }
+ else
+ {
+ button22BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, 0 );
+ }
+
+ button21BoundRect.SetSize( Size( slider_width, stepper_size ) );
+ button22BoundRect.SetSize( Size( slider_width, stepper_size ) );
+ }
+
+ bool has_slider = !thumbRect.IsEmpty();
+
+ // ----------------- CONTENTS
+ GtkStyleContext* pScrollbarContentsStyle = scrollbarOrientation == GTK_ORIENTATION_VERTICAL ?
+ mpVScrollbarContentsStyle : mpHScrollbarContentsStyle;
+
+ gtk_render_background(gtk_widget_get_style_context(gCacheWindow), cr, 0, 0,
+ scrollbarRect.GetWidth(), scrollbarRect.GetHeight() );
+
+ gtk_render_background(context, cr, 0, 0,
+ scrollbarRect.GetWidth(), scrollbarRect.GetHeight() );
+ gtk_render_frame(context, cr, 0, 0,
+ scrollbarRect.GetWidth(), scrollbarRect.GetHeight() );
+
+ gtk_render_background(pScrollbarContentsStyle, cr, 0, 0,
+ scrollbarRect.GetWidth(), scrollbarRect.GetHeight() );
+ gtk_render_frame(pScrollbarContentsStyle, cr, 0, 0,
+ scrollbarRect.GetWidth(), scrollbarRect.GetHeight() );
+
+ bool backwardButtonInsensitive =
+ rScrollbarVal.mnCur == rScrollbarVal.mnMin;
+ bool forwardButtonInsensitive = rScrollbarVal.mnMax == 0 ||
+ rScrollbarVal.mnCur + rScrollbarVal.mnVisibleSize >= rScrollbarVal.mnMax;
+
+ // ----------------- BUTTON 1
+ if ( has_backward )
+ {
+ stateFlags = NWConvertVCLStateToGTKState(rScrollbarVal.mnButton1State);
+ if ( backwardButtonInsensitive )
+ stateFlags = GTK_STATE_FLAG_INSENSITIVE;
+
+ GtkStyleContext* pScrollbarButtonStyle = scrollbarOrientation == GTK_ORIENTATION_VERTICAL ?
+ mpVScrollbarButtonStyle : mpHScrollbarButtonStyle;
+
+ gtk_style_context_set_state(pScrollbarButtonStyle, stateFlags);
+
+ gtk_render_background(pScrollbarButtonStyle, cr,
+ button11BoundRect.Left(), button11BoundRect.Top(),
+ button11BoundRect.GetWidth(), button11BoundRect.GetHeight() );
+ gtk_render_frame(pScrollbarButtonStyle, cr,
+ button11BoundRect.Left(), button11BoundRect.Top(),
+ button11BoundRect.GetWidth(), button11BoundRect.GetHeight() );
+
+ // ----------------- ARROW 1
+ NWCalcArrowRect( button11BoundRect, arrowRect );
+ gtk_render_arrow(pScrollbarButtonStyle, cr,
+ arrow1Angle,
+ arrowRect.Left(), arrowRect.Top(),
+ MIN(arrowRect.GetWidth(), arrowRect.GetHeight()) );
+ }
+ if ( has_forward2 )
+ {
+ stateFlags = NWConvertVCLStateToGTKState(rScrollbarVal.mnButton2State);
+ if ( forwardButtonInsensitive )
+ stateFlags = GTK_STATE_FLAG_INSENSITIVE;
+
+ GtkStyleContext* pScrollbarButtonStyle = scrollbarOrientation == GTK_ORIENTATION_VERTICAL ?
+ mpVScrollbarButtonStyle : mpHScrollbarButtonStyle;
+
+ gtk_style_context_set_state(pScrollbarButtonStyle, stateFlags);
+
+ gtk_render_background(pScrollbarButtonStyle, cr,
+ button12BoundRect.Left(), button12BoundRect.Top(),
+ button12BoundRect.GetWidth(), button12BoundRect.GetHeight() );
+ gtk_render_frame(pScrollbarButtonStyle, cr,
+ button12BoundRect.Left(), button12BoundRect.Top(),
+ button12BoundRect.GetWidth(), button12BoundRect.GetHeight() );
+
+ // ----------------- ARROW 1
+ NWCalcArrowRect( button12BoundRect, arrowRect );
+ gtk_render_arrow(pScrollbarButtonStyle, cr,
+ arrow2Angle,
+ arrowRect.Left(), arrowRect.Top(),
+ MIN(arrowRect.GetWidth(), arrowRect.GetHeight()) );
+ }
+ // ----------------- BUTTON 2
+
+ if ( has_forward )
+ {
+ stateFlags = NWConvertVCLStateToGTKState(rScrollbarVal.mnButton2State);
+ if ( forwardButtonInsensitive )
+ stateFlags = GTK_STATE_FLAG_INSENSITIVE;
+
+ GtkStyleContext* pScrollbarButtonStyle = scrollbarOrientation == GTK_ORIENTATION_VERTICAL ?
+ mpVScrollbarButtonStyle : mpHScrollbarButtonStyle;
+
+ gtk_style_context_set_state(pScrollbarButtonStyle, stateFlags);
+
+ gtk_render_background(pScrollbarButtonStyle, cr,
+ button22BoundRect.Left(), button22BoundRect.Top(),
+ button22BoundRect.GetWidth(), button22BoundRect.GetHeight() );
+ gtk_render_frame(pScrollbarButtonStyle, cr,
+ button22BoundRect.Left(), button22BoundRect.Top(),
+ button22BoundRect.GetWidth(), button22BoundRect.GetHeight() );
+
+ // ----------------- ARROW 2
+ NWCalcArrowRect( button22BoundRect, arrowRect );
+ gtk_render_arrow(pScrollbarButtonStyle, cr,
+ arrow2Angle,
+ arrowRect.Left(), arrowRect.Top(),
+ MIN(arrowRect.GetWidth(), arrowRect.GetHeight()) );
+ }
+
+ if ( has_backward2 )
+ {
+ stateFlags = NWConvertVCLStateToGTKState(rScrollbarVal.mnButton1State);
+ if ( backwardButtonInsensitive )
+ stateFlags = GTK_STATE_FLAG_INSENSITIVE;
+
+ GtkStyleContext* pScrollbarButtonStyle = scrollbarOrientation == GTK_ORIENTATION_VERTICAL ?
+ mpVScrollbarButtonStyle : mpHScrollbarButtonStyle;
+
+ gtk_style_context_set_state(pScrollbarButtonStyle, stateFlags);
+
+ gtk_render_background(pScrollbarButtonStyle, cr,
+ button21BoundRect.Left(), button21BoundRect.Top(),
+ button21BoundRect.GetWidth(), button21BoundRect.GetHeight() );
+ gtk_render_frame(pScrollbarButtonStyle, cr,
+ button21BoundRect.Left(), button21BoundRect.Top(),
+ button21BoundRect.GetWidth(), button21BoundRect.GetHeight() );
+
+ // ----------------- ARROW 2
+ NWCalcArrowRect( button21BoundRect, arrowRect );
+ gtk_render_arrow(pScrollbarButtonStyle, cr,
+ arrow1Angle,
+ arrowRect.Left(), arrowRect.Top(),
+ MIN(arrowRect.GetWidth(), arrowRect.GetHeight()) );
+ }
+
+ // ----------------- TROUGH
+ // trackrect matches that of ScrollBar::ImplCalc
+ tools::Rectangle aTrackRect(Point(0, 0), scrollbarRect.GetSize());
+ if (nPart == ControlPart::DrawBackgroundHorz)
+ {
+ tools::Rectangle aBtn1Rect = NWGetScrollButtonRect(ControlPart::ButtonLeft, aTrackRect);
+ tools::Rectangle aBtn2Rect = NWGetScrollButtonRect(ControlPart::ButtonRight, aTrackRect);
+ if (!aBtn1Rect.IsWidthEmpty())
+ aTrackRect.SetLeft( aBtn1Rect.Right() );
+ if (!aBtn2Rect.IsWidthEmpty())
+ aTrackRect.SetRight( aBtn2Rect.Left() );
+ }
+ else
+ {
+ tools::Rectangle aBtn1Rect = NWGetScrollButtonRect(ControlPart::ButtonUp, aTrackRect);
+ tools::Rectangle aBtn2Rect = NWGetScrollButtonRect(ControlPart::ButtonDown, aTrackRect);
+ if (!aBtn1Rect.IsHeightEmpty())
+ aTrackRect.SetTop( aBtn1Rect.Bottom() + 1 );
+ if (!aBtn2Rect.IsHeightEmpty())
+ aTrackRect.SetBottom( aBtn2Rect.Top() );
+ }
+
+ GtkStyleContext* pScrollbarTroughStyle = scrollbarOrientation == GTK_ORIENTATION_VERTICAL ?
+ mpVScrollbarTroughStyle : mpHScrollbarTroughStyle;
+ gtk_render_background(pScrollbarTroughStyle, cr, aTrackRect.Left(), aTrackRect.Top(),
+ aTrackRect.GetWidth(), aTrackRect.GetHeight() );
+ gtk_render_frame(pScrollbarTroughStyle, cr, aTrackRect.Left(), aTrackRect.Top(),
+ aTrackRect.GetWidth(), aTrackRect.GetHeight() );
+
+ // ----------------- THUMB
+ if ( !has_slider )
+ return;
+
+ stateFlags = NWConvertVCLStateToGTKState(rScrollbarVal.mnThumbState);
+ if ( rScrollbarVal.mnThumbState & ControlState::PRESSED )
+ stateFlags = static_cast<GtkStateFlags>(stateFlags | GTK_STATE_FLAG_PRELIGHT);
+
+ GtkStyleContext* pScrollbarSliderStyle = scrollbarOrientation == GTK_ORIENTATION_VERTICAL ?
+ mpVScrollbarSliderStyle : mpHScrollbarSliderStyle;
+
+ gtk_style_context_set_state(pScrollbarSliderStyle, stateFlags);
+
+ GtkBorder margin;
+ style_context_get_margin(pScrollbarSliderStyle, &margin);
+
+ gtk_render_background(pScrollbarSliderStyle, cr,
+ thumbRect.Left() + margin.left, thumbRect.Top() + margin.top,
+ thumbRect.GetWidth() - margin.left - margin.right,
+ thumbRect.GetHeight() - margin.top - margin.bottom);
+
+ gtk_render_frame(pScrollbarSliderStyle, cr,
+ thumbRect.Left() + margin.left, thumbRect.Top() + margin.top,
+ thumbRect.GetWidth() - margin.left - margin.right,
+ thumbRect.GetHeight() - margin.top - margin.bottom);
+}
+
+void GtkSalGraphics::PaintOneSpinButton( GtkStyleContext *context,
+ cairo_t *cr,
+ ControlPart nPart,
+ tools::Rectangle aAreaRect,
+ ControlState nState )
+{
+ GtkBorder padding, border;
+
+ GtkStateFlags stateFlags = NWConvertVCLStateToGTKState(nState);
+ tools::Rectangle buttonRect = NWGetSpinButtonRect( nPart, aAreaRect );
+
+ gtk_style_context_set_state(context, stateFlags);
+ stateFlags = gtk_style_context_get_state(context);
+
+ style_context_get_padding(context, &padding);
+ style_context_get_border(context, &border);
+
+ gtk_render_background(context, cr,
+ buttonRect.Left(), buttonRect.Top(),
+ buttonRect.GetWidth(), buttonRect.GetHeight() );
+
+ gint iconWidth = buttonRect.GetWidth() - padding.left - padding.right - border.left - border.right;
+ gint iconHeight = buttonRect.GetHeight() - padding.top - padding.bottom - border.top - border.bottom;
+
+ const char* icon = (nPart == ControlPart::ButtonUp) ? "list-add-symbolic" : "list-remove-symbolic";
+ GtkIconTheme *pIconTheme = gtk_icon_theme_get_for_screen(gtk_widget_get_screen(mpWindow));
+
+ gint scale = gtk_style_context_get_scale (context);
+ GtkIconInfo *info = gtk_icon_theme_lookup_icon_for_scale(pIconTheme, icon, std::min(iconWidth, iconHeight), scale,
+ static_cast<GtkIconLookupFlags>(0));
+
+ GdkPixbuf *pixbuf = gtk_icon_info_load_symbolic_for_context(info, context, nullptr, nullptr);
+ g_object_unref(info);
+
+ iconWidth = gdk_pixbuf_get_width(pixbuf)/scale;
+ iconHeight = gdk_pixbuf_get_height(pixbuf)/scale;
+ tools::Rectangle arrowRect(buttonRect.Center() - Point(iconWidth / 2, iconHeight / 2),
+ Size(iconWidth, iconHeight));
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_scale (context, 1);
+ gtk_render_icon(context, cr, pixbuf, arrowRect.Left(), arrowRect.Top());
+ gtk_style_context_restore (context);
+ g_object_unref(pixbuf);
+
+ gtk_render_frame(context, cr,
+ buttonRect.Left(), buttonRect.Top(),
+ buttonRect.GetWidth(), buttonRect.GetHeight() );
+}
+
+void GtkSalGraphics::PaintSpinButton(GtkStateFlags flags,
+ cairo_t *cr,
+ const tools::Rectangle& rControlRectangle,
+ ControlPart nPart,
+ const ImplControlValue& rValue )
+{
+ const SpinbuttonValue *pSpinVal = (rValue.getType() == ControlType::SpinButtons) ? static_cast<const SpinbuttonValue *>(&rValue) : nullptr;
+ ControlPart upBtnPart = ControlPart::ButtonUp;
+ ControlState upBtnState = ControlState::NONE;
+ ControlPart downBtnPart = ControlPart::ButtonDown;
+ ControlState downBtnState = ControlState::NONE;
+
+ if ( pSpinVal )
+ {
+ upBtnPart = pSpinVal->mnUpperPart;
+ upBtnState = pSpinVal->mnUpperState;
+
+ downBtnPart = pSpinVal->mnLowerPart;
+ downBtnState = pSpinVal->mnLowerState;
+ }
+
+ if (nPart == ControlPart::Entire)
+ {
+ gtk_style_context_set_state(mpWindowStyle, flags);
+
+ gtk_render_background(mpWindowStyle, cr,
+ 0, 0,
+ rControlRectangle.GetWidth(), rControlRectangle.GetHeight());
+
+ gtk_style_context_set_state(mpSpinStyle, flags);
+
+ gtk_render_background(mpSpinStyle, cr,
+ 0, 0,
+ rControlRectangle.GetWidth(), rControlRectangle.GetHeight());
+ }
+
+ cairo_translate(cr, -rControlRectangle.Left(), -rControlRectangle.Top());
+ PaintOneSpinButton(mpSpinUpStyle, cr, upBtnPart, rControlRectangle, upBtnState );
+ PaintOneSpinButton(mpSpinDownStyle, cr, downBtnPart, rControlRectangle, downBtnState );
+ cairo_translate(cr, rControlRectangle.Left(), rControlRectangle.Top());
+
+ if (nPart == ControlPart::Entire)
+ {
+ gtk_render_frame(mpSpinStyle, cr,
+ 0, 0,
+ rControlRectangle.GetWidth(), rControlRectangle.GetHeight() );
+ }
+}
+
+#define FALLBACK_ARROW_SIZE gint(11 * 0.85)
+
+tools::Rectangle GtkSalGraphics::NWGetComboBoxButtonRect(ControlType nType,
+ ControlPart nPart,
+ tools::Rectangle aAreaRect )
+{
+ tools::Rectangle aButtonRect;
+
+ GtkBorder padding;
+ if (nType == ControlType::Listbox)
+ style_context_get_padding(mpListboxButtonStyle, &padding);
+ else
+ style_context_get_padding(mpButtonStyle, &padding);
+
+ gint nArrowWidth = FALLBACK_ARROW_SIZE;
+ gtk_style_context_get(mpComboboxButtonArrowStyle,
+ gtk_style_context_get_state(mpComboboxButtonArrowStyle),
+ "min-width", &nArrowWidth, nullptr);
+
+ gint nButtonWidth = nArrowWidth + padding.left + padding.right;
+ if( nPart == ControlPart::ButtonDown )
+ {
+ Point aPos(aAreaRect.Left() + aAreaRect.GetWidth() - nButtonWidth, aAreaRect.Top());
+ if (AllSettings::GetLayoutRTL())
+ aPos.setX( aAreaRect.Left() );
+ aButtonRect.SetSize( Size( nButtonWidth, aAreaRect.GetHeight() ) );
+ aButtonRect.SetPos(aPos);
+ }
+ else if( nPart == ControlPart::SubEdit )
+ {
+ gint adjust_left = padding.left;
+ gint adjust_top = padding.top;
+ gint adjust_right = padding.right;
+ gint adjust_bottom = padding.bottom;
+
+ aButtonRect.SetSize( Size( aAreaRect.GetWidth() - nButtonWidth - (adjust_left + adjust_right),
+ aAreaRect.GetHeight() - (adjust_top + adjust_bottom)) );
+ Point aEditPos = aAreaRect.TopLeft();
+ if (AllSettings::GetLayoutRTL())
+ aEditPos.AdjustX(nButtonWidth );
+ else
+ aEditPos.AdjustX(adjust_left );
+ aEditPos.AdjustY(adjust_top );
+ aButtonRect.SetPos( aEditPos );
+ }
+
+ return aButtonRect;
+}
+
+void GtkSalGraphics::PaintCombobox( GtkStateFlags flags, cairo_t *cr,
+ const tools::Rectangle& rControlRectangle,
+ ControlType nType,
+ ControlPart nPart )
+{
+ tools::Rectangle areaRect;
+ tools::Rectangle buttonRect;
+ tools::Rectangle arrowRect;
+
+ // Find the overall bounding rect of the buttons's drawing area,
+ // plus its actual draw rect excluding adornment
+ areaRect = rControlRectangle;
+
+ buttonRect = NWGetComboBoxButtonRect(ControlType::Combobox, ControlPart::ButtonDown, areaRect);
+
+ tools::Rectangle aEditBoxRect( areaRect );
+ aEditBoxRect.SetSize( Size( areaRect.GetWidth() - buttonRect.GetWidth(), aEditBoxRect.GetHeight() ) );
+ if (AllSettings::GetLayoutRTL())
+ aEditBoxRect.SetPos( Point( areaRect.Left() + buttonRect.GetWidth(), areaRect.Top() ) );
+
+ gint arrow_width = FALLBACK_ARROW_SIZE, arrow_height = FALLBACK_ARROW_SIZE;
+ if (nType == ControlType::Combobox)
+ {
+ gtk_style_context_get(mpComboboxButtonArrowStyle,
+ gtk_style_context_get_state(mpComboboxButtonArrowStyle),
+ "min-width", &arrow_width, "min-height", &arrow_height, nullptr);
+ }
+ else if (nType == ControlType::Listbox)
+ {
+ gtk_style_context_get(mpListboxButtonArrowStyle,
+ gtk_style_context_get_state(mpListboxButtonArrowStyle),
+ "min-width", &arrow_width, "min-height", &arrow_height, nullptr);
+ }
+
+ arrowRect.SetSize(Size(arrow_width, arrow_height));
+ arrowRect.SetPos( Point( buttonRect.Left() + static_cast<gint>((buttonRect.GetWidth() - arrowRect.GetWidth()) / 2),
+ buttonRect.Top() + static_cast<gint>((buttonRect.GetHeight() - arrowRect.GetHeight()) / 2) ) );
+
+
+ tools::Rectangle aRect(Point(0, 0), Size(areaRect.GetWidth(), areaRect.GetHeight()));
+
+ if (nType == ControlType::Combobox)
+ {
+ if( nPart == ControlPart::Entire )
+ {
+ render_common(mpComboboxStyle, cr, aRect, flags);
+ render_common(mpComboboxBoxStyle, cr, aRect, flags);
+ tools::Rectangle aEntryRect(Point(aEditBoxRect.Left() - areaRect.Left(),
+ aEditBoxRect.Top() - areaRect.Top()),
+ Size(aEditBoxRect.GetWidth(), aEditBoxRect.GetHeight()));
+
+ GtkJunctionSides eJuncSides = gtk_style_context_get_junction_sides(mpComboboxEntryStyle);
+ if (AllSettings::GetLayoutRTL())
+ gtk_style_context_set_junction_sides(mpComboboxEntryStyle, GTK_JUNCTION_LEFT);
+ else
+ gtk_style_context_set_junction_sides(mpComboboxEntryStyle, GTK_JUNCTION_RIGHT);
+ render_common(mpComboboxEntryStyle, cr, aEntryRect, flags);
+ gtk_style_context_set_junction_sides(mpComboboxEntryStyle, eJuncSides);
+ }
+
+ tools::Rectangle aButtonRect(Point(buttonRect.Left() - areaRect.Left(), buttonRect.Top() - areaRect.Top()),
+ Size(buttonRect.GetWidth(), buttonRect.GetHeight()));
+ GtkJunctionSides eJuncSides = gtk_style_context_get_junction_sides(mpComboboxButtonStyle);
+ if (AllSettings::GetLayoutRTL())
+ gtk_style_context_set_junction_sides(mpComboboxButtonStyle, GTK_JUNCTION_RIGHT);
+ else
+ gtk_style_context_set_junction_sides(mpComboboxButtonStyle, GTK_JUNCTION_LEFT);
+ render_common(mpComboboxButtonStyle, cr, aButtonRect, flags);
+ gtk_style_context_set_junction_sides(mpComboboxButtonStyle, eJuncSides);
+
+ gtk_render_arrow(mpComboboxButtonArrowStyle, cr,
+ G_PI,
+ (arrowRect.Left() - areaRect.Left()), (arrowRect.Top() - areaRect.Top()),
+ arrowRect.GetWidth() );
+ }
+ else if (nType == ControlType::Listbox)
+ {
+ if( nPart == ControlPart::ListboxWindow )
+ {
+ /* render the popup window with the menu style */
+ gtk_render_frame(mpMenuStyle, cr,
+ 0, 0,
+ areaRect.GetWidth(), areaRect.GetHeight());
+ }
+ else
+ {
+ render_common(mpListboxStyle, cr, aRect, flags);
+ render_common(mpListboxBoxStyle, cr, aRect, flags);
+
+ render_common(mpListboxButtonStyle, cr, aRect, flags);
+
+ gtk_render_arrow(mpListboxButtonArrowStyle, cr,
+ G_PI,
+ (arrowRect.Left() - areaRect.Left()), (arrowRect.Top() - areaRect.Top()),
+ arrowRect.GetWidth() );
+ }
+ }
+}
+
+static void appendComboEntry(GtkWidgetPath* pSiblingsPath)
+{
+ gtk_widget_path_append_type(pSiblingsPath, GTK_TYPE_ENTRY);
+ gtk_widget_path_iter_set_object_name(pSiblingsPath, -1, "entry");
+ gtk_widget_path_iter_add_class(pSiblingsPath, -1, "combo");
+}
+
+static void appendComboButton(GtkWidgetPath* pSiblingsPath)
+{
+ gtk_widget_path_append_type(pSiblingsPath, GTK_TYPE_BUTTON);
+ gtk_widget_path_iter_set_object_name(pSiblingsPath, -1, "button");
+ gtk_widget_path_iter_add_class(pSiblingsPath, -1, "combo");
+}
+
+static GtkWidgetPath* buildLTRComboSiblingsPath()
+{
+ GtkWidgetPath* pSiblingsPath = gtk_widget_path_new();
+
+ appendComboEntry(pSiblingsPath);
+ appendComboButton(pSiblingsPath);
+
+ return pSiblingsPath;
+}
+
+static GtkWidgetPath* buildRTLComboSiblingsPath()
+{
+ GtkWidgetPath* pSiblingsPath = gtk_widget_path_new();
+
+ appendComboButton(pSiblingsPath);
+ appendComboEntry(pSiblingsPath);
+
+ return pSiblingsPath;
+}
+
+
+GtkStyleContext* GtkSalGraphics::makeContext(GtkWidgetPath *pPath, GtkStyleContext *pParent)
+{
+ GtkStyleContext* context = gtk_style_context_new();
+ gtk_style_context_set_screen(context, gtk_widget_get_screen(mpWindow));
+ gtk_style_context_set_path(context, pPath);
+ if (pParent == nullptr)
+ {
+ GtkWidget* pTopLevel = widget_get_toplevel(mpWindow);
+ GtkStyleContext* pStyle = gtk_widget_get_style_context(pTopLevel);
+ gtk_style_context_set_parent(context, pStyle);
+ gtk_style_context_set_scale (context, gtk_style_context_get_scale (pStyle));
+ }
+ else
+ {
+ gtk_style_context_set_parent(context, pParent);
+ gtk_style_context_set_scale (context, gtk_style_context_get_scale (pParent));
+ }
+ gtk_widget_path_unref(pPath);
+ return context;
+}
+
+GtkStyleContext* GtkSalGraphics::createStyleContext(GtkControlPart ePart)
+{
+ switch (ePart)
+ {
+ case GtkControlPart::ToplevelWindow:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "window");
+ gtk_widget_path_iter_add_class(path, -1, "background");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::Button:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "button");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::LinkButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "button");
+ gtk_widget_path_iter_add_class(path, -1, "link");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::CheckButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_CHECK_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "checkbutton");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::CheckButtonCheck:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpCheckButtonStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_CHECK_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "check");
+ return makeContext(path, mpCheckButtonStyle);
+ }
+ case GtkControlPart::RadioButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_RADIO_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "radiobutton");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::RadioButtonRadio:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpRadioButtonStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_RADIO_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "radio");
+ return makeContext(path, mpRadioButtonStyle);
+ }
+ case GtkControlPart::ComboboxBoxButtonBoxArrow:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpComboboxButtonBoxStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_RADIO_BUTTON);
+ gtk_widget_path_append_type(path, GTK_TYPE_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "arrow");
+ return makeContext(path, mpComboboxButtonBoxStyle);
+ }
+ case GtkControlPart::ListboxBoxButtonBoxArrow:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpListboxButtonBoxStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_RADIO_BUTTON);
+ gtk_widget_path_append_type(path, GTK_TYPE_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "arrow");
+ return makeContext(path, mpListboxButtonBoxStyle);
+ }
+ case GtkControlPart::Entry:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_ENTRY);
+ gtk_widget_path_iter_set_object_name(path, -1, "entry");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::Combobox:
+ case GtkControlPart::Listbox:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "combobox");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::ComboboxBox:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpComboboxStyle));
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "box");
+ gtk_widget_path_iter_add_class(path, -1, "horizontal");
+ gtk_widget_path_iter_add_class(path, -1, "linked");
+ return makeContext(path, mpComboboxStyle);
+ }
+ case GtkControlPart::ListboxBox:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpListboxStyle));
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "box");
+ gtk_widget_path_iter_add_class(path, -1, "horizontal");
+ gtk_widget_path_iter_add_class(path, -1, "linked");
+ return makeContext(path, mpListboxStyle);
+ }
+ case GtkControlPart::ComboboxBoxEntry:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpComboboxBoxStyle));
+ GtkWidgetPath* pSiblingsPath;
+ if (AllSettings::GetLayoutRTL())
+ {
+ pSiblingsPath = buildRTLComboSiblingsPath();
+ gtk_widget_path_append_with_siblings(path, pSiblingsPath, 1);
+ }
+ else
+ {
+ pSiblingsPath = buildLTRComboSiblingsPath();
+ gtk_widget_path_append_with_siblings(path, pSiblingsPath, 0);
+ }
+ gtk_widget_path_unref(pSiblingsPath);
+ return makeContext(path, mpComboboxBoxStyle);
+ }
+ case GtkControlPart::ComboboxBoxButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpComboboxBoxStyle));
+ GtkWidgetPath* pSiblingsPath;
+ if (AllSettings::GetLayoutRTL())
+ {
+ pSiblingsPath = buildRTLComboSiblingsPath();
+ gtk_widget_path_append_with_siblings(path, pSiblingsPath, 0);
+ }
+ else
+ {
+ pSiblingsPath = buildLTRComboSiblingsPath();
+ gtk_widget_path_append_with_siblings(path, pSiblingsPath, 1);
+ }
+ gtk_widget_path_unref(pSiblingsPath);
+ return makeContext(path, mpComboboxBoxStyle);
+ }
+ case GtkControlPart::ListboxBoxButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpListboxBoxStyle));
+ GtkWidgetPath* pSiblingsPath = gtk_widget_path_new();
+
+ gtk_widget_path_append_type(pSiblingsPath, GTK_TYPE_BUTTON);
+ gtk_widget_path_iter_set_object_name(pSiblingsPath, -1, "button");
+ gtk_widget_path_iter_add_class(pSiblingsPath, -1, "combo");
+
+ gtk_widget_path_append_with_siblings(path, pSiblingsPath, 0);
+ gtk_widget_path_unref(pSiblingsPath);
+ return makeContext(path, mpListboxBoxStyle);
+ }
+ case GtkControlPart::ComboboxBoxButtonBox:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpComboboxButtonStyle));
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "box");
+ gtk_widget_path_iter_add_class(path, -1, "horizontal");
+ return makeContext(path, mpComboboxButtonStyle);
+ }
+ case GtkControlPart::ListboxBoxButtonBox:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpListboxButtonStyle));
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "box");
+ gtk_widget_path_iter_add_class(path, -1, "horizontal");
+ return makeContext(path, mpListboxButtonStyle);
+ }
+ case GtkControlPart::SpinButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpWindowStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_SPIN_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "spinbutton");
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_HORIZONTAL);
+ return makeContext(path, mpWindowStyle);
+ }
+ case GtkControlPart::SpinButtonUpButton:
+ case GtkControlPart::SpinButtonDownButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpSpinStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_SPIN_BUTTON);
+ gtk_widget_path_iter_set_object_name(path, -1, "button");
+ gtk_widget_path_iter_add_class(path, -1, ePart == GtkControlPart::SpinButtonUpButton ? "up" : "down");
+ return makeContext(path, mpSpinStyle);
+ }
+ case GtkControlPart::ScrollbarVertical:
+ case GtkControlPart::ScrollbarHorizontal:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_SCROLLBAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "scrollbar");
+ gtk_widget_path_iter_add_class(path, -1, ePart == GtkControlPart::ScrollbarVertical ? "vertical" : "horizontal");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::ScrollbarVerticalContents:
+ case GtkControlPart::ScrollbarHorizontalContents:
+ {
+ GtkStyleContext *pParent =
+ (ePart == GtkControlPart::ScrollbarVerticalContents) ? mpVScrollbarStyle : mpHScrollbarStyle;
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(pParent));
+ gtk_widget_path_append_type(path, GTK_TYPE_SCROLLBAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "contents");
+ return makeContext(path, pParent);
+ }
+ case GtkControlPart::ScrollbarVerticalTrough:
+ case GtkControlPart::ScrollbarHorizontalTrough:
+ {
+ GtkStyleContext *pParent =
+ (ePart == GtkControlPart::ScrollbarVerticalTrough) ? mpVScrollbarContentsStyle : mpHScrollbarContentsStyle;
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(pParent));
+ gtk_widget_path_append_type(path, GTK_TYPE_SCROLLBAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "trough");
+ return makeContext(path, pParent);
+ }
+ case GtkControlPart::ScrollbarVerticalSlider:
+ case GtkControlPart::ScrollbarHorizontalSlider:
+ {
+ GtkStyleContext *pParent =
+ (ePart == GtkControlPart::ScrollbarVerticalSlider) ? mpVScrollbarTroughStyle : mpHScrollbarTroughStyle;
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(pParent));
+ gtk_widget_path_append_type(path, GTK_TYPE_SCROLLBAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "slider");
+ return makeContext(path, pParent);
+ }
+ case GtkControlPart::ScrollbarVerticalButton:
+ case GtkControlPart::ScrollbarHorizontalButton:
+ {
+ GtkStyleContext *pParent =
+ (ePart == GtkControlPart::ScrollbarVerticalButton) ? mpVScrollbarStyle : mpHScrollbarStyle;
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(pParent));
+ gtk_widget_path_append_type(path, GTK_TYPE_SCROLLBAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "button");
+ return makeContext(path, pParent);
+ }
+ case GtkControlPart::ProgressBar:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_PROGRESS_BAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "progressbar");
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_HORIZONTAL);
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::ProgressBarTrough:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpProgressBarStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_PROGRESS_BAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "trough");
+ return makeContext(path, mpProgressBarStyle);
+ }
+ case GtkControlPart::ProgressBarProgress:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpProgressBarTroughStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_PROGRESS_BAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "progress");
+ return makeContext(path, mpProgressBarTroughStyle);
+ }
+ case GtkControlPart::Notebook:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpWindowStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_NOTEBOOK);
+ gtk_widget_path_iter_set_object_name(path, -1, "notebook");
+ return makeContext(path, mpWindowStyle);
+ }
+ case GtkControlPart::NotebookStack:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpNotebookStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_NOTEBOOK);
+ gtk_widget_path_iter_set_object_name(path, -1, "stack");
+ return makeContext(path, mpNotebookStyle);
+ }
+ case GtkControlPart::NotebookHeader:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpNotebookStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_NOTEBOOK);
+ gtk_widget_path_iter_set_object_name(path, -1, "header");
+ gtk_widget_path_iter_add_class(path, -1, "frame");
+ gtk_widget_path_iter_add_class(path, -1, "top");
+ return makeContext(path, mpNotebookStyle);
+ }
+ case GtkControlPart::NotebookHeaderTabs:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpNotebookHeaderStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_NOTEBOOK);
+ gtk_widget_path_iter_set_object_name(path, -1, "tabs");
+ gtk_widget_path_iter_add_class(path, -1, "top");
+ return makeContext(path, mpNotebookHeaderStyle);
+ }
+ case GtkControlPart::NotebookHeaderTabsTab:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpNotebookHeaderTabsStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_NOTEBOOK);
+ gtk_widget_path_iter_set_object_name(path, -1, "tab");
+ gtk_widget_path_iter_add_class(path, -1, "top");
+ return makeContext(path, mpNotebookHeaderTabsStyle);
+ }
+ case GtkControlPart::NotebookHeaderTabsTabLabel:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpNotebookHeaderTabsTabStyle));
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "label");
+ return makeContext(path, mpNotebookHeaderTabsTabStyle);
+ }
+ case GtkControlPart::NotebookHeaderTabsTabActiveLabel:
+ case GtkControlPart::NotebookHeaderTabsTabHoverLabel:
+ return mpNotebookHeaderTabsTabLabelStyle;
+ case GtkControlPart::FrameBorder:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_FRAME);
+ gtk_widget_path_iter_set_object_name(path, -1, "frame");
+ gtk_widget_path_iter_add_class(path, -1, "frame");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::MenuBar:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpWindowStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_MENU_BAR);
+ gtk_widget_path_iter_set_object_name(path, -1, "menubar");
+ return makeContext(path, mpWindowStyle);
+ }
+ case GtkControlPart::MenuBarItem:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuBarStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "menuitem");
+ return makeContext(path, mpMenuBarStyle);
+ }
+ case GtkControlPart::MenuWindow:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuBarItemStyle));
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "window");
+ gtk_widget_path_iter_add_class(path, -1, "background");
+ gtk_widget_path_iter_add_class(path, -1, "popup");
+ return makeContext(path, mpMenuBarItemStyle);
+ }
+ case GtkControlPart::Menu:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuWindowStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_MENU);
+ gtk_widget_path_iter_set_object_name(path, -1, "menu");
+ return makeContext(path, mpMenuWindowStyle);
+ }
+ case GtkControlPart::MenuItem:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "menuitem");
+ return makeContext(path, mpMenuStyle);
+ }
+ case GtkControlPart::MenuItemLabel:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuItemStyle));
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ gtk_widget_path_iter_set_object_name(path, -1, "label");
+ return makeContext(path, mpMenuItemStyle);
+ }
+ case GtkControlPart::MenuItemArrow:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuItemStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "arrow");
+ return makeContext(path, mpMenuItemStyle);
+ }
+ case GtkControlPart::CheckMenuItem:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_CHECK_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "menuitem");
+ return makeContext(path, mpMenuStyle);
+ }
+ case GtkControlPart::CheckMenuItemCheck:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpCheckMenuItemStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_CHECK_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "check");
+ return makeContext(path, mpCheckMenuItemStyle);
+ }
+ case GtkControlPart::RadioMenuItem:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_RADIO_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "menuitem");
+ return makeContext(path, mpMenuStyle);
+ }
+ case GtkControlPart::RadioMenuItemRadio:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpRadioMenuItemStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_RADIO_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "radio");
+ return makeContext(path, mpRadioMenuItemStyle);
+ }
+ case GtkControlPart::SeparatorMenuItem:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpMenuStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_SEPARATOR_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "menuitem");
+ return makeContext(path, mpMenuStyle);
+ }
+ case GtkControlPart::SeparatorMenuItemSeparator:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpSeparatorMenuItemStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_SEPARATOR_MENU_ITEM);
+ gtk_widget_path_iter_set_object_name(path, -1, "separator");
+ return makeContext(path, mpSeparatorMenuItemStyle);
+ }
+ }
+
+ return nullptr;
+}
+
+#ifndef GTK_STYLE_CLASS_POPUP
+constexpr OUStringLiteral GTK_STYLE_CLASS_POPUP = u"popup";
+#endif
+#ifndef GTK_STYLE_CLASS_LABEL
+constexpr OUStringLiteral GTK_STYLE_CLASS_LABEL = u"label";
+#endif
+
+void GtkSalGraphics::PaintCheckOrRadio(cairo_t *cr, GtkStyleContext *context,
+ const tools::Rectangle& rControlRectangle, bool bIsCheck, bool bInMenu)
+{
+ gint indicator_size;
+ gtk_style_context_get_style(context, "indicator-size", &indicator_size, nullptr);
+
+ gint x = (rControlRectangle.GetWidth() - indicator_size) / 2;
+ gint y = (rControlRectangle.GetHeight() - indicator_size) / 2;
+
+ if (!bInMenu)
+ gtk_render_background(context, cr, x, y, indicator_size, indicator_size);
+
+ if (bIsCheck)
+ gtk_render_check(context, cr, x, y, indicator_size, indicator_size);
+ else
+ gtk_render_option(context, cr, x, y, indicator_size, indicator_size);
+
+ gtk_render_frame(context, cr, x, y, indicator_size, indicator_size);
+}
+
+void GtkSalGraphics::PaintCheck(cairo_t *cr, GtkStyleContext *context,
+ const tools::Rectangle& rControlRectangle, bool bInMenu)
+{
+ PaintCheckOrRadio(cr, context, rControlRectangle, true, bInMenu);
+}
+
+void GtkSalGraphics::PaintRadio(cairo_t *cr, GtkStyleContext *context,
+ const tools::Rectangle& rControlRectangle, bool bInMenu)
+{
+ PaintCheckOrRadio(cr, context, rControlRectangle, false, bInMenu);
+}
+
+static gfloat getArrowSize(GtkStyleContext* context)
+{
+ gint min_width, min_weight;
+ gtk_style_context_get_style(context, "min-width", &min_width, nullptr);
+ gtk_style_context_get_style(context, "min-height", &min_weight, nullptr);
+ gfloat arrow_size = 11 * MAX (min_width, min_weight);
+ return arrow_size;
+}
+
+namespace
+{
+ void draw_vertical_separator(GtkStyleContext *context, cairo_t *cr, const tools::Rectangle& rControlRegion)
+ {
+ tools::Long nX = 0;
+ tools::Long nY = 0;
+
+ gint nSeparatorWidth = 1;
+
+ gtk_style_context_get(context,
+ gtk_style_context_get_state(context),
+ "min-width", &nSeparatorWidth, nullptr);
+
+ gint nHalfSeparatorWidth = nSeparatorWidth / 2;
+ gint nHalfRegionWidth = rControlRegion.GetWidth() / 2;
+
+ nX = nX + nHalfRegionWidth - nHalfSeparatorWidth;
+ nY = rControlRegion.GetHeight() > 5 ? 1 : 0;
+ int nHeight = rControlRegion.GetHeight() - (2 * nY);
+
+ gtk_render_background(context, cr, nX, nY, nSeparatorWidth, nHeight);
+ gtk_render_frame(context, cr, nX, nY, nSeparatorWidth, nHeight);
+ }
+
+ void draw_horizontal_separator(GtkStyleContext *context, cairo_t *cr, const tools::Rectangle& rControlRegion)
+ {
+ tools::Long nX = 0;
+ tools::Long nY = 0;
+
+ gint nSeparatorHeight = 1;
+
+ gtk_style_context_get(context,
+ gtk_style_context_get_state(context),
+ "min-height", &nSeparatorHeight, nullptr);
+
+ gint nHalfSeparatorHeight = nSeparatorHeight / 2;
+ gint nHalfRegionHeight = rControlRegion.GetHeight() / 2;
+
+ nY = nY + nHalfRegionHeight - nHalfSeparatorHeight;
+ nX = rControlRegion.GetWidth() > 5 ? 1 : 0;
+ int nWidth = rControlRegion.GetWidth() - (2 * nX);
+
+ gtk_render_background(context, cr, nX, nY, nWidth, nSeparatorHeight);
+ gtk_render_frame(context, cr, nX, nY, nWidth, nSeparatorHeight);
+ }
+}
+#endif
+
+void GtkSalGraphics::handleDamage(const tools::Rectangle& rDamagedRegion)
+{
+ assert(m_pWidgetDraw);
+ assert(!rDamagedRegion.IsEmpty());
+ mpFrame->damaged(rDamagedRegion.Left(), rDamagedRegion.Top(), rDamagedRegion.GetWidth(), rDamagedRegion.GetHeight());
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+bool GtkSalGraphics::drawNativeControl( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion,
+ ControlState nState, const ImplControlValue& rValue,
+ const OUString&, const Color& rBackgroundColor)
+{
+ RenderType renderType = nPart == ControlPart::Focus ? RenderType::Focus : RenderType::BackgroundAndFrame;
+ GtkStyleContext *context = nullptr;
+ GdkPixbuf *pixbuf = nullptr;
+ bool bInMenu = false;
+
+ GtkStateFlags flags = NWConvertVCLStateToGTKState(nState);
+
+ switch(nType)
+ {
+ case ControlType::Spinbox:
+ case ControlType::SpinButtons:
+ context = mpSpinStyle;
+ renderType = RenderType::Spinbutton;
+ break;
+ case ControlType::Editbox:
+ context = mpEntryStyle;
+ break;
+ case ControlType::MultilineEditbox:
+ context = mpTextViewStyle;
+ break;
+ case ControlType::Combobox:
+ context = mpComboboxStyle;
+ renderType = RenderType::Combobox;
+ break;
+ case ControlType::Listbox:
+ if (nPart == ControlPart::Focus)
+ {
+ renderType = RenderType::Focus;
+ context = mpListboxButtonStyle;
+ }
+ else
+ {
+ renderType = RenderType::Combobox;
+ context = mpListboxStyle;
+ }
+ break;
+ case ControlType::MenuPopup:
+ bInMenu = true;
+
+ // map selected menu entries in vcl parlance to gtk prelight
+ if (nPart >= ControlPart::MenuItem && nPart <= ControlPart::SubmenuArrow && (nState & ControlState::SELECTED))
+ flags = static_cast<GtkStateFlags>(flags | GTK_STATE_FLAG_PRELIGHT);
+ flags = static_cast<GtkStateFlags>(flags & ~GTK_STATE_FLAG_ACTIVE);
+ switch(nPart)
+ {
+ case ControlPart::MenuItem:
+ context = mpMenuItemStyle;
+ renderType = RenderType::BackgroundAndFrame;
+ break;
+ case ControlPart::MenuItemCheckMark:
+ context = mpCheckMenuItemCheckStyle;
+ renderType = RenderType::Check;
+ nType = ControlType::Checkbox;
+ if (nState & ControlState::PRESSED)
+ {
+ flags = static_cast<GtkStateFlags>(flags | GTK_STATE_FLAG_CHECKED);
+ }
+ break;
+ case ControlPart::MenuItemRadioMark:
+ context = mpRadioMenuItemRadioStyle;
+ renderType = RenderType::Radio;
+ nType = ControlType::Radiobutton;
+ if (nState & ControlState::PRESSED)
+ {
+ flags = static_cast<GtkStateFlags>(flags | GTK_STATE_FLAG_CHECKED);
+ }
+ break;
+ case ControlPart::Separator:
+ context = mpSeparatorMenuItemSeparatorStyle;
+ flags = GtkStateFlags(GTK_STATE_FLAG_BACKDROP | GTK_STATE_FLAG_INSENSITIVE); //GTK_STATE_FLAG_BACKDROP hack ?
+ renderType = RenderType::MenuSeparator;
+ break;
+ case ControlPart::SubmenuArrow:
+ context = mpMenuItemArrowStyle;
+ renderType = RenderType::Arrow;
+ break;
+ case ControlPart::Entire:
+ context = mpMenuStyle;
+ renderType = RenderType::Background;
+ break;
+ default: break;
+ }
+ break;
+ case ControlType::Toolbar:
+ switch(nPart)
+ {
+ case ControlPart::DrawBackgroundHorz:
+ case ControlPart::DrawBackgroundVert:
+ context = mpToolbarStyle;
+ break;
+ case ControlPart::Button:
+ /* For all checkbuttons in the toolbars */
+ flags = static_cast<GtkStateFlags>(flags |
+ ( (rValue.getTristateVal() == ButtonValue::On) ? GTK_STATE_FLAG_CHECKED : GTK_STATE_FLAG_NORMAL));
+ context = mpToolButtonStyle;
+ break;
+ case ControlPart::SeparatorVert:
+ context = mpToolbarSeparatorStyle;
+ renderType = RenderType::ToolbarSeparator;
+ break;
+ default:
+ return false;
+ }
+ break;
+ case ControlType::Radiobutton:
+ flags = static_cast<GtkStateFlags>(flags |
+ ( (rValue.getTristateVal() == ButtonValue::On) ? GTK_STATE_FLAG_CHECKED : GTK_STATE_FLAG_NORMAL));
+ context = mpRadioButtonRadioStyle;
+ renderType = nPart == ControlPart::Focus ? RenderType::Focus : RenderType::Radio;
+ break;
+ case ControlType::Checkbox:
+ flags = static_cast<GtkStateFlags>(flags |
+ ( (rValue.getTristateVal() == ButtonValue::On) ? GTK_STATE_FLAG_CHECKED :
+ (rValue.getTristateVal() == ButtonValue::Mixed) ? GTK_STATE_FLAG_INCONSISTENT :
+ GTK_STATE_FLAG_NORMAL));
+ context = mpCheckButtonCheckStyle;
+ renderType = nPart == ControlPart::Focus ? RenderType::Focus : RenderType::Check;
+ break;
+ case ControlType::Pushbutton:
+ context = mpButtonStyle;
+ break;
+ case ControlType::Scrollbar:
+ switch(nPart)
+ {
+ case ControlPart::DrawBackgroundVert:
+ case ControlPart::DrawBackgroundHorz:
+ context = (nPart == ControlPart::DrawBackgroundVert)
+ ? mpVScrollbarStyle : mpHScrollbarStyle;
+ renderType = RenderType::Scrollbar;
+ break;
+ default: break;
+ }
+ break;
+ case ControlType::ListNet:
+ return true;
+ case ControlType::TabPane:
+ context = mpNotebookStyle;
+ break;
+ case ControlType::TabBody:
+ context = mpNotebookStackStyle;
+ break;
+ case ControlType::TabHeader:
+ context = mpNotebookHeaderStyle;
+ break;
+ case ControlType::TabItem:
+ context = mpNotebookHeaderTabsTabStyle;
+ if (nState & ControlState::SELECTED)
+ flags = static_cast<GtkStateFlags>(flags | GTK_STATE_FLAG_CHECKED);
+ renderType = RenderType::TabItem;
+ break;
+ case ControlType::WindowBackground:
+ context = gtk_widget_get_style_context(widget_get_toplevel(mpWindow));
+ break;
+ case ControlType::Frame:
+ {
+ DrawFrameStyle nStyle = static_cast<DrawFrameStyle>(rValue.getNumericVal() & 0x0f);
+ if (nStyle == DrawFrameStyle::In)
+ context = mpFrameOutStyle;
+ else
+ context = mpFrameInStyle;
+ break;
+ }
+ case ControlType::Menubar:
+ if (nPart == ControlPart::MenuItem)
+ {
+ context = mpMenuBarItemStyle;
+
+ flags = (!(nState & ControlState::ENABLED)) ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL;
+ if (nState & ControlState::SELECTED)
+ flags = static_cast<GtkStateFlags>(flags | GTK_STATE_FLAG_PRELIGHT);
+ }
+ else
+ {
+ context = mpMenuBarStyle;
+ }
+ break;
+ case ControlType::Fixedline:
+ context = nPart == ControlPart::SeparatorHorz ? mpFixedHoriLineStyle : mpFixedVertLineStyle;
+ renderType = RenderType::Separator;
+ break;
+ case ControlType::ListNode:
+ {
+ context = mpTreeHeaderButtonStyle;
+ ButtonValue aButtonValue = rValue.getTristateVal();
+ if (aButtonValue == ButtonValue::On)
+ flags = static_cast<GtkStateFlags>(flags | GTK_STATE_FLAG_CHECKED);
+ renderType = RenderType::Expander;
+ break;
+ }
+ case ControlType::ListHeader:
+ context = mpTreeHeaderButtonStyle;
+ if (nPart == ControlPart::Arrow)
+ {
+ const char* icon = (rValue.getNumericVal() & 1) ? "pan-down-symbolic" : "pan-up-symbolic";
+ GtkIconTheme *pIconTheme = gtk_icon_theme_get_for_screen(gtk_widget_get_screen(mpWindow));
+ pixbuf = gtk_icon_theme_load_icon_for_scale(pIconTheme, icon,
+ std::max(rControlRegion.GetWidth(), rControlRegion.GetHeight()),
+ gtk_style_context_get_scale (context),
+ static_cast<GtkIconLookupFlags>(0), nullptr);
+ flags = GTK_STATE_FLAG_SELECTED;
+ renderType = RenderType::Icon;
+ }
+ break;
+ case ControlType::Progress:
+ context = mpProgressBarProgressStyle;
+ renderType = RenderType::Progress;
+ break;
+ default:
+ return false;
+ }
+
+ cairo_t *cr = getCairoContext();
+ clipRegion(cr);
+ cairo_translate(cr, rControlRegion.Left(), rControlRegion.Top());
+
+ tools::Long nX = 0;
+ tools::Long nY = 0;
+ tools::Long nWidth = rControlRegion.GetWidth();
+ tools::Long nHeight = rControlRegion.GetHeight();
+
+ StyleContextSave aContextState;
+ aContextState.save(context);
+ style_context_set_state(context, flags);
+
+ // apply background in style, if explicitly set
+ // note: for more complex controls that use multiple styles for their elements,
+ // background may have to be applied for more of those as well (s. case RenderType::Combobox below)
+ GtkCssProvider* pBgCssProvider = nullptr;
+ if (rBackgroundColor != COL_AUTO)
+ {
+ const OUString sColorCss = "* { background-color: #" + rBackgroundColor.AsRGBHexString() + "; }";
+ const OString aResult = OUStringToOString(sColorCss, RTL_TEXTENCODING_UTF8);
+ pBgCssProvider = gtk_css_provider_new();
+ css_provider_load_from_data(pBgCssProvider, aResult.getStr(), aResult.getLength());
+ gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(pBgCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+ switch(renderType)
+ {
+ case RenderType::Background:
+ case RenderType::BackgroundAndFrame:
+ gtk_render_background(context, cr, nX, nY, nWidth, nHeight);
+ if (renderType == RenderType::BackgroundAndFrame)
+ {
+ gtk_render_frame(context, cr, nX, nY, nWidth, nHeight);
+ }
+ break;
+ case RenderType::Check:
+ {
+ PaintCheck(cr, context, rControlRegion, bInMenu);
+ break;
+ }
+ case RenderType::Radio:
+ {
+ PaintRadio(cr, context, rControlRegion, bInMenu);
+ break;
+ }
+ case RenderType::MenuSeparator:
+ gtk_render_line(context, cr,
+ 0, rControlRegion.GetHeight() / 2,
+ rControlRegion.GetWidth() - 1, rControlRegion.GetHeight() / 2);
+ break;
+ case RenderType::ToolbarSeparator:
+ {
+ draw_vertical_separator(context, cr, rControlRegion);
+ break;
+ }
+ case RenderType::Separator:
+ if (nPart == ControlPart::SeparatorHorz)
+ draw_horizontal_separator(context, cr, rControlRegion);
+ else
+ draw_vertical_separator(context, cr, rControlRegion);
+ break;
+ case RenderType::Arrow:
+ gtk_render_arrow(context, cr,
+ G_PI / 2, 0, 0,
+ MIN(rControlRegion.GetWidth(), 1 + rControlRegion.GetHeight()));
+ break;
+ case RenderType::Expander:
+ gtk_render_expander(context, cr, -2, -2, nWidth+4, nHeight+4);
+ break;
+ case RenderType::Scrollbar:
+ PaintScrollbar(context, cr, rControlRegion, nPart, rValue);
+ break;
+ case RenderType::Spinbutton:
+ PaintSpinButton(flags, cr, rControlRegion, nPart, rValue);
+ break;
+ case RenderType::Combobox:
+ if (pBgCssProvider)
+ {
+ gtk_style_context_add_provider(mpComboboxEntryStyle, GTK_STYLE_PROVIDER(pBgCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ PaintCombobox(flags, cr, rControlRegion, nType, nPart);
+ if (pBgCssProvider)
+ {
+ gtk_style_context_remove_provider(mpComboboxEntryStyle, GTK_STYLE_PROVIDER(pBgCssProvider));
+ }
+ break;
+ case RenderType::Icon:
+ gtk_style_context_save (context);
+ gtk_style_context_set_scale (context, 1);
+ gtk_render_icon(context, cr, pixbuf, nX, nY);
+ gtk_style_context_restore (context);
+ g_object_unref(pixbuf);
+ break;
+ case RenderType::Focus:
+ {
+ if (nType == ControlType::Checkbox ||
+ nType == ControlType::Radiobutton)
+ {
+ nX -= 2; nY -=2;
+ nHeight += 4; nWidth += 4;
+ }
+ else
+ {
+ GtkBorder border;
+
+ style_context_get_border(context, &border);
+
+ nX += border.left;
+ nY += border.top;
+ nWidth -= border.left + border.right;
+ nHeight -= border.top + border.bottom;
+ }
+
+ gtk_render_focus(context, cr, nX, nY, nWidth, nHeight);
+
+ break;
+ }
+ case RenderType::Progress:
+ {
+ gtk_render_background(mpProgressBarTroughStyle, cr, nX, nY, nWidth, nHeight);
+
+ tools::Long nProgressWidth = rValue.getNumericVal();
+ if (nProgressWidth)
+ {
+ GtkBorder padding;
+ style_context_get_padding(context, &padding);
+
+ nX += padding.left;
+ nY += padding.top;
+ nHeight -= (padding.top + padding.bottom);
+ nProgressWidth -= (padding.left + padding.right);
+ gtk_render_background(context, cr, nX, nY, nProgressWidth, nHeight);
+ gtk_render_frame(context, cr, nX, nY, nProgressWidth, nHeight);
+ }
+
+ gtk_render_frame(mpProgressBarTroughStyle, cr, nX, nY, nWidth, nHeight);
+
+ break;
+ }
+ case RenderType::TabItem:
+ {
+ gint initial_gap(0);
+ gtk_style_context_get_style(mpNotebookStyle,
+ "initial-gap", &initial_gap,
+ nullptr);
+
+ nX += initial_gap/2;
+ nWidth -= initial_gap;
+ tools::Rectangle aRect(Point(nX, nY), Size(nWidth, nHeight));
+ render_common(mpNotebookHeaderTabsTabStyle, cr, aRect, flags);
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (pBgCssProvider)
+ {
+ gtk_style_context_remove_provider(context, GTK_STYLE_PROVIDER(pBgCssProvider));
+ }
+ aContextState.restore();
+
+ cairo_destroy(cr); // unref
+
+ if (!rControlRegion.IsEmpty())
+ mpFrame->damaged(rControlRegion.Left(), rControlRegion.Top(), rControlRegion.GetWidth(), rControlRegion.GetHeight());
+
+ return true;
+}
+
+static tools::Rectangle GetWidgetSize(const tools::Rectangle& rControlRegion, GtkWidget* widget)
+{
+ GtkRequisition aReq;
+ gtk_widget_get_preferred_size(widget, nullptr, &aReq);
+ tools::Long nHeight = std::max<tools::Long>(rControlRegion.GetHeight(), aReq.height);
+ return tools::Rectangle(rControlRegion.TopLeft(), Size(rControlRegion.GetWidth(), nHeight));
+}
+
+static tools::Rectangle AdjustRectForTextBordersPadding(GtkStyleContext* pStyle, tools::Long nContentWidth, tools::Long nContentHeight, const tools::Rectangle& rControlRegion)
+{
+ GtkBorder border;
+ style_context_get_border(pStyle, &border);
+
+ GtkBorder padding;
+ style_context_get_padding(pStyle, &padding);
+
+ gint nWidgetHeight = nContentHeight + padding.top + padding.bottom + border.top + border.bottom;
+ nWidgetHeight = std::max(std::max<gint>(nWidgetHeight, rControlRegion.GetHeight()), 34);
+
+ gint nWidgetWidth = nContentWidth + padding.left + padding.right + border.left + border.right;
+ nWidgetWidth = std::max<gint>(nWidgetWidth, rControlRegion.GetWidth());
+
+ tools::Rectangle aEditRect(rControlRegion.TopLeft(), Size(nWidgetWidth, nWidgetHeight));
+
+ return aEditRect;
+}
+
+bool GtkSalGraphics::getNativeControlRegion( ControlType nType, ControlPart nPart, const tools::Rectangle& rControlRegion, ControlState,
+ const ImplControlValue& rValue, const OUString&,
+ tools::Rectangle &rNativeBoundingRegion, tools::Rectangle &rNativeContentRegion )
+{
+ /* TODO: all this functions needs improvements */
+ tools::Rectangle aEditRect = rControlRegion;
+ gint indicator_size, indicator_spacing;
+
+ if(((nType == ControlType::Checkbox) || (nType == ControlType::Radiobutton)) &&
+ nPart == ControlPart::Entire)
+ {
+ rNativeBoundingRegion = rControlRegion;
+
+ GtkStyleContext *pButtonStyle = (nType == ControlType::Checkbox) ? mpCheckButtonCheckStyle : mpRadioButtonRadioStyle;
+
+
+ gtk_style_context_get_style( pButtonStyle,
+ "indicator-size", &indicator_size,
+ "indicator-spacing", &indicator_spacing,
+ nullptr );
+
+ GtkBorder border;
+ style_context_get_border(pButtonStyle, &border);
+
+ GtkBorder padding;
+ style_context_get_padding(pButtonStyle, &padding);
+
+
+ indicator_size += 2*indicator_spacing + border.left + padding.left + border.right + padding.right;
+ tools::Rectangle aIndicatorRect( Point( 0,
+ (rControlRegion.GetHeight()-indicator_size)/2),
+ Size( indicator_size, indicator_size ) );
+ rNativeContentRegion = aIndicatorRect;
+
+ return true;
+ }
+ else if( nType == ControlType::MenuPopup)
+ {
+ if ((nPart == ControlPart::MenuItemCheckMark) ||
+ (nPart == ControlPart::MenuItemRadioMark) )
+ {
+ indicator_size = 0;
+
+ GtkStyleContext *pMenuItemStyle = (nPart == ControlPart::MenuItemCheckMark ) ? mpCheckMenuItemCheckStyle
+ : mpRadioMenuItemRadioStyle;
+
+ gtk_style_context_get_style( pMenuItemStyle,
+ "indicator-size", &indicator_size,
+ nullptr );
+
+ gint point = MAX(0, rControlRegion.GetHeight() - indicator_size);
+ aEditRect = tools::Rectangle( Point( 0, point / 2),
+ Size( indicator_size, indicator_size ) );
+ }
+ else if (nPart == ControlPart::Separator)
+ {
+ gint separator_height, separator_width, wide_separators;
+
+ gtk_style_context_get_style (mpSeparatorMenuItemSeparatorStyle,
+ "wide-separators", &wide_separators,
+ "separator-width", &separator_width,
+ "separator-height", &separator_height,
+ nullptr);
+
+ aEditRect = tools::Rectangle( aEditRect.TopLeft(),
+ Size( aEditRect.GetWidth(), wide_separators ? separator_height : 1 ) );
+ }
+ else if (nPart == ControlPart::SubmenuArrow)
+ {
+ gfloat arrow_size = getArrowSize(mpMenuItemArrowStyle);
+ aEditRect = tools::Rectangle( aEditRect.TopLeft(),
+ Size( arrow_size, arrow_size ) );
+ }
+ }
+ else if ( (nType==ControlType::Scrollbar) &&
+ ((nPart==ControlPart::ButtonLeft) || (nPart==ControlPart::ButtonRight) ||
+ (nPart==ControlPart::ButtonUp) || (nPart==ControlPart::ButtonDown) ) )
+ {
+ rNativeBoundingRegion = NWGetScrollButtonRect( nPart, rControlRegion );
+ rNativeContentRegion = rNativeBoundingRegion;
+
+ if (!rNativeContentRegion.GetWidth())
+ rNativeContentRegion.SetRight( rNativeContentRegion.Left() + 1 );
+ if (!rNativeContentRegion.GetHeight())
+ rNativeContentRegion.SetBottom( rNativeContentRegion.Top() + 1 );
+
+ return true;
+ }
+ else if ( (nType==ControlType::Spinbox) &&
+ ((nPart==ControlPart::ButtonUp) || (nPart==ControlPart::ButtonDown) ||
+ (nPart==ControlPart::SubEdit)) )
+ {
+ tools::Rectangle aControlRegion(GetWidgetSize(rControlRegion, gSpinBox));
+ aEditRect = NWGetSpinButtonRect(nPart, aControlRegion);
+ }
+ else if ( (nType==ControlType::Combobox) &&
+ ((nPart==ControlPart::ButtonDown) || (nPart==ControlPart::SubEdit)) )
+ {
+ aEditRect = NWGetComboBoxButtonRect(nType, nPart, rControlRegion);
+ }
+ else if ( (nType==ControlType::Listbox) &&
+ ((nPart==ControlPart::ButtonDown) || (nPart==ControlPart::SubEdit)) )
+ {
+ aEditRect = NWGetComboBoxButtonRect(nType, nPart, rControlRegion);
+ }
+ else if (nType == ControlType::Editbox && nPart == ControlPart::Entire)
+ {
+ aEditRect = GetWidgetSize(rControlRegion, gEntryBox);
+ }
+ else if (nType == ControlType::Listbox && nPart == ControlPart::Entire)
+ {
+ aEditRect = GetWidgetSize(rControlRegion, gListBox);
+ }
+ else if (nType == ControlType::Combobox && nPart == ControlPart::Entire)
+ {
+ aEditRect = GetWidgetSize(rControlRegion, gComboBox);
+ }
+ else if (nType == ControlType::Spinbox && nPart == ControlPart::Entire)
+ {
+ aEditRect = GetWidgetSize(rControlRegion, gSpinBox);
+ }
+ else if (nType == ControlType::TabItem && nPart == ControlPart::Entire)
+ {
+ const TabitemValue& rTabitemValue = static_cast<const TabitemValue&>(rValue);
+ const tools::Rectangle& rTabitemRect = rTabitemValue.getContentRect();
+
+ aEditRect = AdjustRectForTextBordersPadding(mpNotebookHeaderTabsTabStyle, rTabitemRect.GetWidth(),
+ rTabitemRect.GetHeight(), rControlRegion);
+ }
+ else if (nType == ControlType::Frame && nPart == ControlPart::Border)
+ {
+ aEditRect = rControlRegion;
+
+ GtkBorder padding;
+ style_context_get_padding(mpFrameInStyle, &padding);
+
+ GtkBorder border;
+ style_context_get_border(mpFrameInStyle, &border);
+
+ int x1 = aEditRect.Left();
+ int y1 = aEditRect.Top();
+ int x2 = aEditRect.Right();
+ int y2 = aEditRect.Bottom();
+
+ rNativeBoundingRegion = aEditRect;
+ rNativeContentRegion = tools::Rectangle(x1 + (padding.left + border.left),
+ y1 + (padding.top + border.top),
+ x2 - (padding.right + border.right),
+ y2 - (padding.bottom + border.bottom));
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+
+ rNativeBoundingRegion = aEditRect;
+ rNativeContentRegion = rNativeBoundingRegion;
+
+ return true;
+}
+#endif
+
+/************************************************************************
+ * helper for GtkSalFrame
+ ************************************************************************/
+static ::Color getColor( const GdkRGBA& rCol )
+{
+ return ::Color( static_cast<int>(rCol.red * 0xFFFF) >> 8, static_cast<int>(rCol.green * 0xFFFF) >> 8, static_cast<int>(rCol.blue * 0xFFFF) >> 8 );
+}
+
+static ::Color style_context_get_background_color(GtkStyleContext* pStyle)
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkRGBA background_color;
+ gtk_style_context_get_background_color(pStyle, gtk_style_context_get_state(pStyle), &background_color);
+ return getColor(background_color);
+#else
+ cairo_surface_t *target = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
+ cairo_t* cr = cairo_create(target);
+ gtk_render_background(pStyle, cr, 0, 0, 1, 1);
+ cairo_destroy(cr);
+
+ cairo_surface_flush(target);
+ vcl::bitmap::lookup_table const & unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
+ unsigned char *data = cairo_image_surface_get_data(target);
+ sal_uInt8 a = data[SVP_CAIRO_ALPHA];
+ sal_uInt8 b = unpremultiply_table[a][data[SVP_CAIRO_BLUE]];
+ sal_uInt8 g = unpremultiply_table[a][data[SVP_CAIRO_GREEN]];
+ sal_uInt8 r = unpremultiply_table[a][data[SVP_CAIRO_RED]];
+ Color aColor(r, g, b);
+ cairo_surface_destroy(target);
+
+ return aColor;
+#endif
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+static vcl::Font getFont(GtkStyleContext* pStyle, const css::lang::Locale& rLocale)
+{
+ const PangoFontDescription* font = gtk_style_context_get_font(pStyle, gtk_style_context_get_state(pStyle));
+ return pango_to_vcl(font, rLocale);
+}
+#endif
+
+vcl::Font pango_to_vcl(const PangoFontDescription* font, const css::lang::Locale& rLocale)
+{
+ OString aFamily = pango_font_description_get_family( font );
+ PangoStyle eStyle = pango_font_description_get_style( font );
+ PangoWeight eWeight = pango_font_description_get_weight( font );
+ PangoStretch eStretch = pango_font_description_get_stretch( font );
+
+ psp::FastPrintFontInfo aInfo;
+ // set family name
+ aInfo.m_aFamilyName = OStringToOUString( aFamily, RTL_TEXTENCODING_UTF8 );
+ // set italic
+ switch( eStyle )
+ {
+ case PANGO_STYLE_NORMAL: aInfo.m_eItalic = ITALIC_NONE;break;
+ case PANGO_STYLE_ITALIC: aInfo.m_eItalic = ITALIC_NORMAL;break;
+ case PANGO_STYLE_OBLIQUE: aInfo.m_eItalic = ITALIC_OBLIQUE;break;
+ }
+ // set weight
+ if( eWeight <= PANGO_WEIGHT_ULTRALIGHT )
+ aInfo.m_eWeight = WEIGHT_ULTRALIGHT;
+ else if( eWeight <= PANGO_WEIGHT_LIGHT )
+ aInfo.m_eWeight = WEIGHT_LIGHT;
+ else if( eWeight <= PANGO_WEIGHT_NORMAL )
+ aInfo.m_eWeight = WEIGHT_NORMAL;
+ else if( eWeight <= PANGO_WEIGHT_BOLD )
+ aInfo.m_eWeight = WEIGHT_BOLD;
+ else
+ aInfo.m_eWeight = WEIGHT_ULTRABOLD;
+ // set width
+ switch( eStretch )
+ {
+ case PANGO_STRETCH_ULTRA_CONDENSED: aInfo.m_eWidth = WIDTH_ULTRA_CONDENSED;break;
+ case PANGO_STRETCH_EXTRA_CONDENSED: aInfo.m_eWidth = WIDTH_EXTRA_CONDENSED;break;
+ case PANGO_STRETCH_CONDENSED: aInfo.m_eWidth = WIDTH_CONDENSED;break;
+ case PANGO_STRETCH_SEMI_CONDENSED: aInfo.m_eWidth = WIDTH_SEMI_CONDENSED;break;
+ case PANGO_STRETCH_NORMAL: aInfo.m_eWidth = WIDTH_NORMAL;break;
+ case PANGO_STRETCH_SEMI_EXPANDED: aInfo.m_eWidth = WIDTH_SEMI_EXPANDED;break;
+ case PANGO_STRETCH_EXPANDED: aInfo.m_eWidth = WIDTH_EXPANDED;break;
+ case PANGO_STRETCH_EXTRA_EXPANDED: aInfo.m_eWidth = WIDTH_EXTRA_EXPANDED;break;
+ case PANGO_STRETCH_ULTRA_EXPANDED: aInfo.m_eWidth = WIDTH_ULTRA_EXPANDED;break;
+ }
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.gtk3", "font name BEFORE system match: \""
+ << aFamily << "\".");
+#endif
+
+ // match font to e.g. resolve "Sans"
+ psp::PrintFontManager::get().matchFont(aInfo, rLocale);
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO("vcl.gtk3", "font match "
+ << (aInfo.m_nID != 0 ? "succeeded" : "failed")
+ << ", name AFTER: \""
+ << aInfo.m_aFamilyName
+ << "\".");
+#endif
+
+ int nPangoHeight = pango_font_description_get_size(font) / PANGO_SCALE;
+
+ if (pango_font_description_get_size_is_absolute(font))
+ {
+ const sal_Int32 nDPIY = 96;
+ nPangoHeight = nPangoHeight * 72;
+ nPangoHeight = nPangoHeight + nDPIY / 2;
+ nPangoHeight = nPangoHeight / nDPIY;
+ }
+
+ vcl::Font aFont(aInfo.m_aFamilyName, Size(0, nPangoHeight));
+ if( aInfo.m_eWeight != WEIGHT_DONTKNOW )
+ aFont.SetWeight( aInfo.m_eWeight );
+ if( aInfo.m_eWidth != WIDTH_DONTKNOW )
+ aFont.SetWidthType( aInfo.m_eWidth );
+ if( aInfo.m_eItalic != ITALIC_DONTKNOW )
+ aFont.SetItalic( aInfo.m_eItalic );
+ if( aInfo.m_ePitch != PITCH_DONTKNOW )
+ aFont.SetPitch( aInfo.m_ePitch );
+ return aFont;
+}
+
+bool GtkSalGraphics::updateSettings(AllSettings& rSettings)
+{
+ GtkWidget* pTopLevel = widget_get_toplevel(mpWindow);
+ GtkStyleContext* pStyle = gtk_widget_get_style_context(pTopLevel);
+ StyleContextSave aContextState;
+ aContextState.save(pStyle);
+ GtkSettings* pSettings = gtk_widget_get_settings(pTopLevel);
+ StyleSettings aStyleSet = rSettings.GetStyleSettings();
+
+ // text colors
+ GdkRGBA text_color;
+ style_context_set_state(pStyle, GTK_STATE_FLAG_NORMAL);
+ style_context_get_color(pStyle, &text_color);
+ ::Color aTextColor = getColor( text_color );
+ aStyleSet.SetDialogTextColor( aTextColor );
+ aStyleSet.SetButtonTextColor( aTextColor );
+ aStyleSet.SetDefaultActionButtonTextColor(aTextColor);
+ aStyleSet.SetActionButtonTextColor(aTextColor);
+ aStyleSet.SetRadioCheckTextColor( aTextColor );
+ aStyleSet.SetGroupTextColor( aTextColor );
+ aStyleSet.SetLabelTextColor( aTextColor );
+ aStyleSet.SetWindowTextColor( aTextColor );
+ aStyleSet.SetFieldTextColor( aTextColor );
+
+ // background colors
+ ::Color aBackColor = style_context_get_background_color(pStyle);
+ aStyleSet.BatchSetBackgrounds( aBackColor );
+
+ // UI font
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gchar* pFontname = nullptr;
+ g_object_get(pSettings, "gtk-font-name", &pFontname, nullptr);
+ PangoFontDescription* pFontDesc = pango_font_description_from_string(pFontname);
+ vcl::Font aFont(pango_to_vcl(pFontDesc, rSettings.GetUILanguageTag().getLocale()));
+ pango_font_description_free(pFontDesc);
+#else
+ vcl::Font aFont(getFont(pStyle, rSettings.GetUILanguageTag().getLocale()));
+#endif
+
+ aStyleSet.BatchSetFonts( aFont, aFont);
+
+ aFont.SetWeight( WEIGHT_BOLD );
+ aStyleSet.SetTitleFont( aFont );
+ aStyleSet.SetFloatTitleFont( aFont );
+
+ // mouse over text colors
+ style_context_set_state(pStyle, GTK_STATE_FLAG_PRELIGHT);
+ style_context_get_color(pStyle, &text_color);
+ aTextColor = getColor(text_color);
+ aStyleSet.SetDefaultButtonTextColor(aTextColor);
+ aStyleSet.SetDefaultButtonRolloverTextColor(aTextColor);
+ aStyleSet.SetDefaultButtonPressedRolloverTextColor(aTextColor);
+ aStyleSet.SetButtonRolloverTextColor(aTextColor);
+ aStyleSet.SetDefaultActionButtonRolloverTextColor(aTextColor);
+ aStyleSet.SetDefaultActionButtonPressedRolloverTextColor(aTextColor);
+ aStyleSet.SetActionButtonRolloverTextColor(aTextColor);
+ aStyleSet.SetActionButtonPressedRolloverTextColor(aTextColor);
+ aStyleSet.SetFlatButtonTextColor(aTextColor);
+ aStyleSet.SetFlatButtonPressedRolloverTextColor(aTextColor);
+ aStyleSet.SetFlatButtonRolloverTextColor(aTextColor);
+ aStyleSet.SetFieldRolloverTextColor(aTextColor);
+
+ aContextState.restore();
+
+ // button mouse over colors
+ {
+ GdkRGBA normal_button_rollover_text_color, pressed_button_rollover_text_color;
+ aContextState.save(mpButtonStyle);
+ style_context_set_state(mpButtonStyle, GTK_STATE_FLAG_PRELIGHT);
+ style_context_get_color(mpButtonStyle, &normal_button_rollover_text_color);
+ aTextColor = getColor(normal_button_rollover_text_color);
+ aStyleSet.SetButtonRolloverTextColor( aTextColor );
+ style_context_set_state(mpButtonStyle, static_cast<GtkStateFlags>(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE));
+ style_context_get_color(mpButtonStyle, &pressed_button_rollover_text_color);
+ aTextColor = getColor(pressed_button_rollover_text_color);
+ style_context_set_state(mpButtonStyle, GTK_STATE_FLAG_NORMAL);
+ aStyleSet.SetButtonPressedRolloverTextColor( aTextColor );
+ aContextState.restore();
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // tooltip colors
+ {
+ GtkWidgetPath *pCPath = gtk_widget_path_new();
+ guint pos = gtk_widget_path_append_type(pCPath, GTK_TYPE_WINDOW);
+ gtk_widget_path_iter_add_class(pCPath, pos, GTK_STYLE_CLASS_TOOLTIP);
+ pos = gtk_widget_path_append_type (pCPath, GTK_TYPE_LABEL);
+ gtk_widget_path_iter_add_class(pCPath, pos, GTK_STYLE_CLASS_LABEL);
+ GtkStyleContext *pCStyle = makeContext (pCPath, nullptr);
+ aContextState.save(pCStyle);
+
+ GdkRGBA tooltip_fg_color;
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_NORMAL);
+ style_context_get_color(pCStyle, &tooltip_fg_color);
+ ::Color aTooltipBgColor = style_context_get_background_color(pCStyle);
+
+ aContextState.restore();
+ g_object_unref( pCStyle );
+
+ aStyleSet.SetHelpColor(aTooltipBgColor);
+ aStyleSet.SetHelpTextColor( getColor( tooltip_fg_color ));
+ }
+#endif
+
+ GdkRGBA color;
+ {
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ // construct style context for text view
+ GtkWidgetPath *pCPath = gtk_widget_path_new();
+ gtk_widget_path_append_type( pCPath, GTK_TYPE_TEXT_VIEW );
+ gtk_widget_path_iter_add_class( pCPath, -1, GTK_STYLE_CLASS_VIEW );
+ GtkStyleContext *pCStyle = makeContext( pCPath, nullptr );
+#else
+ GtkStyleContext *pCStyle = gtk_widget_get_style_context(gTextView);
+#endif
+ aContextState.save(pCStyle);
+
+ // highlighting colors
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_SELECTED);
+ ::Color aHighlightColor = style_context_get_background_color(pCStyle);
+ style_context_get_color(pCStyle, &text_color);
+ ::Color aHighlightTextColor = getColor( text_color );
+ aStyleSet.SetHighlightColor( aHighlightColor );
+ aStyleSet.SetHighlightTextColor( aHighlightTextColor );
+ // make active like highlight, except with a small contrast. Note, see
+ // a GtkListBoxRow in a GtkStackSidebar for a gtk widget with a
+ // difference between highlighted and highlighted with focus.
+ aHighlightColor.IncreaseLuminance(16);
+ aStyleSet.SetActiveColor( aHighlightColor );
+ aStyleSet.SetActiveTextColor( aHighlightTextColor );
+
+ // warning color
+ GdkRGBA warning_color;
+ if (gtk_style_context_lookup_color(pCStyle, "warning_color", &warning_color))
+ aStyleSet.SetWarningColor(getColor(warning_color));
+
+ // field background color
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_NORMAL);
+ ::Color aBackFieldColor = style_context_get_background_color(pCStyle);
+ aStyleSet.SetFieldColor( aBackFieldColor );
+ // This baby is the default page/paper color
+ aStyleSet.SetWindowColor( aBackFieldColor );
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ double caretAspectRatio = 0.04f;
+ g_object_get(pSettings, "gtk-cursor-aspect-ratio", &caretAspectRatio, nullptr);
+#else
+ // Cursor width
+ gfloat caretAspectRatio = 0.04f;
+ gtk_style_context_get_style( pCStyle, "cursor-aspect-ratio", &caretAspectRatio, nullptr );
+#endif
+ // Assume 20px tall for the ratio computation, which should give reasonable results
+ aStyleSet.SetCursorSize(20 * caretAspectRatio + 1);
+
+ // Dark shadow color
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_INSENSITIVE);
+ style_context_get_color(pCStyle, &color);
+ ::Color aDarkShadowColor = getColor( color );
+ aStyleSet.SetDarkShadowColor( aDarkShadowColor );
+
+ ::Color aShadowColor(aBackColor);
+ if (aDarkShadowColor.GetLuminance() > aBackColor.GetLuminance())
+ aShadowColor.IncreaseLuminance(64);
+ else
+ aShadowColor.DecreaseLuminance(64);
+ aStyleSet.SetShadowColor(aShadowColor);
+
+ aContextState.restore();
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ g_object_unref( pCStyle );
+#endif
+
+ // Tab colors
+ aStyleSet.SetActiveTabColor( aBackFieldColor ); // same as the window color.
+ aStyleSet.SetInactiveTabColor( aBackColor );
+ }
+
+ // menu disabled entries handling
+ aStyleSet.SetSkipDisabledInMenus( true );
+ aStyleSet.SetPreferredContextMenuShortcuts( false );
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ aContextState.save(mpMenuItemLabelStyle);
+
+ // menu colors
+ style_context_set_state(mpMenuStyle, GTK_STATE_FLAG_NORMAL);
+ aBackColor = style_context_get_background_color(mpMenuStyle);
+ aStyleSet.SetMenuColor( aBackColor );
+
+ // menu bar
+ style_context_set_state(mpMenuBarStyle, GTK_STATE_FLAG_NORMAL);
+ aBackColor = style_context_get_background_color(mpMenuBarStyle);
+ aStyleSet.SetMenuBarColor( aBackColor );
+ aStyleSet.SetMenuBarRolloverColor( aBackColor );
+
+ style_context_set_state(mpMenuBarItemStyle, GTK_STATE_FLAG_NORMAL);
+ style_context_get_color(mpMenuBarItemStyle, &text_color);
+ aTextColor = aStyleSet.GetPersonaMenuBarTextColor().value_or( getColor( text_color ) );
+ aStyleSet.SetMenuBarTextColor( aTextColor );
+ aStyleSet.SetMenuBarRolloverTextColor( aTextColor );
+
+ style_context_set_state(mpMenuBarItemStyle, GTK_STATE_FLAG_PRELIGHT);
+ style_context_get_color(mpMenuBarItemStyle, &text_color);
+ aTextColor = aStyleSet.GetPersonaMenuBarTextColor().value_or( getColor( text_color ) );
+ aStyleSet.SetMenuBarHighlightTextColor( aTextColor );
+
+ // menu items
+ style_context_set_state(mpMenuItemLabelStyle, GTK_STATE_FLAG_NORMAL);
+ style_context_get_color(mpMenuItemLabelStyle, &color);
+ aTextColor = getColor(color);
+ aStyleSet.SetMenuTextColor(aTextColor);
+
+ style_context_set_state(mpMenuItemLabelStyle, GTK_STATE_FLAG_PRELIGHT);
+ ::Color aHighlightColor = style_context_get_background_color(mpMenuItemLabelStyle);
+ aStyleSet.SetMenuHighlightColor( aHighlightColor );
+
+ style_context_get_color(mpMenuItemLabelStyle, &color);
+ ::Color aHighlightTextColor = getColor( color );
+ aStyleSet.SetMenuHighlightTextColor( aHighlightTextColor );
+
+ aContextState.restore();
+#endif
+
+ // hyperlink colors
+ aContextState.save(mpLinkButtonStyle);
+ style_context_set_state(mpLinkButtonStyle, GTK_STATE_FLAG_LINK);
+ style_context_get_color(mpLinkButtonStyle, &text_color);
+ aStyleSet.SetLinkColor(getColor(text_color));
+ style_context_set_state(mpLinkButtonStyle, GTK_STATE_FLAG_VISITED);
+ style_context_get_color(mpLinkButtonStyle, &text_color);
+ aStyleSet.SetVisitedLinkColor(getColor(text_color));
+ aContextState.restore();
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ {
+ GtkStyleContext *pCStyle = mpNotebookHeaderTabsTabLabelStyle;
+ aContextState.save(pCStyle);
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_NORMAL);
+ style_context_get_color(pCStyle, &text_color);
+ aTextColor = getColor( text_color );
+ aStyleSet.SetTabTextColor(aTextColor);
+ aStyleSet.SetTabFont(getFont(mpNotebookHeaderTabsTabLabelStyle, rSettings.GetUILanguageTag().getLocale()));
+ aContextState.restore();
+ }
+
+ {
+ GtkStyleContext *pCStyle = mpToolButtonStyle;
+ aContextState.save(pCStyle);
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_NORMAL);
+ style_context_get_color(pCStyle, &text_color);
+ aTextColor = getColor( text_color );
+ aStyleSet.SetToolTextColor(aTextColor);
+ aStyleSet.SetToolFont(getFont(mpToolButtonStyle, rSettings.GetUILanguageTag().getLocale()));
+ aContextState.restore();
+ }
+
+ // mouse over text colors
+ {
+ GtkStyleContext *pCStyle = mpNotebookHeaderTabsTabHoverLabelStyle;
+ aContextState.save(pCStyle);
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_PRELIGHT);
+ style_context_get_color(pCStyle, &text_color);
+ aTextColor = getColor( text_color );
+ aStyleSet.SetTabRolloverTextColor(aTextColor);
+ aContextState.restore();
+ }
+
+ {
+ GtkStyleContext *pCStyle = mpNotebookHeaderTabsTabActiveLabelStyle;
+ aContextState.save(pCStyle);
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_CHECKED);
+ style_context_get_color(pCStyle, &text_color);
+ aTextColor = getColor( text_color );
+ aStyleSet.SetTabHighlightTextColor(aTextColor);
+ aContextState.restore();
+ }
+#endif
+
+ // get cursor blink time
+ gboolean blink = false;
+
+ g_object_get( pSettings, "gtk-cursor-blink", &blink, nullptr );
+ if( blink )
+ {
+ gint blink_time = static_cast<gint>(STYLE_CURSOR_NOBLINKTIME);
+ g_object_get( pSettings, "gtk-cursor-blink-time", &blink_time, nullptr );
+ // set the blink_time if there is a setting and it is reasonable
+ // else leave the default value
+ if( blink_time > 100 )
+ aStyleSet.SetCursorBlinkTime( blink_time/2 );
+ }
+ else
+ aStyleSet.SetCursorBlinkTime( STYLE_CURSOR_NOBLINKTIME );
+
+ MouseSettings aMouseSettings = rSettings.GetMouseSettings();
+ int iDoubleClickTime, iDoubleClickDistance, iDragThreshold;
+ static const int MENU_POPUP_DELAY = 225;
+ g_object_get( pSettings,
+ "gtk-double-click-time", &iDoubleClickTime,
+ "gtk-double-click-distance", &iDoubleClickDistance,
+ "gtk-dnd-drag-threshold", &iDragThreshold,
+ nullptr );
+ aMouseSettings.SetDoubleClickTime( iDoubleClickTime );
+ aMouseSettings.SetDoubleClickWidth( iDoubleClickDistance );
+ aMouseSettings.SetDoubleClickHeight( iDoubleClickDistance );
+ aMouseSettings.SetStartDragWidth( iDragThreshold );
+ aMouseSettings.SetStartDragHeight( iDragThreshold );
+ aMouseSettings.SetMenuDelay( MENU_POPUP_DELAY );
+ rSettings.SetMouseSettings( aMouseSettings );
+
+ gboolean primarybuttonwarps = false;
+ g_object_get( pSettings,
+ "gtk-primary-button-warps-slider", &primarybuttonwarps,
+ nullptr );
+ aStyleSet.SetPreferredUseImagesInMenus(false);
+ aStyleSet.SetPrimaryButtonWarpsSlider(primarybuttonwarps);
+
+ // set scrollbar settings
+ gint min_slider_length = 21;
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ GtkRequisition natural_size;
+ gtk_widget_get_preferred_size(gHScrollbar, nullptr, &natural_size);
+ aStyleSet.SetScrollBarSize(natural_size.height);
+#else
+ // Grab some button style attributes
+ Size aSize;
+ QuerySize(mpHScrollbarStyle, aSize);
+ QuerySize(mpHScrollbarContentsStyle, aSize);
+ QuerySize(mpHScrollbarTroughStyle, aSize);
+ QuerySize(mpHScrollbarSliderStyle, aSize);
+
+ gboolean has_forward, has_forward2, has_backward, has_backward2;
+ gtk_style_context_get_style(mpHScrollbarStyle,
+ "has-forward-stepper", &has_forward,
+ "has-secondary-forward-stepper", &has_forward2,
+ "has-backward-stepper", &has_backward,
+ "has-secondary-backward-stepper", &has_backward2, nullptr);
+ if (has_forward || has_backward || has_forward2 || has_backward2)
+ QuerySize(mpHScrollbarButtonStyle, aSize);
+
+ aStyleSet.SetScrollBarSize(aSize.Height());
+
+ gtk_style_context_get(mpVScrollbarSliderStyle, gtk_style_context_get_state(mpVScrollbarSliderStyle),
+ "min-height", &min_slider_length,
+ nullptr);
+#endif
+ aStyleSet.SetMinThumbSize(min_slider_length);
+
+ // preferred icon style
+ gchar* pIconThemeName = nullptr;
+ gboolean bDarkIconTheme = false;
+ g_object_get(pSettings, "gtk-icon-theme-name", &pIconThemeName,
+ "gtk-application-prefer-dark-theme", &bDarkIconTheme,
+ nullptr );
+ OUString sIconThemeName(OUString::createFromAscii(pIconThemeName));
+ aStyleSet.SetPreferredIconTheme(sIconThemeName, bDarkIconTheme);
+ g_free( pIconThemeName );
+
+ aStyleSet.SetToolbarIconSize( ToolbarIconSize::Large );
+
+ gchar* pThemeName = nullptr;
+ g_object_get( pSettings, "gtk-theme-name", &pThemeName, nullptr );
+ SAL_INFO("vcl.gtk3", "Theme name is \""
+ << pThemeName
+ << "\".");
+ // High contrast
+ aStyleSet.SetHighContrastMode(g_strcmp0(pThemeName, "HighContrast") == 0);
+ g_free(pThemeName);
+
+ // finally update the collected settings
+ rSettings.SetStyleSettings( aStyleSet );
+
+ return true;
+}
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+bool GtkSalGraphics::isNativeControlSupported( ControlType nType, ControlPart nPart )
+{
+ switch(nType)
+ {
+ case ControlType::Pushbutton:
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ case ControlType::Progress:
+ case ControlType::ListNode:
+ case ControlType::ListNet:
+ if (nPart==ControlPart::Entire || nPart == ControlPart::Focus)
+ return true;
+ break;
+
+ case ControlType::Scrollbar:
+ if(nPart==ControlPart::DrawBackgroundHorz || nPart==ControlPart::DrawBackgroundVert ||
+ nPart==ControlPart::Entire || nPart==ControlPart::HasThreeButtons)
+ return true;
+ break;
+
+ case ControlType::Editbox:
+ case ControlType::MultilineEditbox:
+ if (nPart==ControlPart::Entire || nPart==ControlPart::HasBackgroundTexture)
+ return true;
+ break;
+
+ case ControlType::Combobox:
+ if (nPart==ControlPart::Entire || nPart==ControlPart::HasBackgroundTexture || nPart == ControlPart::AllButtons)
+ return true;
+ break;
+
+ case ControlType::Spinbox:
+ if (nPart==ControlPart::Entire || nPart==ControlPart::HasBackgroundTexture || nPart == ControlPart::AllButtons || nPart == ControlPart::ButtonUp || nPart == ControlPart::ButtonDown)
+ return true;
+ break;
+
+ case ControlType::SpinButtons:
+ if (nPart==ControlPart::Entire || nPart==ControlPart::AllButtons)
+ return true;
+ break;
+
+ case ControlType::Frame:
+ case ControlType::WindowBackground:
+ return true;
+
+ case ControlType::TabItem:
+ case ControlType::TabHeader:
+ case ControlType::TabPane:
+ case ControlType::TabBody:
+ if(nPart==ControlPart::Entire || nPart==ControlPart::TabsDrawRtl)
+ return true;
+ break;
+
+ case ControlType::Listbox:
+ if (nPart==ControlPart::Entire || nPart==ControlPart::ListboxWindow || nPart==ControlPart::HasBackgroundTexture || nPart == ControlPart::Focus)
+ return true;
+ break;
+
+ case ControlType::Toolbar:
+ if( nPart==ControlPart::Entire
+// || nPart==ControlPart::DrawBackgroundHorz
+// || nPart==ControlPart::DrawBackgroundVert
+// || nPart==ControlPart::ThumbHorz
+// || nPart==ControlPart::ThumbVert
+ || nPart==ControlPart::Button
+// || nPart==ControlPart::SeparatorHorz
+ || nPart==ControlPart::SeparatorVert
+ )
+ return true;
+ break;
+
+ case ControlType::Menubar:
+ if (nPart==ControlPart::Entire || nPart==ControlPart::MenuItem)
+ return true;
+ break;
+
+ case ControlType::MenuPopup:
+ if (nPart==ControlPart::Entire
+ || nPart==ControlPart::MenuItem
+ || nPart==ControlPart::MenuItemCheckMark
+ || nPart==ControlPart::MenuItemRadioMark
+ || nPart==ControlPart::Separator
+ || nPart==ControlPart::SubmenuArrow
+ )
+ return true;
+ break;
+
+// case ControlType::Slider:
+// if(nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea)
+// return true;
+// break;
+
+ case ControlType::Fixedline:
+ if (nPart == ControlPart::SeparatorVert || nPart == ControlPart::SeparatorHorz)
+ return true;
+ break;
+
+ case ControlType::ListHeader:
+ if (nPart == ControlPart::Button || nPart == ControlPart::Arrow)
+ return true;
+ break;
+ default: break;
+ }
+
+ SAL_INFO("vcl.gtk", "Unhandled is native supported for Type:" << static_cast<int>(nType) << ", Part" << static_cast<int>(nPart));
+ return false;
+}
+#endif
+
+#if ENABLE_CAIRO_CANVAS
+
+bool GtkSalGraphics::SupportsCairo() const
+{
+ return true;
+}
+
+cairo::SurfaceSharedPtr GtkSalGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const
+{
+ return std::make_shared<cairo::Gtk3Surface>(rSurface);
+}
+
+cairo::SurfaceSharedPtr GtkSalGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int x, int y, int width, int height) const
+{
+ return std::make_shared<cairo::Gtk3Surface>(this, x, y, width, height);
+}
+
+#endif
+
+void GtkSalGraphics::WidgetQueueDraw() const
+{
+ //request gtk to sync the entire contents
+ mpFrame->queue_draw();
+}
+
+namespace {
+
+void getStyleContext(GtkStyleContext** style, GtkWidget* widget)
+{
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_fixed_put(GTK_FIXED(gDumbContainer), widget, 0, 0);
+#else
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), widget);
+#endif
+ *style = gtk_widget_get_style_context(widget);
+ g_object_ref(*style);
+}
+
+}
+
+void GtkSalData::initNWF()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maNWFData.mbFlatMenu = true;
+ pSVData->maNWFData.mbDockingAreaAvoidTBFrames = true;
+ pSVData->maNWFData.mbCanDrawWidgetAnySize = true;
+ pSVData->maNWFData.mbDDListBoxNoTextArea = true;
+ pSVData->maNWFData.mbNoFocusRects = true;
+ pSVData->maNWFData.mbNoFocusRectsForFlatButtons = true;
+ pSVData->maNWFData.mbAutoAccel = true;
+
+#if defined(GDK_WINDOWING_WAYLAND)
+ //gnome#768128 for the car crash that is wayland
+ //and floating dockable toolbars
+ GdkDisplay *pDisplay = gdk_display_get_default();
+ if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
+ pSVData->maNWFData.mbCanDetermineWindowPosition = false;
+#endif
+}
+
+void GtkSalData::deInitNWF()
+{
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ if (gCacheWindow)
+ gtk_widget_destroy(gCacheWindow);
+#endif
+}
+
+GtkSalGraphics::GtkSalGraphics( GtkSalFrame *pFrame, GtkWidget *pWindow )
+ : mpFrame( pFrame ),
+ mpWindow( pWindow )
+{
+ if (style_loaded)
+ return;
+
+ style_loaded = true;
+
+ /* Load the GtkStyleContexts, it might be a bit slow, but usually,
+ * gtk apps create a lot of widgets at startup, so, it shouldn't be
+ * too slow */
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gCacheWindow = gtk_window_new();
+#else
+ gCacheWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+#endif
+ gDumbContainer = gtk_fixed_new();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_window_set_child(GTK_WINDOW(gCacheWindow), gDumbContainer);
+#else
+ gtk_container_add(GTK_CONTAINER(gCacheWindow), gDumbContainer);
+#endif
+ gtk_widget_realize(gDumbContainer);
+ gtk_widget_realize(gCacheWindow);
+
+ gEntryBox = gtk_entry_new();
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gtk_fixed_put(GTK_FIXED(gDumbContainer), gEntryBox, 0, 0);
+#else
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gEntryBox);
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mpWindowStyle = createStyleContext(GtkControlPart::ToplevelWindow);
+ mpEntryStyle = createStyleContext(GtkControlPart::Entry);
+#else
+ mpWindowStyle = gtk_widget_get_style_context(gCacheWindow);
+ getStyleContext(&mpEntryStyle, gtk_entry_new());
+#endif
+
+ getStyleContext(&mpTextViewStyle, gtk_text_view_new());
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mpButtonStyle = createStyleContext(GtkControlPart::Button);
+ mpLinkButtonStyle = createStyleContext(GtkControlPart::LinkButton);
+#else
+ getStyleContext(&mpButtonStyle, gtk_button_new());
+ getStyleContext(&mpLinkButtonStyle, gtk_link_button_new("https://www.libreoffice.org"));
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GtkWidget* pToolbar = gtk_toolbar_new();
+ mpToolbarStyle = gtk_widget_get_style_context(pToolbar);
+ gtk_style_context_add_class(mpToolbarStyle, GTK_STYLE_CLASS_TOOLBAR);
+
+ GtkToolItem *item = gtk_separator_tool_item_new();
+ gtk_toolbar_insert(GTK_TOOLBAR(pToolbar), item, -1);
+ mpToolbarSeparatorStyle = gtk_widget_get_style_context(GTK_WIDGET(item));
+
+ GtkWidget *pButton = gtk_button_new();
+ item = gtk_tool_button_new(pButton, nullptr);
+ gtk_toolbar_insert(GTK_TOOLBAR(pToolbar), item, -1);
+ mpToolButtonStyle = gtk_widget_get_style_context(GTK_WIDGET(pButton));
+#endif
+
+#if GTK_CHECK_VERSION(4, 0, 0)
+ gVScrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, nullptr);
+ gtk_fixed_put(GTK_FIXED(gDumbContainer), gVScrollbar, 0, 0);
+ gtk_widget_show(gVScrollbar);
+ mpVScrollbarStyle = gtk_widget_get_style_context(gVScrollbar);
+
+ gHScrollbar = gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, nullptr);
+ gtk_fixed_put(GTK_FIXED(gDumbContainer), gHScrollbar, 0, 0);
+ gtk_widget_show(gHScrollbar);
+ mpHScrollbarStyle = gtk_widget_get_style_context(gHScrollbar);
+
+ gTextView = gtk_text_view_new();
+ gtk_fixed_put(GTK_FIXED(gDumbContainer), gTextView, 0, 0);
+ gtk_widget_show(gTextView);
+#else
+ mpVScrollbarStyle = createStyleContext(GtkControlPart::ScrollbarVertical);
+ mpVScrollbarContentsStyle = createStyleContext(GtkControlPart::ScrollbarVerticalContents);
+ mpVScrollbarTroughStyle = createStyleContext(GtkControlPart::ScrollbarVerticalTrough);
+ mpVScrollbarSliderStyle = createStyleContext(GtkControlPart::ScrollbarVerticalSlider);
+ mpVScrollbarButtonStyle = createStyleContext(GtkControlPart::ScrollbarVerticalButton);
+ mpHScrollbarStyle = createStyleContext(GtkControlPart::ScrollbarHorizontal);
+ mpHScrollbarContentsStyle = createStyleContext(GtkControlPart::ScrollbarHorizontalContents);
+ mpHScrollbarTroughStyle = createStyleContext(GtkControlPart::ScrollbarHorizontalTrough);
+ mpHScrollbarSliderStyle = createStyleContext(GtkControlPart::ScrollbarHorizontalSlider);
+ mpHScrollbarButtonStyle = createStyleContext(GtkControlPart::ScrollbarHorizontalButton);
+#endif
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ mpCheckButtonStyle = createStyleContext(GtkControlPart::CheckButton);
+ mpCheckButtonCheckStyle = createStyleContext(GtkControlPart::CheckButtonCheck);
+
+ mpRadioButtonStyle = createStyleContext(GtkControlPart::RadioButton);
+ mpRadioButtonRadioStyle = createStyleContext(GtkControlPart::RadioButtonRadio);
+
+ /* Spinbutton */
+ gSpinBox = gtk_spin_button_new(nullptr, 0, 0);
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gSpinBox);
+ mpSpinStyle = createStyleContext(GtkControlPart::SpinButton);
+ mpSpinUpStyle = createStyleContext(GtkControlPart::SpinButtonUpButton);
+ mpSpinDownStyle = createStyleContext(GtkControlPart::SpinButtonDownButton);
+
+ /* NoteBook */
+ mpNotebookStyle = createStyleContext(GtkControlPart::Notebook);
+ mpNotebookStackStyle = createStyleContext(GtkControlPart::NotebookStack);
+ mpNotebookHeaderStyle = createStyleContext(GtkControlPart::NotebookHeader);
+ mpNotebookHeaderTabsStyle = createStyleContext(GtkControlPart::NotebookHeaderTabs);
+ mpNotebookHeaderTabsTabStyle = createStyleContext(GtkControlPart::NotebookHeaderTabsTab);
+ mpNotebookHeaderTabsTabLabelStyle = createStyleContext(GtkControlPart::NotebookHeaderTabsTabLabel);
+ mpNotebookHeaderTabsTabActiveLabelStyle = createStyleContext(GtkControlPart::NotebookHeaderTabsTabActiveLabel);
+ mpNotebookHeaderTabsTabHoverLabelStyle = createStyleContext(GtkControlPart::NotebookHeaderTabsTabHoverLabel);
+
+ /* Combobox */
+ gComboBox = gtk_combo_box_text_new_with_entry();
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gComboBox);
+ mpComboboxStyle = createStyleContext(GtkControlPart::Combobox);
+ mpComboboxBoxStyle = createStyleContext(GtkControlPart::ComboboxBox);
+ mpComboboxEntryStyle = createStyleContext(GtkControlPart::ComboboxBoxEntry);
+ mpComboboxButtonStyle = createStyleContext(GtkControlPart::ComboboxBoxButton);
+ mpComboboxButtonBoxStyle = createStyleContext(GtkControlPart::ComboboxBoxButtonBox);
+ mpComboboxButtonArrowStyle = createStyleContext(GtkControlPart::ComboboxBoxButtonBoxArrow);
+
+ /* Listbox */
+ gListBox = gtk_combo_box_text_new();
+ gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(gListBox), "sample");
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gListBox);
+ mpListboxStyle = createStyleContext(GtkControlPart::Listbox);
+ mpListboxBoxStyle = createStyleContext(GtkControlPart::ListboxBox);
+ mpListboxButtonStyle = createStyleContext(GtkControlPart::ListboxBoxButton);
+ mpListboxButtonBoxStyle = createStyleContext(GtkControlPart::ListboxBoxButtonBox);
+ mpListboxButtonArrowStyle = createStyleContext(GtkControlPart::ListboxBoxButtonBoxArrow);
+
+ mpMenuBarStyle = createStyleContext(GtkControlPart::MenuBar);
+ mpMenuBarItemStyle = createStyleContext(GtkControlPart::MenuBarItem);
+
+ /* Menu */
+ mpMenuWindowStyle = createStyleContext(GtkControlPart::MenuWindow);
+ mpMenuStyle = createStyleContext(GtkControlPart::Menu);
+
+ mpMenuItemStyle = createStyleContext(GtkControlPart::MenuItem);
+ mpMenuItemLabelStyle = createStyleContext(GtkControlPart::MenuItemLabel);
+ mpMenuItemArrowStyle = createStyleContext(GtkControlPart::MenuItemArrow);
+ mpCheckMenuItemStyle = createStyleContext(GtkControlPart::CheckMenuItem);
+ mpCheckMenuItemCheckStyle = createStyleContext(GtkControlPart::CheckMenuItemCheck);
+ mpRadioMenuItemStyle = createStyleContext(GtkControlPart::RadioMenuItem);
+ mpRadioMenuItemRadioStyle = createStyleContext(GtkControlPart::RadioMenuItemRadio);
+ mpSeparatorMenuItemStyle = createStyleContext(GtkControlPart::SeparatorMenuItem);
+ mpSeparatorMenuItemSeparatorStyle = createStyleContext(GtkControlPart::SeparatorMenuItemSeparator);
+
+ /* Frames */
+ mpFrameOutStyle = mpFrameInStyle = createStyleContext(GtkControlPart::FrameBorder);
+ getStyleContext(&mpFixedHoriLineStyle, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
+ getStyleContext(&mpFixedVertLineStyle, gtk_separator_new(GTK_ORIENTATION_VERTICAL));
+
+
+ /* Tree List */
+ gTreeViewWidget = gtk_tree_view_new();
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gTreeViewWidget);
+
+ GtkTreeViewColumn* firstTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(firstTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(gTreeViewWidget), firstTreeViewColumn);
+
+ GtkTreeViewColumn* middleTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(middleTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(gTreeViewWidget), middleTreeViewColumn);
+ gtk_tree_view_set_expander_column(GTK_TREE_VIEW(gTreeViewWidget), middleTreeViewColumn);
+
+ GtkTreeViewColumn* lastTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(lastTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(gTreeViewWidget), lastTreeViewColumn);
+
+ /* Use the middle column's header for our button */
+ GtkWidget* pTreeHeaderCellWidget = gtk_tree_view_column_get_button(middleTreeViewColumn);
+ mpTreeHeaderButtonStyle = gtk_widget_get_style_context(pTreeHeaderCellWidget);
+
+ /* Progress Bar */
+ mpProgressBarStyle = createStyleContext(GtkControlPart::ProgressBar);
+ mpProgressBarTroughStyle = createStyleContext(GtkControlPart::ProgressBarTrough);
+ mpProgressBarProgressStyle = createStyleContext(GtkControlPart::ProgressBarProgress);
+
+ gtk_widget_show_all(gDumbContainer);
+#endif
+}
+
+void GtkSalGraphics::GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY)
+{
+ char* pForceDpi;
+ if ((pForceDpi = getenv("SAL_FORCEDPI")))
+ {
+ OString sForceDPI(pForceDpi);
+ rDPIX = rDPIY = sForceDPI.toInt32();
+ return;
+ }
+
+#if !GTK_CHECK_VERSION(4, 0, 0)
+ GdkScreen* pScreen = gtk_widget_get_screen(mpWindow);
+ double fResolution = -1.0;
+ g_object_get(pScreen, "resolution", &fResolution, nullptr);
+
+ if (fResolution > 0.0)
+ rDPIX = rDPIY = sal_Int32(fResolution);
+ else
+ rDPIX = rDPIY = 96;
+#else
+ rDPIX = rDPIY = 96;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/FPServiceInfo.hxx b/vcl/unx/gtk3_kde5/FPServiceInfo.hxx
new file mode 100644
index 000000000..1fbb8fd27
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/FPServiceInfo.hxx
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+// the service names
+#define FILE_PICKER_SERVICE_NAME "com.sun.star.ui.dialogs.Gtk3KDE5FilePicker"
+
+// the implementation names
+#define FILE_PICKER_IMPL_NAME "com.sun.star.ui.dialogs.Gtk3KDE5FilePicker"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkaction.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkaction.cxx
new file mode 100644
index 000000000..6b9f5e3d8
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkaction.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/a11y/atkaction.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkbridge.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkbridge.cxx
new file mode 100644
index 000000000..e07fc6c29
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkbridge.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/a11y/atkbridge.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkcomponent.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkcomponent.cxx
new file mode 100644
index 000000000..5eac70ebd
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkcomponent.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/a11y/atkcomponent.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkeditabletext.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkeditabletext.cxx
new file mode 100644
index 000000000..792e432e1
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkeditabletext.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/a11y/atkeditabletext.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkfactory.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkfactory.cxx
new file mode 100644
index 000000000..2ddc5c55c
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkfactory.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/a11y/atkfactory.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkhypertext.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkhypertext.cxx
new file mode 100644
index 000000000..f3fa99006
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkhypertext.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/a11y/atkhypertext.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkimage.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkimage.cxx
new file mode 100644
index 000000000..eb3acc713
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkimage.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/a11y/atkimage.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atklistener.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atklistener.cxx
new file mode 100644
index 000000000..cf10d29df
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atklistener.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/a11y/atklistener.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkregistry.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkregistry.cxx
new file mode 100644
index 000000000..cc10fbfdd
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkregistry.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/a11y/atkregistry.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkselection.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkselection.cxx
new file mode 100644
index 000000000..4c7fc5c15
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkselection.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/a11y/atkselection.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktable.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktable.cxx
new file mode 100644
index 000000000..672125d1a
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktable.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/a11y/atktable.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktext.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktext.cxx
new file mode 100644
index 000000000..de1d1f06c
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktext.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/a11y/atktext.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktextattributes.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktextattributes.cxx
new file mode 100644
index 000000000..aeac0e02d
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atktextattributes.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/a11y/atktextattributes.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkutil.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkutil.cxx
new file mode 100644
index 000000000..193b08e9a
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkutil.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/a11y/atkutil.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkvalue.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkvalue.cxx
new file mode 100644
index 000000000..75825b4e1
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkvalue.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/a11y/atkvalue.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwrapper.cxx b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwrapper.cxx
new file mode 100644
index 000000000..a1bcc2e29
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/a11y/gtk3_kde5_atkwrapper.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/a11y/atkwrapper.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/filepicker_ipc_commands.hxx b/vcl/unx/gtk3_kde5/filepicker_ipc_commands.hxx
new file mode 100644
index 000000000..1d0925257
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/filepicker_ipc_commands.hxx
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <iostream>
+#include <vector>
+
+#include <sal/types.h>
+#include <com/sun/star/uno/Sequence.hxx>
+
+// #define DEBUG_FILEPICKER_IPC
+
+namespace rtl
+{
+class OUString;
+}
+class QString;
+
+enum class Commands : uint16_t
+{
+ SetTitle,
+ SetWinId,
+ Execute,
+ SetMultiSelectionMode,
+ SetDefaultName,
+ SetDisplayDirectory,
+ GetDisplayDirectory,
+ GetSelectedFiles,
+ AppendFilter,
+ SetCurrentFilter,
+ GetCurrentFilter,
+ SetValue,
+ GetValue,
+ EnableControl,
+ SetLabel,
+ GetLabel,
+ AddCheckBox,
+ Initialize,
+ Quit,
+ EnablePickFolderMode,
+};
+
+inline std::vector<char> readIpcStringArg(std::istream& stream)
+{
+ uint32_t length = 0;
+ stream >> length;
+ stream.ignore(); // skip space separator
+ std::vector<char> buffer(length, '\0');
+ stream.read(buffer.data(), length);
+ return buffer;
+}
+
+void readIpcArg(std::istream& stream, OUString& string);
+void readIpcArg(std::istream& stream, QString& string);
+void readIpcArg(std::istream& stream, css::uno::Sequence<OUString>& seq);
+
+inline void readIpcArg(std::istream& stream, Commands& value)
+{
+ uint16_t v = 0;
+ stream >> v;
+ stream.ignore(); // skip space
+ value = static_cast<Commands>(v);
+}
+
+void readIpcArg(std::istream&, sal_Bool) = delete;
+
+inline void readIpcArg(std::istream& stream, bool& value)
+{
+ stream >> value;
+ stream.ignore(); // skip space
+}
+
+inline void readIpcArg(std::istream& stream, sal_Int16& value)
+{
+ stream >> value;
+ stream.ignore(); // skip space
+}
+
+inline void readIpcArg(std::istream& stream, sal_uIntPtr& value)
+{
+ stream >> value;
+ stream.ignore(); // skip space
+}
+
+#if SAL_TYPES_SIZEOFPOINTER == 4
+inline void readIpcArg(std::istream& stream, uint64_t& value)
+{
+ stream >> value;
+ stream.ignore(); // skip space
+}
+#endif
+
+inline void readIpcArgs(std::istream& /*stream*/)
+{
+ // end of arguments, nothing to do
+}
+
+template <typename T, typename... Args>
+inline void readIpcArgs(std::istream& stream, T& arg, Args&... args)
+{
+ readIpcArg(stream, arg);
+ readIpcArgs(stream, args...);
+}
+
+void sendIpcArg(std::ostream& stream, const OUString& string);
+void sendIpcArg(std::ostream& stream, const QString& string);
+
+inline void sendIpcStringArg(std::ostream& stream, uint32_t length, const char* string)
+{
+ stream << length << ' ';
+ stream.write(string, length);
+ stream << ' ';
+}
+
+inline void sendIpcArg(std::ostream& stream, Commands value)
+{
+ stream << static_cast<uint16_t>(value) << ' ';
+}
+
+void sendIpcArg(std::ostream&, sal_Bool) = delete;
+
+inline void sendIpcArg(std::ostream& stream, bool value) { stream << value << ' '; }
+
+inline void sendIpcArg(std::ostream& stream, sal_Int16 value) { stream << value << ' '; }
+
+inline void sendIpcArg(std::ostream& stream, sal_uIntPtr value) { stream << value << ' '; }
+
+#if SAL_TYPES_SIZEOFPOINTER == 4
+inline void sendIpcArg(std::ostream& stream, uint64_t value) { stream << value << ' '; }
+#endif
+
+inline void sendIpcArgsImpl(std::ostream& stream)
+{
+ // end of arguments, flush stream
+ stream << std::endl;
+}
+
+template <typename T, typename... Args>
+inline void sendIpcArgsImpl(std::ostream& stream, const T& arg, const Args&... args)
+{
+ sendIpcArg(stream, arg);
+ sendIpcArgsImpl(stream, args...);
+}
+
+template <typename T, typename... Args>
+inline void sendIpcArgs(std::ostream& stream, const T& arg, const Args&... args)
+{
+ sendIpcArgsImpl(stream, arg, args...);
+#ifdef DEBUG_FILEPICKER_IPC
+ std::cerr << "IPC MSG: ";
+ sendIpcArgsImpl(std::cerr, arg, args...);
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_a11y.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_a11y.cxx
new file mode 100644
index 000000000..02fd47a60
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_a11y.cxx
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/a11y/gtk3atkaction.cxx"
+#include "../gtk3/a11y/gtk3atkbridge.cxx"
+#include "../gtk3/a11y/gtk3atkcomponent.cxx"
+#include "../gtk3/a11y/gtk3atkeditabletext.cxx"
+#include "../gtk3/a11y/gtk3atkfactory.cxx"
+#include "../gtk3/a11y/gtk3atkhypertext.cxx"
+#include "../gtk3/a11y/gtk3atkimage.cxx"
+#include "../gtk3/a11y/gtk3atklistener.cxx"
+#include "../gtk3/a11y/gtk3atkregistry.cxx"
+#include "../gtk3/a11y/gtk3atkselection.cxx"
+#include "../gtk3/a11y/gtk3atktable.cxx"
+#include "../gtk3/a11y/gtk3atktextattributes.cxx"
+#include "../gtk3/a11y/gtk3atktext.cxx"
+#include "../gtk3/a11y/gtk3atkutil.cxx"
+#include "../gtk3/a11y/gtk3atkvalue.cxx"
+#include "../gtk3/a11y/gtk3atkwindow.cxx"
+#include "../gtk3/a11y/gtk3atkwrapper.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_cairo.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_cairo.cxx
new file mode 100644
index 000000000..ed8780447
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_cairo.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/gtkcairo.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_customcellrenderer.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_customcellrenderer.cxx
new file mode 100644
index 000000000..aebe2c89e
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_customcellrenderer.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 "../gtk3/customcellrenderer.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.cxx
new file mode 100644
index 000000000..d3a053a08
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.cxx
@@ -0,0 +1,455 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <QUrl>
+#include <KFileWidget>
+
+#include "gtk3_kde5_filepicker.hxx"
+
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp>
+#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
+
+#include <sal/log.hxx>
+#include <vcl/svapp.hxx>
+#include "FPServiceInfo.hxx"
+
+#undef Region
+
+#include <fpicker/strings.hrc>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::ui::dialogs::TemplateDescription;
+using namespace ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds;
+using namespace ::com::sun::star::ui::dialogs::CommonFilePickerElementIds;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::uno;
+
+// helper functions
+
+namespace
+{
+uno::Sequence<OUString> FilePicker_getSupportedServiceNames()
+{
+ return { "com.sun.star.ui.dialogs.FilePicker", "com.sun.star.ui.dialogs.SystemFilePicker",
+ "com.sun.star.ui.dialogs.Gtk3KDE5FilePicker" };
+}
+}
+
+// Gtk3KDE5FilePicker
+
+Gtk3KDE5FilePicker::Gtk3KDE5FilePicker(const uno::Reference<uno::XComponentContext>&)
+ : Gtk3KDE5FilePicker_Base(_helperMutex)
+{
+ setMultiSelectionMode(false);
+
+ // tdf#124598 dummy KWidget use to make gtk3_kde5 VCL plugin link against KIO libraries
+ QString sDummyStr;
+ QUrl aUrl = KFileWidget::getStartUrl(QUrl(), sDummyStr);
+ aUrl.setPath("/dev/null");
+}
+
+Gtk3KDE5FilePicker::~Gtk3KDE5FilePicker() = default;
+
+void SAL_CALL
+Gtk3KDE5FilePicker::addFilePickerListener(const uno::Reference<XFilePickerListener>& xListener)
+{
+ SolarMutexGuard aGuard;
+ m_xListener = xListener;
+}
+
+void SAL_CALL
+Gtk3KDE5FilePicker::removeFilePickerListener(const uno::Reference<XFilePickerListener>&)
+{
+ SolarMutexGuard aGuard;
+ m_xListener.clear();
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::setTitle(const OUString& title)
+{
+ m_ipc.sendCommand(Commands::SetTitle, title);
+}
+
+sal_Int16 SAL_CALL Gtk3KDE5FilePicker::execute()
+{
+ SolarMutexGuard g;
+ return m_ipc.execute();
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::setMultiSelectionMode(sal_Bool multiSelect)
+{
+ m_ipc.sendCommand(Commands::SetMultiSelectionMode, bool(multiSelect));
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::setDefaultName(const OUString& name)
+{
+ m_ipc.sendCommand(Commands::SetDefaultName, name);
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::setDisplayDirectory(const OUString& dir)
+{
+ m_ipc.sendCommand(Commands::SetDisplayDirectory, dir);
+}
+
+OUString SAL_CALL Gtk3KDE5FilePicker::getDisplayDirectory()
+{
+ auto id = m_ipc.sendCommand(Commands::GetDisplayDirectory);
+ OUString dir;
+ m_ipc.readResponse(id, dir);
+ return dir;
+}
+
+uno::Sequence<OUString> SAL_CALL Gtk3KDE5FilePicker::getFiles()
+{
+ uno::Sequence<OUString> seq = getSelectedFiles();
+ if (seq.getLength() > 1)
+ seq.realloc(1);
+ return seq;
+}
+
+uno::Sequence<OUString> SAL_CALL Gtk3KDE5FilePicker::getSelectedFiles()
+{
+ auto id = m_ipc.sendCommand(Commands::GetSelectedFiles);
+ uno::Sequence<OUString> seq;
+ m_ipc.readResponse(id, seq);
+ return seq;
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::appendFilter(const OUString& title, const OUString& filter)
+{
+ m_ipc.sendCommand(Commands::AppendFilter, title, filter);
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::setCurrentFilter(const OUString& title)
+{
+ m_ipc.sendCommand(Commands::SetCurrentFilter, title);
+}
+
+OUString SAL_CALL Gtk3KDE5FilePicker::getCurrentFilter()
+{
+ auto id = m_ipc.sendCommand(Commands::GetCurrentFilter);
+ OUString filter;
+ m_ipc.readResponse(id, filter);
+ return filter;
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::appendFilterGroup(const OUString& /*rGroupTitle*/,
+ const uno::Sequence<beans::StringPair>& filters)
+{
+ const sal_uInt16 length = filters.getLength();
+ for (sal_uInt16 i = 0; i < length; ++i)
+ {
+ beans::StringPair aPair = filters[i];
+ appendFilter(aPair.First, aPair.Second);
+ }
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::setValue(sal_Int16 controlId, sal_Int16 nControlAction,
+ const uno::Any& value)
+{
+ if (value.has<bool>())
+ {
+ m_ipc.sendCommand(Commands::SetValue, controlId, nControlAction, value.get<bool>());
+ }
+ else
+ {
+ SAL_INFO("vcl.gtkkde5", "set value of unhandled type " << controlId);
+ }
+}
+
+uno::Any SAL_CALL Gtk3KDE5FilePicker::getValue(sal_Int16 controlId, sal_Int16 nControlAction)
+{
+ if (CHECKBOX_AUTOEXTENSION == controlId)
+ // We ignore this one and rely on QFileDialog to provide the function.
+ // Always return false, to pretend we do not support this, otherwise
+ // LO core would try to be smart and cut the extension in some places,
+ // interfering with QFileDialog's handling of it. QFileDialog also
+ // saves the value of the setting, so LO core is not needed for that either.
+ return uno::Any(false);
+
+ auto id = m_ipc.sendCommand(Commands::GetValue, controlId, nControlAction);
+
+ bool value = false;
+ m_ipc.readResponse(id, value);
+
+ return uno::Any(value);
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::enableControl(sal_Int16 controlId, sal_Bool enable)
+{
+ m_ipc.sendCommand(Commands::EnableControl, controlId, bool(enable));
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::setLabel(sal_Int16 controlId, const OUString& label)
+{
+ m_ipc.sendCommand(Commands::SetLabel, controlId, label);
+}
+
+OUString SAL_CALL Gtk3KDE5FilePicker::getLabel(sal_Int16 controlId)
+{
+ auto id = m_ipc.sendCommand(Commands::GetLabel, controlId);
+ OUString label;
+ m_ipc.readResponse(id, label);
+ return label;
+}
+
+void Gtk3KDE5FilePicker::addCustomControl(sal_Int16 controlId)
+{
+ TranslateId resId;
+
+ switch (controlId)
+ {
+ case CHECKBOX_AUTOEXTENSION:
+ resId = STR_SVT_FILEPICKER_AUTO_EXTENSION;
+ break;
+ case CHECKBOX_PASSWORD:
+ resId = STR_SVT_FILEPICKER_PASSWORD;
+ break;
+ case CHECKBOX_FILTEROPTIONS:
+ resId = STR_SVT_FILEPICKER_FILTER_OPTIONS;
+ break;
+ case CHECKBOX_READONLY:
+ resId = STR_SVT_FILEPICKER_READONLY;
+ break;
+ case CHECKBOX_LINK:
+ resId = STR_SVT_FILEPICKER_INSERT_AS_LINK;
+ break;
+ case CHECKBOX_PREVIEW:
+ resId = STR_SVT_FILEPICKER_SHOW_PREVIEW;
+ break;
+ case CHECKBOX_SELECTION:
+ resId = STR_SVT_FILEPICKER_SELECTION;
+ break;
+ case CHECKBOX_GPGENCRYPTION:
+ resId = STR_SVT_FILEPICKER_GPGENCRYPT;
+ break;
+ case PUSHBUTTON_PLAY:
+ resId = STR_SVT_FILEPICKER_PLAY;
+ break;
+ case LISTBOX_VERSION:
+ resId = STR_SVT_FILEPICKER_VERSION;
+ break;
+ case LISTBOX_TEMPLATE:
+ resId = STR_SVT_FILEPICKER_TEMPLATES;
+ break;
+ case LISTBOX_IMAGE_TEMPLATE:
+ resId = STR_SVT_FILEPICKER_IMAGE_TEMPLATE;
+ break;
+ case LISTBOX_IMAGE_ANCHOR:
+ resId = STR_SVT_FILEPICKER_IMAGE_ANCHOR;
+ break;
+ case LISTBOX_VERSION_LABEL:
+ case LISTBOX_TEMPLATE_LABEL:
+ case LISTBOX_IMAGE_TEMPLATE_LABEL:
+ case LISTBOX_IMAGE_ANCHOR_LABEL:
+ case LISTBOX_FILTER_SELECTOR:
+ break;
+ }
+
+ switch (controlId)
+ {
+ case CHECKBOX_AUTOEXTENSION:
+ case CHECKBOX_PASSWORD:
+ case CHECKBOX_FILTEROPTIONS:
+ case CHECKBOX_READONLY:
+ case CHECKBOX_LINK:
+ case CHECKBOX_PREVIEW:
+ case CHECKBOX_SELECTION:
+ case CHECKBOX_GPGENCRYPTION:
+ {
+ // the checkbox is created even for CHECKBOX_AUTOEXTENSION to simplify
+ // code, but the checkbox is hidden and ignored
+ bool hidden = controlId == CHECKBOX_AUTOEXTENSION;
+
+ m_ipc.sendCommand(Commands::AddCheckBox, controlId, hidden, getResString(resId));
+
+ break;
+ }
+ case PUSHBUTTON_PLAY:
+ case LISTBOX_VERSION:
+ case LISTBOX_TEMPLATE:
+ case LISTBOX_IMAGE_TEMPLATE:
+ case LISTBOX_IMAGE_ANCHOR:
+ case LISTBOX_VERSION_LABEL:
+ case LISTBOX_TEMPLATE_LABEL:
+ case LISTBOX_IMAGE_TEMPLATE_LABEL:
+ case LISTBOX_IMAGE_ANCHOR_LABEL:
+ case LISTBOX_FILTER_SELECTOR:
+ break;
+ }
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::initialize(const uno::Sequence<uno::Any>& args)
+{
+ // parameter checking
+ uno::Any arg;
+ if (args.getLength() == 0)
+ {
+ throw lang::IllegalArgumentException("no arguments", static_cast<XFilePicker2*>(this), 1);
+ }
+
+ arg = args[0];
+
+ if ((arg.getValueType() != cppu::UnoType<sal_Int16>::get())
+ && (arg.getValueType() != cppu::UnoType<sal_Int8>::get()))
+ {
+ throw lang::IllegalArgumentException("invalid argument type",
+ static_cast<XFilePicker2*>(this), 1);
+ }
+
+ sal_Int16 templateId = -1;
+ arg >>= templateId;
+
+ bool saveDialog = false;
+ switch (templateId)
+ {
+ case FILEOPEN_SIMPLE:
+ break;
+
+ case FILESAVE_SIMPLE:
+ saveDialog = true;
+ break;
+
+ case FILESAVE_AUTOEXTENSION:
+ saveDialog = true;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ break;
+
+ case FILESAVE_AUTOEXTENSION_PASSWORD:
+ {
+ saveDialog = true;
+ addCustomControl(CHECKBOX_PASSWORD);
+ addCustomControl(CHECKBOX_GPGENCRYPTION);
+ break;
+ }
+ case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS:
+ {
+ saveDialog = true;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ addCustomControl(CHECKBOX_PASSWORD);
+ addCustomControl(CHECKBOX_GPGENCRYPTION);
+ addCustomControl(CHECKBOX_FILTEROPTIONS);
+ break;
+ }
+ case FILESAVE_AUTOEXTENSION_SELECTION:
+ saveDialog = true;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ addCustomControl(CHECKBOX_SELECTION);
+ break;
+
+ case FILESAVE_AUTOEXTENSION_TEMPLATE:
+ saveDialog = true;
+ addCustomControl(CHECKBOX_AUTOEXTENSION);
+ addCustomControl(LISTBOX_TEMPLATE);
+ break;
+
+ case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE:
+ addCustomControl(CHECKBOX_LINK);
+ addCustomControl(CHECKBOX_PREVIEW);
+ addCustomControl(LISTBOX_IMAGE_TEMPLATE);
+ break;
+
+ case FILEOPEN_LINK_PREVIEW_IMAGE_ANCHOR:
+ addCustomControl(CHECKBOX_LINK);
+ addCustomControl(CHECKBOX_PREVIEW);
+ addCustomControl(LISTBOX_IMAGE_ANCHOR);
+ break;
+
+ case FILEOPEN_PLAY:
+ addCustomControl(PUSHBUTTON_PLAY);
+ break;
+
+ case FILEOPEN_LINK_PLAY:
+ addCustomControl(CHECKBOX_LINK);
+ addCustomControl(PUSHBUTTON_PLAY);
+ break;
+
+ case FILEOPEN_READONLY_VERSION:
+ addCustomControl(CHECKBOX_READONLY);
+ addCustomControl(LISTBOX_VERSION);
+ break;
+
+ case FILEOPEN_LINK_PREVIEW:
+ addCustomControl(CHECKBOX_LINK);
+ addCustomControl(CHECKBOX_PREVIEW);
+ break;
+
+ case FILEOPEN_PREVIEW:
+ addCustomControl(CHECKBOX_PREVIEW);
+ break;
+
+ default:
+ SAL_INFO("vcl.gtkkde5", "unknown templates " << templateId);
+ return;
+ }
+
+ setTitle(getResString(saveDialog ? STR_FILEDLG_SAVE : STR_FILEDLG_OPEN));
+
+ m_ipc.sendCommand(Commands::Initialize, saveDialog);
+}
+
+void SAL_CALL Gtk3KDE5FilePicker::cancel()
+{
+ // TODO
+}
+
+void Gtk3KDE5FilePicker::disposing(const lang::EventObject& rEvent)
+{
+ uno::Reference<XFilePickerListener> xFilePickerListener(rEvent.Source, uno::UNO_QUERY);
+
+ if (xFilePickerListener.is())
+ {
+ removeFilePickerListener(xFilePickerListener);
+ }
+}
+
+OUString SAL_CALL Gtk3KDE5FilePicker::getImplementationName() { return FILE_PICKER_IMPL_NAME; }
+
+sal_Bool SAL_CALL Gtk3KDE5FilePicker::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+uno::Sequence<OUString> SAL_CALL Gtk3KDE5FilePicker::getSupportedServiceNames()
+{
+ return FilePicker_getSupportedServiceNames();
+}
+
+void Gtk3KDE5FilePicker::filterChanged()
+{
+ FilePickerEvent aEvent;
+ aEvent.ElementId = LISTBOX_FILTER;
+ SAL_INFO("vcl.gtkkde5", "filter changed");
+ if (m_xListener.is())
+ m_xListener->controlStateChanged(aEvent);
+}
+
+void Gtk3KDE5FilePicker::selectionChanged()
+{
+ FilePickerEvent aEvent;
+ SAL_INFO("vcl.gtkkde5", "file selection changed");
+ if (m_xListener.is())
+ m_xListener->fileSelectionChanged(aEvent);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.hxx b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.hxx
new file mode 100644
index 000000000..7ce391004
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.hxx
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <cppuhelper/compbase.hxx>
+
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/ui/dialogs/XFilePicker3.hpp>
+#include <com/sun/star/ui/dialogs/XFilePickerControlAccess.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <osl/mutex.hxx>
+
+#include "gtk3_kde5_filepicker_ipc.hxx"
+
+typedef ::cppu::WeakComponentImplHelper<css::ui::dialogs::XFilePicker3,
+ css::ui::dialogs::XFilePickerControlAccess
+ // TODO css::ui::dialogs::XFilePreview
+ ,
+ css::lang::XInitialization, css::lang::XServiceInfo>
+ Gtk3KDE5FilePicker_Base;
+
+class Gtk3KDE5FilePicker : public Gtk3KDE5FilePicker_Base
+{
+protected:
+ css::uno::Reference<css::ui::dialogs::XFilePickerListener> m_xListener;
+
+ osl::Mutex _helperMutex;
+ Gtk3KDE5FilePickerIpc m_ipc;
+
+public:
+ explicit Gtk3KDE5FilePicker(const css::uno::Reference<css::uno::XComponentContext>&);
+ virtual ~Gtk3KDE5FilePicker() override;
+
+ // XFilePickerNotifier
+ virtual void SAL_CALL addFilePickerListener(
+ const css::uno::Reference<css::ui::dialogs::XFilePickerListener>& xListener) override;
+ virtual void SAL_CALL removeFilePickerListener(
+ const css::uno::Reference<css::ui::dialogs::XFilePickerListener>& xListener) override;
+
+ // XExecutableDialog functions
+ virtual void SAL_CALL setTitle(const OUString& rTitle) override;
+ virtual sal_Int16 SAL_CALL execute() override;
+
+ // XFilePicker functions
+ virtual void SAL_CALL setMultiSelectionMode(sal_Bool bMode) override;
+ virtual void SAL_CALL setDefaultName(const OUString& rName) override;
+ virtual void SAL_CALL setDisplayDirectory(const OUString& rDirectory) override;
+ virtual OUString SAL_CALL getDisplayDirectory() override;
+ virtual css::uno::Sequence<OUString> SAL_CALL getFiles() override;
+
+ // XFilterManager functions
+ virtual void SAL_CALL appendFilter(const OUString& rTitle, const OUString& rFilter) override;
+ virtual void SAL_CALL setCurrentFilter(const OUString& rTitle) override;
+ virtual OUString SAL_CALL getCurrentFilter() override;
+
+ // XFilterGroupManager functions
+ virtual void SAL_CALL
+ appendFilterGroup(const OUString& rGroupTitle,
+ const css::uno::Sequence<css::beans::StringPair>& rFilters) override;
+
+ // XFilePickerControlAccess functions
+ virtual void SAL_CALL setValue(sal_Int16 nControlId, sal_Int16 nControlAction,
+ const css::uno::Any& rValue) override;
+ virtual css::uno::Any SAL_CALL getValue(sal_Int16 nControlId,
+ sal_Int16 nControlAction) override;
+ virtual void SAL_CALL enableControl(sal_Int16 nControlId, sal_Bool bEnable) override;
+ virtual void SAL_CALL setLabel(sal_Int16 nControlId, const OUString& rLabel) override;
+ virtual OUString SAL_CALL getLabel(sal_Int16 nControlId) override;
+
+ /* TODO XFilePreview
+
+ virtual css::uno::Sequence< sal_Int16 > SAL_CALL getSupportedImageFormats( );
+ virtual sal_Int32 SAL_CALL getTargetColorDepth( );
+ virtual sal_Int32 SAL_CALL getAvailableWidth( );
+ virtual sal_Int32 SAL_CALL getAvailableHeight( );
+ virtual void SAL_CALL setImage( sal_Int16 aImageFormat, const css::uno::Any &rImage );
+ virtual sal_Bool SAL_CALL setShowState( sal_Bool bShowState );
+ virtual sal_Bool SAL_CALL getShowState( );
+ */
+
+ // XFilePicker2 functions
+ virtual css::uno::Sequence<OUString> SAL_CALL getSelectedFiles() override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize(const css::uno::Sequence<css::uno::Any>& rArguments) override;
+
+ // XCancellable
+ virtual void SAL_CALL cancel() override;
+
+ // XEventListener
+ virtual void disposing(const css::lang::EventObject& rEvent);
+ using cppu::WeakComponentImplHelperBase::disposing;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override;
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+private:
+ Gtk3KDE5FilePicker(const Gtk3KDE5FilePicker&) = delete;
+ Gtk3KDE5FilePicker& operator=(const Gtk3KDE5FilePicker&) = delete;
+
+ //add a custom control widget to the file dialog
+ void addCustomControl(sal_Int16 controlId);
+
+ // emit XFilePickerListener controlStateChanged event
+ void filterChanged();
+ // emit XFilePickerListener fileSelectionChanged event
+ void selectionChanged();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.cxx
new file mode 100644
index 000000000..f9b0da12c
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.cxx
@@ -0,0 +1,277 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include "gtk3_kde5_filepicker_ipc.hxx"
+
+#undef Region
+
+#include <system_error>
+
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+
+#include <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/weld.hxx>
+
+#include <osl/file.h>
+#include <osl/process.h>
+
+#include <gtk/gtk.h>
+
+#include <boost/filesystem/path.hpp>
+
+#include <fpicker/fpsofficeResMgr.hxx>
+
+using namespace ::com::sun::star::ui::dialogs;
+
+// helper functions
+
+namespace
+{
+OUString applicationDirPath()
+{
+ OUString applicationFilePath;
+ osl_getExecutableFile(&applicationFilePath.pData);
+ OUString applicationSystemPath;
+ osl_getSystemPathFromFileURL(applicationFilePath.pData, &applicationSystemPath.pData);
+ const auto utf8Path = applicationSystemPath.toUtf8();
+ auto ret = boost::filesystem::path(utf8Path.getStr(), utf8Path.getStr() + utf8Path.getLength());
+ ret.remove_filename();
+ return OUString::fromUtf8(std::string_view(ret.c_str()));
+}
+
+OUString findPickerExecutable()
+{
+ const auto path = applicationDirPath();
+ const OUString app("lo_kde5filepicker");
+ OUString ret;
+ osl_searchFileURL(app.pData, path.pData, &ret.pData);
+ if (ret.isEmpty())
+ throw std::system_error(std::make_error_code(std::errc::no_such_file_or_directory),
+ "could not find lo_kde5filepicker executable");
+ return ret;
+}
+}
+
+void readIpcArg(std::istream& stream, OUString& str)
+{
+ const auto buffer = readIpcStringArg(stream);
+ str = OUString::fromUtf8(std::string_view(buffer.data(), buffer.size()));
+}
+
+void readIpcArg(std::istream& stream, css::uno::Sequence<OUString>& seq)
+{
+ uint32_t numFiles = 0;
+ stream >> numFiles;
+ stream.ignore(); // skip space;
+ seq.realloc(numFiles);
+ OUString* pSeq = seq.getArray();
+ for (size_t i = 0; i < numFiles; ++i)
+ {
+ readIpcArg(stream, pSeq[i]);
+ }
+}
+
+void sendIpcArg(std::ostream& stream, const OUString& string)
+{
+ const auto utf8 = string.toUtf8();
+ sendIpcStringArg(stream, utf8.getLength(), utf8.getStr());
+}
+
+OUString getResString(TranslateId pResId)
+{
+ if (!pResId)
+ return {};
+
+ return FpsResId(pResId);
+}
+
+// handles the IPC commands for dialog execution and ends the dummy Gtk dialog once the IPC response is there
+static void handleIpcForExecute(Gtk3KDE5FilePickerIpc* pFilePickerIpc, GtkWidget* pDummyDialog,
+ bool* bResult)
+{
+ auto id = pFilePickerIpc->sendCommand(Commands::Execute);
+ pFilePickerIpc->readResponse(id, *bResult);
+
+ // end the dummy dialog
+ gtk_widget_hide(pDummyDialog);
+}
+
+// Gtk3KDE5FilePicker
+
+Gtk3KDE5FilePickerIpc::Gtk3KDE5FilePickerIpc()
+{
+ const auto exe = findPickerExecutable();
+ oslProcessError result;
+ oslSecurity pSecurity = osl_getCurrentSecurity();
+ result = osl_executeProcess_WithRedirectedIO(exe.pData, nullptr, 0, osl_Process_NORMAL,
+ pSecurity, nullptr, nullptr, 0, &m_process,
+ &m_inputWrite, &m_outputRead, nullptr);
+ osl_freeSecurityHandle(pSecurity);
+ if (result != osl_Process_E_None)
+ throw std::system_error(std::make_error_code(std::errc::no_such_process),
+ "could not start lo_kde5filepicker executable");
+}
+
+Gtk3KDE5FilePickerIpc::~Gtk3KDE5FilePickerIpc()
+{
+ if (!m_process)
+ return;
+
+ sendCommand(Commands::Quit);
+ osl_joinProcess(m_process);
+
+ if (m_inputWrite)
+ osl_closeFile(m_inputWrite);
+ if (m_outputRead)
+ osl_closeFile(m_outputRead);
+ osl_freeProcessHandle(m_process);
+}
+
+sal_Int16 Gtk3KDE5FilePickerIpc::execute()
+{
+ auto restoreMainWindow = blockMainWindow();
+
+ // dummy gtk dialog that will take care of processing events,
+ // not meant to be actually seen by user
+ GtkWidget* pDummyDialog = gtk_dialog_new();
+
+ bool accepted = false;
+
+ // send IPC command and read response in a separate thread
+ std::thread aIpcHandler(&handleIpcForExecute, this, pDummyDialog, &accepted);
+
+ // make dummy dialog not to be seen by user
+ gtk_window_set_decorated(GTK_WINDOW(pDummyDialog), false);
+ gtk_window_set_default_size(GTK_WINDOW(pDummyDialog), 0, 0);
+ gtk_window_set_accept_focus(GTK_WINDOW(pDummyDialog), false);
+ // gtk_widget_set_opacity() only has the desired effect when widget is already shown
+ gtk_widget_show(pDummyDialog);
+ gtk_widget_set_opacity(pDummyDialog, 0);
+ // run dialog, leaving event processing to GTK
+ // dialog will be closed by the separate 'aIpcHandler' thread once the IPC response is there
+ gtk_dialog_run(GTK_DIALOG(pDummyDialog));
+
+ aIpcHandler.join();
+
+ gtk_widget_destroy(pDummyDialog);
+
+ if (restoreMainWindow)
+ restoreMainWindow();
+
+ return accepted ? ExecutableDialogResults::OK : ExecutableDialogResults::CANCEL;
+}
+
+static gboolean ignoreDeleteEvent(GtkWidget* /*widget*/, GdkEvent* /*event*/,
+ gpointer /*user_data*/)
+{
+ return true;
+}
+
+std::function<void()> Gtk3KDE5FilePickerIpc::blockMainWindow()
+{
+ weld::Window* pParentWin = Application::GetDefDialogParent();
+ if (!pParentWin)
+ return {};
+
+ const SystemEnvData aSysData = pParentWin->get_system_data();
+ auto* pMainWindow = static_cast<GtkWidget*>(aSysData.pWidget);
+ if (!pMainWindow)
+ return {};
+
+ sendCommand(Commands::SetWinId, aSysData.GetWindowHandle(aSysData.pSalFrame));
+
+ SolarMutexGuard guard;
+ auto deleteEventSignalId = g_signal_lookup("delete_event", gtk_widget_get_type());
+
+ // disable the mainwindow
+ gtk_widget_set_sensitive(pMainWindow, false);
+
+ // block the GtkSalFrame delete_event handler
+ auto blockedHandler = g_signal_handler_find(
+ pMainWindow, static_cast<GSignalMatchType>(G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_DATA),
+ deleteEventSignalId, 0, nullptr, nullptr, aSysData.pSalFrame);
+ g_signal_handler_block(pMainWindow, blockedHandler);
+
+ // prevent the window from being closed
+ auto ignoreDeleteEventHandler
+ = g_signal_connect(pMainWindow, "delete_event", G_CALLBACK(ignoreDeleteEvent), nullptr);
+
+ return [pMainWindow, ignoreDeleteEventHandler, blockedHandler] {
+ SolarMutexGuard cleanupGuard;
+ // re-enable window
+ gtk_widget_set_sensitive(pMainWindow, true);
+
+ // allow it to be closed again
+ g_signal_handler_disconnect(pMainWindow, ignoreDeleteEventHandler);
+
+ // unblock the GtkSalFrame handler
+ g_signal_handler_unblock(pMainWindow, blockedHandler);
+ };
+}
+
+void Gtk3KDE5FilePickerIpc::writeResponseLine(const std::string& line)
+{
+ sal_uInt64 bytesWritten = 0;
+ osl_writeFile(m_inputWrite, line.c_str(), line.size(), &bytesWritten);
+}
+
+std::string Gtk3KDE5FilePickerIpc::readResponseLine()
+{
+ if (!m_responseBuffer.empty()) // check whether we have a line in our buffer
+ {
+ std::size_t it = m_responseBuffer.find('\n');
+ if (it != std::string::npos)
+ {
+ auto ret = m_responseBuffer.substr(0, it);
+ m_responseBuffer.erase(0, it + 1);
+ return ret;
+ }
+ }
+
+ const sal_uInt64 BUF_SIZE = 1024;
+ char buffer[BUF_SIZE];
+ while (true)
+ {
+ sal_uInt64 bytesRead = 0;
+ auto err = osl_readFile(m_outputRead, buffer, BUF_SIZE, &bytesRead);
+ auto it = std::find(buffer, buffer + bytesRead, '\n');
+ if (it != buffer + bytesRead) // check whether the chunk we read contains an EOL
+ {
+ // if so, append that part to the buffer and return it
+ std::string ret = m_responseBuffer.append(buffer, it);
+ // but keep anything else we may have read in our buffer
+ ++it;
+ m_responseBuffer.assign(it, buffer + bytesRead);
+ return ret;
+ }
+ // otherwise append everything we read to the buffer and try again
+ m_responseBuffer.append(buffer, bytesRead);
+
+ if (err != osl_File_E_None && err != osl_File_E_AGAIN)
+ break;
+ }
+ return {};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.hxx b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.hxx
new file mode 100644
index 000000000..58ca3eb45
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.hxx
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osl/process.h>
+#include <unotools/resmgr.hxx>
+
+#include "filepicker_ipc_commands.hxx"
+
+#include <functional>
+#include <mutex>
+#include <thread>
+#include <sstream>
+
+OUString getResString(TranslateId pResId);
+
+class Gtk3KDE5FilePickerIpc
+{
+protected:
+ oslProcess m_process;
+ oslFileHandle m_inputWrite;
+ oslFileHandle m_outputRead;
+ // simple multiplexing: every command gets its own ID that can be used to
+ // read the corresponding response
+ uint64_t m_msgId = 1;
+ std::mutex m_mutex;
+ uint64_t m_incomingResponse = 0;
+ std::string m_responseBuffer;
+ std::stringstream m_responseStream;
+
+public:
+ explicit Gtk3KDE5FilePickerIpc();
+ ~Gtk3KDE5FilePickerIpc();
+
+ sal_Int16 execute();
+
+ void writeResponseLine(const std::string& line);
+
+ template <typename... Args> uint64_t sendCommand(Commands command, const Args&... args)
+ {
+ auto id = m_msgId;
+ ++m_msgId;
+ std::stringstream stream;
+ sendIpcArgs(stream, id, command, args...);
+ writeResponseLine(stream.str());
+ return id;
+ }
+
+ std::string readResponseLine();
+
+ // workaround gcc <= 4.8 bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55914
+ template <int...> struct seq
+ {
+ };
+ template <int N, int... S> struct gens : gens<N - 1, N - 1, S...>
+ {
+ };
+ template <int... S> struct gens<0, S...>
+ {
+ typedef seq<S...> type;
+ };
+ template <typename... Args> struct ArgsReader
+ {
+ ArgsReader(Args&... args)
+ : m_args(args...)
+ {
+ }
+
+ void operator()(std::istream& stream)
+ {
+ callFunc(stream, typename gens<sizeof...(Args)>::type());
+ }
+
+ private:
+ template <int... S> void callFunc(std::istream& stream, seq<S...>)
+ {
+ readIpcArgs(stream, std::get<S>(m_args)...);
+ }
+
+ std::tuple<Args&...> m_args;
+ };
+
+ template <typename... Args> void readResponse(uint64_t id, Args&... args)
+ {
+ ArgsReader<Args...> argsReader(args...);
+ while (true)
+ {
+ // only let one thread read at any given time
+ std::scoped_lock<std::mutex> lock(m_mutex);
+
+ // check if we need to read (and potentially wait) a response ID
+ if (m_incomingResponse == 0)
+ {
+ m_responseStream.clear();
+ m_responseStream.str(readResponseLine());
+ readIpcArgs(m_responseStream, m_incomingResponse);
+ }
+
+ if (m_incomingResponse == id)
+ {
+ // the response we are waiting for came in
+ argsReader(m_responseStream);
+ m_incomingResponse = 0;
+ break;
+ }
+ else
+ {
+ // the next response answers some other request, yield
+ std::this_thread::yield();
+ }
+ }
+ }
+
+private:
+ std::function<void()> blockMainWindow();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker.cxx
new file mode 100644
index 000000000..c1569e6be
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker.cxx
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "gtk3_kde5_folderpicker.hxx"
+
+#include <vcl/svapp.hxx>
+
+#include <fpicker/strings.hrc>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+// constructor
+
+Gtk3KDE5FolderPicker::Gtk3KDE5FolderPicker(
+ const uno::Reference<uno::XComponentContext>& /*xContext*/)
+{
+ m_ipc.sendCommand(Commands::EnablePickFolderMode);
+ setTitle(getResString(STR_SVT_FOLDERPICKER_DEFAULT_TITLE));
+}
+
+Gtk3KDE5FolderPicker::~Gtk3KDE5FolderPicker() = default;
+
+void SAL_CALL Gtk3KDE5FolderPicker::setDisplayDirectory(const OUString& aDirectory)
+{
+ m_ipc.sendCommand(Commands::SetDisplayDirectory, aDirectory);
+}
+
+OUString SAL_CALL Gtk3KDE5FolderPicker::getDisplayDirectory()
+{
+ auto id = m_ipc.sendCommand(Commands::GetDisplayDirectory);
+ OUString ret;
+ m_ipc.readResponse(id, ret);
+ return ret;
+}
+
+OUString SAL_CALL Gtk3KDE5FolderPicker::getDirectory()
+{
+ auto id = m_ipc.sendCommand(Commands::GetSelectedFiles);
+ uno::Sequence<OUString> seq;
+ m_ipc.readResponse(id, seq);
+ return seq.hasElements() ? seq[0] : OUString();
+}
+
+void SAL_CALL Gtk3KDE5FolderPicker::setDescription(const OUString& /*rDescription*/) {}
+
+// XExecutableDialog functions
+
+void SAL_CALL Gtk3KDE5FolderPicker::setTitle(const OUString& aTitle)
+{
+ m_ipc.sendCommand(Commands::SetTitle, aTitle);
+}
+
+sal_Int16 SAL_CALL Gtk3KDE5FolderPicker::execute()
+{
+ SolarMutexGuard g;
+ return m_ipc.execute();
+}
+
+// XCancellable
+
+void SAL_CALL Gtk3KDE5FolderPicker::cancel()
+{
+ // TODO
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker.hxx b/vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker.hxx
new file mode 100644
index 000000000..9801a072a
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_folderpicker.hxx
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <cppuhelper/implbase.hxx>
+
+#include <com/sun/star/ui/dialogs/XFolderPicker2.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include "gtk3_kde5_filepicker_ipc.hxx"
+
+class Gtk3KDE5FolderPicker : public cppu::WeakImplHelper<css::ui::dialogs::XFolderPicker2>
+{
+protected:
+ Gtk3KDE5FilePickerIpc m_ipc;
+
+public:
+ // constructor
+ explicit Gtk3KDE5FolderPicker(
+ const css::uno::Reference<css::uno::XComponentContext>& xServiceMgr);
+ virtual ~Gtk3KDE5FolderPicker() override;
+
+ // XExecutableDialog functions
+ virtual void SAL_CALL setTitle(const OUString& aTitle) override;
+ virtual sal_Int16 SAL_CALL execute() override;
+
+ // XFolderPicker functions
+ virtual void SAL_CALL setDisplayDirectory(const OUString& rDirectory) override;
+ virtual OUString SAL_CALL getDisplayDirectory() override;
+ virtual OUString SAL_CALL getDirectory() override;
+ virtual void SAL_CALL setDescription(const OUString& rDescription) override;
+
+ // XCancellable
+ virtual void SAL_CALL cancel() override;
+
+private:
+ Gtk3KDE5FolderPicker(const Gtk3KDE5FolderPicker&) = delete;
+ Gtk3KDE5FolderPicker& operator=(const Gtk3KDE5FolderPicker&) = delete;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_gloactiongroup.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_gloactiongroup.cxx
new file mode 100644
index 000000000..eccf49d3d
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_gloactiongroup.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/gloactiongroup.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_glomenu.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_glomenu.cxx
new file mode 100644
index 000000000..979c9fba0
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_glomenu.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/glomenu.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_gtkdata.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_gtkdata.cxx
new file mode 100644
index 000000000..f03dce99a
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_gtkdata.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/gtkdata.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_gtkframe.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_gtkframe.cxx
new file mode 100644
index 000000000..8d8a07a3f
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_gtkframe.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/gtkframe.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_gtkinst.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_gtkinst.cxx
new file mode 100644
index 000000000..dd6aace5d
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_gtkinst.cxx
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+// make gtk3 plug advertise correctly as kde5 hybrid
+#define GTK_TOOLKIT_NAME "gtk3_kde5"
+#include "../gtk3/gtkinst.cxx"
+
+#include "gtk3_kde5_filepicker.hxx"
+#include "gtk3_kde5_folderpicker.hxx"
+
+#include <system_error>
+
+uno::Reference<ui::dialogs::XFilePicker2>
+GtkInstance::createFilePicker(const uno::Reference<uno::XComponentContext>& xMSF)
+{
+ try
+ {
+ return uno::Reference<ui::dialogs::XFilePicker2>(new Gtk3KDE5FilePicker(xMSF));
+ }
+ catch (const std::system_error& error)
+ {
+ OSL_FAIL(error.what());
+ return { nullptr };
+ }
+}
+
+uno::Reference<ui::dialogs::XFolderPicker2>
+GtkInstance::createFolderPicker(const uno::Reference<uno::XComponentContext>& xMSF)
+{
+ try
+ {
+ return uno::Reference<ui::dialogs::XFolderPicker2>(new Gtk3KDE5FolderPicker(xMSF));
+ }
+ catch (const std::system_error& error)
+ {
+ OSL_FAIL(error.what());
+ return { nullptr };
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_gtkobject.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_gtkobject.cxx
new file mode 100644
index 000000000..948c6bd3a
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_gtkobject.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/gtkobject.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_gtksalmenu.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_gtksalmenu.cxx
new file mode 100644
index 000000000..7963e6b60
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_gtksalmenu.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/gtksalmenu.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_gtksys.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_gtksys.cxx
new file mode 100644
index 000000000..3f3ad9f1d
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_gtksys.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/gtksys.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_hudawareness.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_hudawareness.cxx
new file mode 100644
index 000000000..e0b8f1812
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_hudawareness.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/hudawareness.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_salnativewidgets-gtk.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_salnativewidgets-gtk.cxx
new file mode 100644
index 000000000..428ff8204
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_salnativewidgets-gtk.cxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "../gtk3/salnativewidgets-gtk.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/kde5_filepicker.cxx b/vcl/unx/gtk3_kde5/kde5_filepicker.cxx
new file mode 100644
index 000000000..b3382b33d
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/kde5_filepicker.cxx
@@ -0,0 +1,287 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/svapp.hxx>
+
+#include "kde5_filepicker.hxx"
+
+#include <KWindowSystem>
+#include <KFileWidget>
+
+#include <QtCore/QDebug>
+#include <QtCore/QUrl>
+#include <QtWidgets/QCheckBox>
+#include <QtWidgets/QFileDialog>
+#include <QtWidgets/QGridLayout>
+#include <QtWidgets/QWidget>
+#include <QtWidgets/QApplication>
+
+// KDE5FilePicker
+
+KDE5FilePicker::KDE5FilePicker(QObject* parent)
+ : QObject(parent)
+ , _dialog(new QFileDialog(nullptr, {}, QDir::homePath()))
+ , _extraControls(new QWidget)
+ , _layout(new QGridLayout(_extraControls))
+ , _winId(0)
+{
+ _dialog->setSupportedSchemes({
+ QStringLiteral("file"), QStringLiteral("ftp"), QStringLiteral("http"),
+ QStringLiteral("https"), QStringLiteral("webdav"), QStringLiteral("webdavs"),
+ QStringLiteral("smb"),
+ QStringLiteral(""), // this makes removable devices shown
+ });
+
+ setMultiSelectionMode(false);
+
+ connect(_dialog, &QFileDialog::filterSelected, this, &KDE5FilePicker::filterChanged);
+ connect(_dialog, &QFileDialog::fileSelected, this, &KDE5FilePicker::selectionChanged);
+
+ setupCustomWidgets();
+}
+
+void KDE5FilePicker::enableFolderMode()
+{
+ _dialog->setOption(QFileDialog::ShowDirsOnly, true);
+ // Workaround for https://bugs.kde.org/show_bug.cgi?id=406464 :
+ // Don't set file mode to QFileDialog::Directory when native KDE Plasma 5
+ // file dialog is used, since clicking on directory "bar" inside directory "foo"
+ // and then confirming would return "foo" rather than "foo/bar";
+ // on the other hand, non-native file dialog needs 'QFileDialog::Directory'
+ // and doesn't allow folder selection otherwise
+ if (Application::GetDesktopEnvironment() != "PLASMA5")
+ {
+ _dialog->setFileMode(QFileDialog::Directory);
+ }
+}
+
+KDE5FilePicker::~KDE5FilePicker()
+{
+ delete _extraControls;
+ delete _dialog;
+}
+
+void KDE5FilePicker::setTitle(const QString& title) { _dialog->setWindowTitle(title); }
+
+bool KDE5FilePicker::execute()
+{
+ if (!_filters.isEmpty())
+ _dialog->setNameFilters(_filters);
+ if (!_currentFilter.isEmpty())
+ _dialog->selectNameFilter(_currentFilter);
+
+ _dialog->show();
+ //block and wait for user input
+ return _dialog->exec() == QFileDialog::Accepted;
+}
+
+void KDE5FilePicker::setMultiSelectionMode(bool multiSelect)
+{
+ if (_dialog->acceptMode() == QFileDialog::AcceptSave)
+ return;
+
+ _dialog->setFileMode(multiSelect ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile);
+}
+
+void KDE5FilePicker::setDefaultName(const QString& name) { _dialog->selectFile(name); }
+
+void KDE5FilePicker::setDisplayDirectory(const QString& dir)
+{
+ _dialog->setDirectoryUrl(QUrl(dir));
+}
+
+QString KDE5FilePicker::getDisplayDirectory() const { return _dialog->directoryUrl().url(); }
+
+QList<QUrl> KDE5FilePicker::getSelectedFiles() const { return _dialog->selectedUrls(); }
+
+void KDE5FilePicker::appendFilter(const QString& title, const QString& filter)
+{
+ QString t = title;
+ QString f = filter;
+ // '/' need to be escaped else they are assumed to be mime types by kfiledialog
+ //see the docs
+ t.replace("/", "\\/");
+
+ // openoffice gives us filters separated by ';' qt dialogs just want space separated
+ f.replace(";", " ");
+
+ // make sure "*.*" is not used as "all files"
+ f.replace("*.*", "*");
+
+ _filters << QStringLiteral("%1 (%2)").arg(t, f);
+ _titleToFilters[t] = _filters.constLast();
+}
+
+void KDE5FilePicker::setCurrentFilter(const QString& title)
+{
+ _currentFilter = _titleToFilters.value(title);
+}
+
+QString KDE5FilePicker::getCurrentFilter() const
+{
+ QString filter = _titleToFilters.key(_dialog->selectedNameFilter());
+
+ //default if not found
+ if (filter.isEmpty())
+ filter = "ODF Text Document (.odt)";
+
+ return filter;
+}
+
+void KDE5FilePicker::setValue(sal_Int16 controlId, sal_Int16 /*nControlAction*/, bool value)
+{
+ if (_customWidgets.contains(controlId))
+ {
+ QCheckBox* cb = dynamic_cast<QCheckBox*>(_customWidgets.value(controlId));
+ if (cb)
+ cb->setChecked(value);
+ }
+ else
+ qWarning() << "set value on unknown control" << controlId;
+}
+
+bool KDE5FilePicker::getValue(sal_Int16 controlId, sal_Int16 /*nControlAction*/) const
+{
+ bool ret = false;
+ if (_customWidgets.contains(controlId))
+ {
+ QCheckBox* cb = dynamic_cast<QCheckBox*>(_customWidgets.value(controlId));
+ if (cb)
+ ret = cb->isChecked();
+ }
+ else
+ qWarning() << "get value on unknown control" << controlId;
+
+ return ret;
+}
+
+void KDE5FilePicker::enableControl(sal_Int16 controlId, bool enable)
+{
+ if (_customWidgets.contains(controlId))
+ _customWidgets.value(controlId)->setEnabled(enable);
+ else
+ qWarning() << "enable on unknown control" << controlId;
+}
+
+void KDE5FilePicker::setLabel(sal_Int16 controlId, const QString& label)
+{
+ if (_customWidgets.contains(controlId))
+ {
+ QCheckBox* cb = dynamic_cast<QCheckBox*>(_customWidgets.value(controlId));
+ if (cb)
+ cb->setText(label);
+ }
+ else
+ qWarning() << "set label on unknown control" << controlId;
+}
+
+QString KDE5FilePicker::getLabel(sal_Int16 controlId) const
+{
+ QString label;
+ if (_customWidgets.contains(controlId))
+ {
+ QCheckBox* cb = dynamic_cast<QCheckBox*>(_customWidgets.value(controlId));
+ if (cb)
+ label = cb->text();
+ }
+ else
+ qWarning() << "get label on unknown control" << controlId;
+
+ return label;
+}
+
+void KDE5FilePicker::addCheckBox(sal_Int16 controlId, const QString& label, bool hidden)
+{
+ auto resString = label;
+ resString.replace('~', '&');
+
+ auto widget = new QCheckBox(resString, _extraControls);
+ widget->setHidden(hidden);
+ if (!hidden)
+ {
+ _layout->addWidget(widget);
+ }
+ _customWidgets.insert(controlId, widget);
+}
+
+void KDE5FilePicker::initialize(bool saveDialog)
+{
+ //default is opening
+ QFileDialog::AcceptMode operationMode
+ = saveDialog ? QFileDialog::AcceptSave : QFileDialog::AcceptOpen;
+
+ _dialog->setAcceptMode(operationMode);
+
+ if (saveDialog)
+ _dialog->setFileMode(QFileDialog::AnyFile);
+}
+
+void KDE5FilePicker::setWinId(sal_uInt64 winId) { _winId = winId; }
+
+void KDE5FilePicker::setupCustomWidgets()
+{
+ // When using the platform-native Plasma/KDE5 file picker, we currently rely on KFileWidget
+ // being present to add the custom controls visible (s. 'eventFilter' method).
+ // Since this doesn't work for other desktop environments, use a non-native
+ // dialog there in order not to lose the custom controls and insert the custom
+ // widget in the layout returned by QFileDialog::layout()
+ // (which returns nullptr for native file dialogs)
+ if (Application::GetDesktopEnvironment() == "PLASMA5")
+ {
+ qApp->installEventFilter(this);
+ }
+ else
+ {
+ _dialog->setOption(QFileDialog::DontUseNativeDialog);
+ QGridLayout* pLayout = static_cast<QGridLayout*>(_dialog->layout());
+ assert(pLayout);
+ const int row = pLayout->rowCount();
+ pLayout->addWidget(_extraControls, row, 1);
+ }
+}
+
+bool KDE5FilePicker::eventFilter(QObject* o, QEvent* e)
+{
+ if (e->type() == QEvent::Show && o->isWidgetType())
+ {
+ auto* w = static_cast<QWidget*>(o);
+ if (!w->parentWidget() && w->isModal())
+ {
+ /*
+ To replace when baseline will include kwindowsystem >= 5.62 with:
+ w->setAttribute(Qt::WA_NativeWindow, true);
+ KWindowSystem::setMainWindow(w->windowHandle(), _winId);
+ */
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ KWindowSystem::setMainWindow(w, _winId);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ if (auto* fileWidget = w->findChild<KFileWidget*>({}, Qt::FindDirectChildrenOnly))
+ {
+ fileWidget->setCustomWidget(_extraControls);
+ // remove event filter again; the only purpose was to set the custom widget here
+ qApp->removeEventFilter(this);
+ }
+ }
+ }
+ return QObject::eventFilter(o, e);
+}
+
+#include <kde5_filepicker.moc>
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/kde5_filepicker.hxx b/vcl/unx/gtk3_kde5/kde5_filepicker.hxx
new file mode 100644
index 000000000..74f94d222
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/kde5_filepicker.hxx
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <QtCore/QObject>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtCore/QHash>
+
+#include <sal/types.h>
+
+class QFileDialog;
+class QWidget;
+class QGridLayout;
+
+class KDE5FilePicker : public QObject
+{
+ Q_OBJECT
+protected:
+ //the dialog to display
+ QFileDialog* _dialog;
+
+ //running filter string to add to dialog
+ QStringList _filters;
+ // map of filter titles to full filter for selection
+ QHash<QString, QString> _titleToFilters;
+ // string to set the current filter
+ QString _currentFilter;
+
+ //mapping of SAL control ID's to created custom controls
+ QHash<sal_Int16, QWidget*> _customWidgets;
+
+ //widget to contain extra custom controls
+ QWidget* _extraControls;
+
+ //layout for extra custom controls
+ QGridLayout* _layout;
+
+ sal_uInt64 _winId;
+
+public:
+ explicit KDE5FilePicker(QObject* parent = nullptr);
+ ~KDE5FilePicker() override;
+
+ void enableFolderMode();
+
+ // XExecutableDialog functions
+ void setTitle(const QString& rTitle);
+ bool execute();
+
+ // XFilePicker functions
+ void setMultiSelectionMode(bool bMode);
+ void setDefaultName(const QString& rName);
+ void setDisplayDirectory(const QString& rDirectory);
+ QString getDisplayDirectory() const;
+
+ // XFilterManager functions
+ void appendFilter(const QString& rTitle, const QString& rFilter);
+ void setCurrentFilter(const QString& rTitle);
+ QString getCurrentFilter() const;
+
+ // XFilePickerControlAccess functions
+ void setValue(sal_Int16 nControlId, sal_Int16 nControlAction, bool rValue);
+ bool getValue(sal_Int16 nControlId, sal_Int16 nControlAction) const;
+ void enableControl(sal_Int16 nControlId, bool bEnable);
+ void setLabel(sal_Int16 nControlId, const QString& rLabel);
+ QString getLabel(sal_Int16 nControlId) const;
+
+ // XFilePicker2 functions
+ QList<QUrl> getSelectedFiles() const;
+
+ // XInitialization
+ void initialize(bool saveDialog);
+
+ //add a custom control widget to the file dialog
+ void addCheckBox(sal_Int16 nControlId, const QString& label, bool hidden);
+
+ void setWinId(sal_uInt64 winId);
+
+private:
+ Q_DISABLE_COPY(KDE5FilePicker)
+ // adds the custom controls to the dialog
+ void setupCustomWidgets();
+
+protected:
+ bool eventFilter(QObject* watched, QEvent* event) override;
+
+Q_SIGNALS:
+ void filterChanged();
+ void selectionChanged();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.cxx b/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.cxx
new file mode 100644
index 000000000..7f0bfff98
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.cxx
@@ -0,0 +1,374 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "kde5_filepicker_ipc.hxx"
+
+#include <QUrl>
+#include <QApplication>
+#include <QDebug>
+
+#include <iostream>
+
+#include "kde5_filepicker.hxx"
+
+void readIpcArg(std::istream& stream, QString& string)
+{
+ const auto buffer = readIpcStringArg(stream);
+ string = QString::fromUtf8(buffer.data(), buffer.size());
+}
+
+void sendIpcArg(std::ostream& stream, const QString& string)
+{
+ const auto utf8 = string.toUtf8();
+ sendIpcStringArg(stream, utf8.size(), utf8.data());
+}
+
+static void sendIpcArg(std::ostream& stream, const QStringList& list)
+{
+ stream << static_cast<uint32_t>(list.size()) << ' ';
+ for (const auto& entry : list)
+ {
+ sendIpcArg(stream, entry);
+ }
+}
+
+static void readCommandArgs(Commands command, QList<QVariant>& args)
+{
+ switch (command)
+ {
+ case Commands::SetTitle:
+ {
+ QString title;
+ readIpcArgs(std::cin, title);
+ args.append(title);
+ break;
+ }
+ case Commands::SetWinId:
+ {
+ sal_uIntPtr winId = 0;
+ readIpcArgs(std::cin, winId);
+ QVariant aWinIdVariant;
+ aWinIdVariant.setValue(winId);
+ args.append(aWinIdVariant);
+ break;
+ }
+ case Commands::SetMultiSelectionMode:
+ {
+ bool multiSelection = false;
+ readIpcArgs(std::cin, multiSelection);
+ args.append(multiSelection);
+ break;
+ }
+ case Commands::SetDefaultName:
+ {
+ QString name;
+ readIpcArgs(std::cin, name);
+ args.append(name);
+ break;
+ }
+ case Commands::SetDisplayDirectory:
+ {
+ QString dir;
+ readIpcArgs(std::cin, dir);
+ args.append(dir);
+ break;
+ }
+ case Commands::AppendFilter:
+ {
+ QString title, filter;
+ readIpcArgs(std::cin, title, filter);
+ args.append(title);
+ args.append(filter);
+ break;
+ }
+ case Commands::SetCurrentFilter:
+ {
+ QString title;
+ readIpcArgs(std::cin, title);
+ args.append(title);
+ break;
+ }
+ case Commands::SetValue:
+ {
+ sal_Int16 controlId = 0;
+ sal_Int16 nControlAction = 0;
+ bool value = false;
+ readIpcArgs(std::cin, controlId, nControlAction, value);
+ args.append(controlId);
+ args.append(nControlAction);
+ args.append(value);
+ break;
+ }
+ case Commands::GetValue:
+ {
+ sal_Int16 controlId = 0;
+ sal_Int16 nControlAction = 0;
+ readIpcArgs(std::cin, controlId, nControlAction);
+ args.append(controlId);
+ args.append(nControlAction);
+ break;
+ }
+ case Commands::EnableControl:
+ {
+ sal_Int16 controlId = 0;
+ bool enabled = false;
+ readIpcArgs(std::cin, controlId, enabled);
+ args.append(controlId);
+ args.append(enabled);
+ break;
+ }
+ case Commands::SetLabel:
+ {
+ sal_Int16 controlId = 0;
+ QString label;
+ readIpcArgs(std::cin, controlId, label);
+ args.append(controlId);
+ args.append(label);
+ break;
+ }
+ case Commands::GetLabel:
+ {
+ sal_Int16 controlId = 0;
+ readIpcArgs(std::cin, controlId);
+ args.append(controlId);
+ break;
+ }
+ case Commands::AddCheckBox:
+ {
+ sal_Int16 controlId = 0;
+ bool hidden = false;
+ QString label;
+ readIpcArgs(std::cin, controlId, hidden, label);
+ args.append(controlId);
+ args.append(hidden);
+ args.append(label);
+ break;
+ }
+ case Commands::Initialize:
+ {
+ bool saveDialog = false;
+ readIpcArgs(std::cin, saveDialog);
+ args.append(saveDialog);
+ break;
+ }
+ default:
+ {
+ // no extra parameters/arguments
+ break;
+ }
+ };
+}
+
+static void readCommands(FilePickerIpc* ipc)
+{
+ while (!std::cin.eof())
+ {
+ uint64_t messageId = 0;
+ Commands command;
+ readIpcArgs(std::cin, messageId, command);
+
+ // retrieve additional command-specific arguments
+ QList<QVariant> args;
+ readCommandArgs(command, args);
+
+ emit ipc->commandReceived(messageId, command, args);
+
+ // stop processing once 'Quit' command has been sent
+ if (command == Commands::Quit)
+ {
+ return;
+ }
+ }
+}
+
+FilePickerIpc::FilePickerIpc(KDE5FilePicker* filePicker, QObject* parent)
+ : QObject(parent)
+ , m_filePicker(filePicker)
+{
+ // required to be able to pass those via signal/slot
+ qRegisterMetaType<uint64_t>("uint64_t");
+ qRegisterMetaType<Commands>("Commands");
+
+ connect(this, &FilePickerIpc::commandReceived, this, &FilePickerIpc::handleCommand);
+
+ // read IPC commands and their args in a separate thread, so this does not block everything else;
+ // 'commandReceived' signal is emitted every time a command and its args have been read;
+ // thread will run until the filepicker process is terminated
+ m_ipcReaderThread = std::make_unique<std::thread>(readCommands, this);
+}
+
+FilePickerIpc::~FilePickerIpc()
+{
+ // join thread that reads commands
+ m_ipcReaderThread->join();
+};
+
+bool FilePickerIpc::handleCommand(uint64_t messageId, Commands command, QList<QVariant> args)
+{
+ switch (command)
+ {
+ case Commands::SetTitle:
+ {
+ QString title = args.takeFirst().toString();
+ m_filePicker->setTitle(title);
+ return true;
+ }
+ case Commands::SetWinId:
+ {
+ sal_uIntPtr winId = args.takeFirst().value<sal_uIntPtr>();
+ m_filePicker->setWinId(winId);
+ return true;
+ }
+ case Commands::Execute:
+ {
+ sendIpcArgs(std::cout, messageId, m_filePicker->execute());
+ return true;
+ }
+ case Commands::SetMultiSelectionMode:
+ {
+ bool multiSelection = args.takeFirst().toBool();
+ m_filePicker->setMultiSelectionMode(multiSelection);
+ return true;
+ }
+ case Commands::SetDefaultName:
+ {
+ QString name = args.takeFirst().toString();
+ m_filePicker->setDefaultName(name);
+ return true;
+ }
+ case Commands::SetDisplayDirectory:
+ {
+ QString dir = args.takeFirst().toString();
+ m_filePicker->setDisplayDirectory(dir);
+ return true;
+ }
+ case Commands::GetDisplayDirectory:
+ {
+ sendIpcArgs(std::cout, messageId, m_filePicker->getDisplayDirectory());
+ return true;
+ }
+ case Commands::GetSelectedFiles:
+ {
+ QStringList files;
+ for (auto const& url_ : m_filePicker->getSelectedFiles())
+ {
+ auto url = url_;
+ if (url.scheme() == QLatin1String("webdav")
+ || url.scheme() == QLatin1String("webdavs"))
+ {
+ // translate webdav and webdavs URLs into a format supported by LO
+ url.setScheme(QLatin1String("vnd.sun.star.") + url.scheme());
+ }
+ else if (url.scheme() == QLatin1String("smb"))
+ {
+ // clear the user name - the GIO backend does not support this apparently
+ // when no username is available, it will ask for the password
+ url.setUserName({});
+ }
+ files << url.toString();
+ }
+ sendIpcArgs(std::cout, messageId, files);
+ return true;
+ }
+ case Commands::AppendFilter:
+ {
+ QString title = args.takeFirst().toString();
+ QString filter = args.takeFirst().toString();
+ m_filePicker->appendFilter(title, filter);
+ return true;
+ }
+ case Commands::SetCurrentFilter:
+ {
+ QString title = args.takeFirst().toString();
+ m_filePicker->setCurrentFilter(title);
+ return true;
+ }
+ case Commands::GetCurrentFilter:
+ {
+ sendIpcArgs(std::cout, messageId, m_filePicker->getCurrentFilter());
+ return true;
+ }
+ case Commands::SetValue:
+ {
+ sal_Int16 controlId = args.takeFirst().value<sal_Int16>();
+ sal_Int16 nControlAction = args.takeFirst().value<sal_Int16>();
+ bool value = args.takeFirst().toBool();
+ m_filePicker->setValue(controlId, nControlAction, value);
+ return true;
+ }
+ case Commands::GetValue:
+ {
+ sal_Int16 controlId = args.takeFirst().value<sal_Int16>();
+ sal_Int16 nControlAction = args.takeFirst().value<sal_Int16>();
+ sendIpcArgs(std::cout, messageId, m_filePicker->getValue(controlId, nControlAction));
+ return true;
+ }
+ case Commands::EnableControl:
+ {
+ sal_Int16 controlId = args.takeFirst().value<sal_Int16>();
+ bool enabled = args.takeFirst().toBool();
+ m_filePicker->enableControl(controlId, enabled);
+ return true;
+ }
+ case Commands::SetLabel:
+ {
+ sal_Int16 controlId = args.takeFirst().value<sal_Int16>();
+ QString label = args.takeFirst().toString();
+ m_filePicker->setLabel(controlId, label);
+ return true;
+ }
+ case Commands::GetLabel:
+ {
+ sal_Int16 controlId = args.takeFirst().value<sal_Int16>();
+ sendIpcArgs(std::cout, messageId, m_filePicker->getLabel(controlId));
+ return true;
+ }
+ case Commands::AddCheckBox:
+ {
+ sal_Int16 controlId = args.takeFirst().value<sal_Int16>();
+ bool hidden = args.takeFirst().toBool();
+ QString label = args.takeFirst().toString();
+ m_filePicker->addCheckBox(controlId, label, hidden);
+ return true;
+ }
+ case Commands::Initialize:
+ {
+ bool saveDialog = args.takeFirst().toBool();
+ m_filePicker->initialize(saveDialog);
+ return true;
+ }
+ case Commands::EnablePickFolderMode:
+ {
+ m_filePicker->enableFolderMode();
+ return true;
+ }
+ case Commands::Quit:
+ {
+ QCoreApplication::quit();
+ return false;
+ }
+ }
+ qWarning() << "unhandled command " << static_cast<uint16_t>(command);
+ QCoreApplication::exit(1);
+ return false;
+}
+
+#include <kde5_filepicker_ipc.moc>
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.hxx b/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.hxx
new file mode 100644
index 000000000..fa9be696c
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.hxx
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <memory>
+#include <thread>
+
+#include <QObject>
+
+#include "filepicker_ipc_commands.hxx"
+
+class KDE5FilePicker;
+class WinIdEmbedder;
+class QSocketNotifier;
+
+class FilePickerIpc : public QObject
+{
+ Q_OBJECT
+public:
+ explicit FilePickerIpc(KDE5FilePicker* filePicker, QObject* parent = nullptr);
+ ~FilePickerIpc() override;
+
+private:
+ KDE5FilePicker* m_filePicker = nullptr;
+ std::unique_ptr<std::thread> m_ipcReaderThread;
+
+private Q_SLOTS:
+ bool handleCommand(uint64_t messageId, Commands command, QList<QVariant> args);
+
+Q_SIGNALS:
+ bool commandReceived(uint64_t messageId, Commands command, QList<QVariant> args);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/kde5_lo_filepicker_main.cxx b/vcl/unx/gtk3_kde5/kde5_lo_filepicker_main.cxx
new file mode 100644
index 000000000..4e73e9ee4
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/kde5_lo_filepicker_main.cxx
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "kde5_filepicker.hxx"
+#include "kde5_filepicker_ipc.hxx"
+
+#include <QApplication>
+#include <QCommandLineParser>
+
+#include <config_version.h>
+
+int main(int argc, char** argv)
+{
+ QApplication::setOrganizationName("LibreOffice");
+ QApplication::setOrganizationDomain("libreoffice.org");
+ QApplication::setApplicationName(QStringLiteral("lo_kde5filepicker"));
+ QApplication::setQuitOnLastWindowClosed(false);
+ QApplication::setApplicationVersion(LIBO_VERSION_DOTTED);
+
+ QApplication app(argc, argv);
+
+ QCommandLineParser parser;
+ parser.setApplicationDescription(
+ QObject::tr("Helper executable for LibreOffice KDE/Plasma integration.\n"
+ "Do not run this executable directly. Rather, use it indirectly via "
+ "the gtk3_kde5 VCL plugin (SAL_USE_VCLPLUGIN=gtk3_kde5)."));
+ parser.addVersionOption();
+ parser.addHelpOption();
+ parser.process(app);
+
+ KDE5FilePicker filePicker;
+ FilePickerIpc ipc(&filePicker);
+
+ return QApplication::exec();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/convert3to4.cxx b/vcl/unx/gtk4/convert3to4.cxx
new file mode 100644
index 000000000..e00009cd1
--- /dev/null
+++ b/vcl/unx/gtk4/convert3to4.cxx
@@ -0,0 +1,1576 @@
+/* -*- 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 <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/io/TempFile.hpp>
+#include <com/sun/star/xml/dom/DocumentBuilder.hpp>
+#include <com/sun/star/xml/sax/Writer.hpp>
+#include <com/sun/star/xml/sax/XSAXSerializable.hpp>
+#include <comphelper/processfactory.hxx>
+#include <unx/gtk/gtkdata.hxx>
+#include <vcl/builder.hxx>
+#include <cairo/cairo-gobject.h>
+#include "convert3to4.hxx"
+
+namespace
+{
+typedef std::pair<css::uno::Reference<css::xml::dom::XNode>, OUString> named_node;
+
+bool sortButtonNodes(const named_node& rA, const named_node& rB)
+{
+ OString sA(rA.second.toUtf8());
+ OString sB(rB.second.toUtf8());
+ //order within groups according to platform rules
+ return getButtonPriority(sA) < getButtonPriority(sB);
+}
+
+// <property name="spacing">6</property>
+css::uno::Reference<css::xml::dom::XNode>
+CreateProperty(const css::uno::Reference<css::xml::dom::XDocument>& xDoc, const OUString& rPropName,
+ const OUString& rValue)
+{
+ css::uno::Reference<css::xml::dom::XElement> xProperty = xDoc->createElement("property");
+ css::uno::Reference<css::xml::dom::XAttr> xPropName = xDoc->createAttribute("name");
+ xPropName->setValue(rPropName);
+ xProperty->setAttributeNode(xPropName);
+ css::uno::Reference<css::xml::dom::XText> xValue = xDoc->createTextNode(rValue);
+ xProperty->appendChild(xValue);
+ return xProperty;
+}
+
+bool ToplevelIsMessageDialog(const css::uno::Reference<css::xml::dom::XNode>& xNode)
+{
+ for (css::uno::Reference<css::xml::dom::XNode> xObjectCandidate = xNode->getParentNode();
+ xObjectCandidate.is(); xObjectCandidate = xObjectCandidate->getParentNode())
+ {
+ if (xObjectCandidate->getNodeName() == "object")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xObjectMap
+ = xObjectCandidate->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xClass = xObjectMap->getNamedItem("class");
+ if (xClass->getNodeValue() == "GtkMessageDialog")
+ return true;
+ }
+ }
+ return false;
+}
+
+void insertAsFirstChild(const css::uno::Reference<css::xml::dom::XNode>& xParentNode,
+ const css::uno::Reference<css::xml::dom::XNode>& xChildNode)
+{
+ auto xFirstChild = xParentNode->getFirstChild();
+ if (xFirstChild.is())
+ xParentNode->insertBefore(xChildNode, xFirstChild);
+ else
+ xParentNode->appendChild(xChildNode);
+}
+
+void SetPropertyOnTopLevel(const css::uno::Reference<css::xml::dom::XNode>& xNode,
+ const css::uno::Reference<css::xml::dom::XNode>& xProperty)
+{
+ for (css::uno::Reference<css::xml::dom::XNode> xObjectCandidate = xNode->getParentNode();
+ xObjectCandidate.is(); xObjectCandidate = xObjectCandidate->getParentNode())
+ {
+ if (xObjectCandidate->getNodeName() == "object")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xObjectMap
+ = xObjectCandidate->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xClass = xObjectMap->getNamedItem("class");
+ if (xClass->getNodeValue() == "GtkDialog")
+ {
+ insertAsFirstChild(xObjectCandidate, xProperty);
+ break;
+ }
+ }
+ }
+}
+
+OUString GetParentObjectType(const css::uno::Reference<css::xml::dom::XNode>& xNode)
+{
+ auto xParent = xNode->getParentNode();
+ assert(xParent->getNodeName() == "object");
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap = xParent->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xClass = xParentMap->getNamedItem("class");
+ return xClass->getNodeValue();
+}
+
+css::uno::Reference<css::xml::dom::XNode>
+GetChildObject(const css::uno::Reference<css::xml::dom::XNode>& xChild)
+{
+ for (css::uno::Reference<css::xml::dom::XNode> xObjectCandidate = xChild->getFirstChild();
+ xObjectCandidate.is(); xObjectCandidate = xObjectCandidate->getNextSibling())
+ {
+ if (xObjectCandidate->getNodeName() == "object")
+ return xObjectCandidate;
+ }
+ return nullptr;
+}
+
+// currently runs the risk of duplicate margin-* properties if there was already such as well
+// as the border
+void AddBorderAsMargins(const css::uno::Reference<css::xml::dom::XNode>& xNode,
+ const OUString& rBorderWidth)
+{
+ auto xDoc = xNode->getOwnerDocument();
+
+ auto xMarginEnd = CreateProperty(xDoc, "margin-end", rBorderWidth);
+ insertAsFirstChild(xNode, xMarginEnd);
+ xNode->insertBefore(CreateProperty(xDoc, "margin-top", rBorderWidth), xMarginEnd);
+ xNode->insertBefore(CreateProperty(xDoc, "margin-bottom", rBorderWidth), xMarginEnd);
+ xNode->insertBefore(CreateProperty(xDoc, "margin-start", rBorderWidth), xMarginEnd);
+}
+
+struct MenuEntry
+{
+ bool m_bDrawAsRadio;
+ css::uno::Reference<css::xml::dom::XNode> m_xPropertyLabel;
+
+ MenuEntry(bool bDrawAsRadio, const css::uno::Reference<css::xml::dom::XNode>& rPropertyLabel)
+ : m_bDrawAsRadio(bDrawAsRadio)
+ , m_xPropertyLabel(rPropertyLabel)
+ {
+ }
+};
+
+MenuEntry ConvertMenu(css::uno::Reference<css::xml::dom::XNode>& xMenuSection,
+ const css::uno::Reference<css::xml::dom::XNode>& xNode)
+{
+ bool bDrawAsRadio = false;
+ css::uno::Reference<css::xml::dom::XNode> xPropertyLabel;
+
+ css::uno::Reference<css::xml::dom::XNode> xChild = xNode->getFirstChild();
+ while (xChild.is())
+ {
+ if (xChild->getNodeName() == "property")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xName = xMap->getNamedItem("name");
+ OUString sName(xName->getNodeValue().replace('_', '-'));
+
+ if (sName == "label")
+ {
+ xPropertyLabel = xChild;
+ }
+ else if (sName == "draw-as-radio")
+ {
+ bDrawAsRadio = toBool(xChild->getFirstChild()->getNodeValue());
+ }
+ }
+
+ auto xNextChild = xChild->getNextSibling();
+
+ auto xCurrentMenuSection = xMenuSection;
+
+ if (xChild->getNodeName() == "object")
+ {
+ auto xDoc = xChild->getOwnerDocument();
+
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xClass = xMap->getNamedItem("class");
+ OUString sClass(xClass->getNodeValue());
+
+ if (sClass == "GtkMenuItem" || sClass == "GtkRadioMenuItem")
+ {
+ /* <item> */
+ css::uno::Reference<css::xml::dom::XElement> xItem = xDoc->createElement("item");
+ xMenuSection->appendChild(xItem);
+ }
+ else if (sClass == "GtkSeparatorMenuItem")
+ {
+ /* <section> */
+ css::uno::Reference<css::xml::dom::XElement> xSection
+ = xDoc->createElement("section");
+ xMenuSection->getParentNode()->appendChild(xSection);
+ xMenuSection = xSection;
+ xCurrentMenuSection = xMenuSection;
+ }
+ else if (sClass == "GtkMenu")
+ {
+ xMenuSection->removeChild(xMenuSection->getLastChild()); // remove preceding <item>
+
+ css::uno::Reference<css::xml::dom::XElement> xSubMenu
+ = xDoc->createElement("submenu");
+ css::uno::Reference<css::xml::dom::XAttr> xIdAttr = xDoc->createAttribute("id");
+
+ css::uno::Reference<css::xml::dom::XNode> xId = xMap->getNamedItem("id");
+ OUString sId(xId->getNodeValue());
+
+ xIdAttr->setValue(sId);
+ xSubMenu->setAttributeNode(xIdAttr);
+ xMenuSection->appendChild(xSubMenu);
+
+ css::uno::Reference<css::xml::dom::XElement> xSection
+ = xDoc->createElement("section");
+ xSubMenu->appendChild(xSection);
+
+ xMenuSection = xSubMenu;
+ }
+ }
+
+ bool bChildDrawAsRadio = false;
+ css::uno::Reference<css::xml::dom::XNode> xChildPropertyLabel;
+ if (xChild->hasChildNodes())
+ {
+ MenuEntry aEntry = ConvertMenu(xMenuSection, xChild);
+ bChildDrawAsRadio = aEntry.m_bDrawAsRadio;
+ xChildPropertyLabel = aEntry.m_xPropertyLabel;
+ }
+
+ if (xChild->getNodeName() == "object")
+ {
+ xMenuSection = xCurrentMenuSection;
+
+ auto xDoc = xChild->getOwnerDocument();
+
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xClass = xMap->getNamedItem("class");
+ OUString sClass(xClass->getNodeValue());
+
+ if (sClass == "GtkMenuItem" || sClass == "GtkRadioMenuItem")
+ {
+ css::uno::Reference<css::xml::dom::XNode> xId = xMap->getNamedItem("id");
+ OUString sId = xId->getNodeValue();
+
+ /*
+ <attribute name='label' translatable='yes'>whatever</attribute>
+ <attribute name='action'>menu.action</attribute>
+ <attribute name='target'>id</attribute>
+ */
+ auto xItem = xMenuSection->getLastChild();
+
+ if (xChildPropertyLabel)
+ {
+ css::uno::Reference<css::xml::dom::XElement> xChildPropertyElem(
+ xChildPropertyLabel, css::uno::UNO_QUERY_THROW);
+
+ css::uno::Reference<css::xml::dom::XElement> xLabelAttr
+ = xDoc->createElement("attribute");
+
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xLabelMap
+ = xChildPropertyLabel->getAttributes();
+ while (xLabelMap->getLength())
+ {
+ css::uno::Reference<css::xml::dom::XAttr> xAttr(xLabelMap->item(0),
+ css::uno::UNO_QUERY_THROW);
+ xLabelAttr->setAttributeNode(
+ xChildPropertyElem->removeAttributeNode(xAttr));
+ }
+ xLabelAttr->appendChild(
+ xChildPropertyLabel->removeChild(xChildPropertyLabel->getFirstChild()));
+
+ xChildPropertyLabel->getParentNode()->removeChild(xChildPropertyLabel);
+ xItem->appendChild(xLabelAttr);
+ }
+
+ css::uno::Reference<css::xml::dom::XElement> xActionAttr
+ = xDoc->createElement("attribute");
+ css::uno::Reference<css::xml::dom::XAttr> xActionName
+ = xDoc->createAttribute("name");
+ xActionName->setValue("action");
+ xActionAttr->setAttributeNode(xActionName);
+ if (bChildDrawAsRadio)
+ xActionAttr->appendChild(xDoc->createTextNode("menu.radio." + sId));
+ else
+ xActionAttr->appendChild(xDoc->createTextNode("menu.normal." + sId));
+ xItem->appendChild(xActionAttr);
+
+ css::uno::Reference<css::xml::dom::XElement> xTargetAttr
+ = xDoc->createElement("attribute");
+ css::uno::Reference<css::xml::dom::XAttr> xTargetName
+ = xDoc->createAttribute("name");
+ xTargetName->setValue("target");
+ xTargetAttr->setAttributeNode(xTargetName);
+ xTargetAttr->appendChild(xDoc->createTextNode(sId));
+ xItem->appendChild(xTargetAttr);
+
+ css::uno::Reference<css::xml::dom::XElement> xHiddenWhenAttr
+ = xDoc->createElement("attribute");
+ css::uno::Reference<css::xml::dom::XAttr> xHiddenWhenName
+ = xDoc->createAttribute("name");
+ xHiddenWhenName->setValue("hidden-when");
+ xHiddenWhenAttr->setAttributeNode(xHiddenWhenName);
+ xHiddenWhenAttr->appendChild(xDoc->createTextNode("action-missing"));
+ xItem->appendChild(xHiddenWhenAttr);
+ }
+ }
+
+ xChild = xNextChild;
+ }
+
+ return MenuEntry(bDrawAsRadio, xPropertyLabel);
+}
+
+struct ConvertResult
+{
+ bool m_bChildCanFocus;
+ bool m_bHasVisible;
+ bool m_bHasIconSize;
+ bool m_bAlwaysShowImage;
+ bool m_bUseUnderline;
+ bool m_bVertOrientation;
+ bool m_bXAlign;
+ GtkPositionType m_eImagePos;
+ css::uno::Reference<css::xml::dom::XNode> m_xPropertyLabel;
+ css::uno::Reference<css::xml::dom::XNode> m_xPropertyIconName;
+
+ ConvertResult(bool bChildCanFocus, bool bHasVisible, bool bHasIconSize, bool bAlwaysShowImage,
+ bool bUseUnderline, bool bVertOrientation, bool bXAlign,
+ GtkPositionType eImagePos,
+ const css::uno::Reference<css::xml::dom::XNode>& rPropertyLabel,
+ const css::uno::Reference<css::xml::dom::XNode>& rPropertyIconName)
+ : m_bChildCanFocus(bChildCanFocus)
+ , m_bHasVisible(bHasVisible)
+ , m_bHasIconSize(bHasIconSize)
+ , m_bAlwaysShowImage(bAlwaysShowImage)
+ , m_bUseUnderline(bUseUnderline)
+ , m_bVertOrientation(bVertOrientation)
+ , m_bXAlign(bXAlign)
+ , m_eImagePos(eImagePos)
+ , m_xPropertyLabel(rPropertyLabel)
+ , m_xPropertyIconName(rPropertyIconName)
+ {
+ }
+};
+
+bool IsAllowedBuiltInIcon(std::u16string_view iconName)
+{
+ // limit the named icons to those known by VclBuilder
+ return VclBuilder::mapStockToSymbol(iconName) != SymbolType::DONTKNOW;
+}
+
+ConvertResult Convert3To4(const css::uno::Reference<css::xml::dom::XNode>& xNode)
+{
+ css::uno::Reference<css::xml::dom::XNodeList> xNodeList = xNode->getChildNodes();
+ if (!xNodeList.is())
+ {
+ return ConvertResult(false, false, false, false, false, false, false, GTK_POS_LEFT, nullptr,
+ nullptr);
+ }
+
+ std::vector<css::uno::Reference<css::xml::dom::XNode>> xRemoveList;
+
+ OUString sBorderWidth;
+ bool bChildCanFocus = false;
+ bool bHasVisible = false;
+ bool bHasIconSize = false;
+ bool bAlwaysShowImage = false;
+ GtkPositionType eImagePos = GTK_POS_LEFT;
+ bool bUseUnderline = false;
+ bool bVertOrientation = false;
+ bool bXAlign = false;
+ css::uno::Reference<css::xml::dom::XNode> xPropertyLabel;
+ css::uno::Reference<css::xml::dom::XNode> xPropertyIconName;
+ css::uno::Reference<css::xml::dom::XNode> xCantFocus;
+
+ css::uno::Reference<css::xml::dom::XElement> xGeneratedImageChild;
+
+ css::uno::Reference<css::xml::dom::XNode> xChild = xNode->getFirstChild();
+ while (xChild.is())
+ {
+ if (xChild->getNodeName() == "requires")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xLib = xMap->getNamedItem("lib");
+ assert(xLib->getNodeValue() == "gtk+");
+ xLib->setNodeValue("gtk");
+ css::uno::Reference<css::xml::dom::XNode> xVersion = xMap->getNamedItem("version");
+ assert(xVersion->getNodeValue() == "3.20");
+ xVersion->setNodeValue("4.0");
+ }
+ else if (xChild->getNodeName() == "property")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xName = xMap->getNamedItem("name");
+ OUString sName(xName->getNodeValue().replace('_', '-'));
+
+ if (sName == "border-width")
+ sBorderWidth = xChild->getFirstChild()->getNodeValue();
+
+ if (sName == "has-default")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap
+ = xChild->getParentNode()->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xId = xParentMap->getNamedItem("id");
+ auto xDoc = xChild->getOwnerDocument();
+ auto xDefaultWidget = CreateProperty(xDoc, "default-widget", xId->getNodeValue());
+ SetPropertyOnTopLevel(xChild, xDefaultWidget);
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "has-focus" || sName == "is-focus")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap
+ = xChild->getParentNode()->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xId = xParentMap->getNamedItem("id");
+ auto xDoc = xChild->getOwnerDocument();
+ auto xDefaultWidget = CreateProperty(xDoc, "focus-widget", xId->getNodeValue());
+ SetPropertyOnTopLevel(xChild, xDefaultWidget);
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "can-focus")
+ {
+ bChildCanFocus = toBool(xChild->getFirstChild()->getNodeValue());
+ if (!bChildCanFocus)
+ {
+ OUString sParentClass = GetParentObjectType(xChild);
+ if (sParentClass == "GtkBox" || sParentClass == "GtkGrid"
+ || sParentClass == "GtkViewport")
+ {
+ // e.g. for the case of notebooks without children yet, just remove the can't focus property
+ // from Boxes and Grids
+ xRemoveList.push_back(xChild);
+ }
+ else if (sParentClass == "GtkComboBoxText")
+ {
+ // this was always a bit finicky in gtk3, fix it up to default to can-focus
+ xRemoveList.push_back(xChild);
+ }
+ else
+ {
+ // otherwise mark the property as needing removal if there turns out to be a child
+ // with can-focus of true, in which case remove this parent conflicting property
+ xCantFocus = xChild;
+ }
+ }
+ }
+
+ if (sName == "label")
+ {
+ OUString sParentClass = GetParentObjectType(xChild);
+ if (sParentClass == "GtkToolButton" || sParentClass == "GtkMenuToolButton"
+ || sParentClass == "GtkToggleToolButton")
+ {
+ xName->setNodeValue("tooltip-text");
+ }
+ xPropertyLabel = xChild;
+ }
+
+ if (sName == "modal")
+ {
+ OUString sParentClass = GetParentObjectType(xChild);
+ if (sParentClass == "GtkPopover")
+ xName->setNodeValue("autohide");
+ }
+
+ if (sName == "visible")
+ bHasVisible = true;
+
+ if (sName == "icon-name")
+ xPropertyIconName = xChild;
+
+ if (sName == "show-arrow")
+ xRemoveList.push_back(xChild);
+
+ if (sName == "events")
+ xRemoveList.push_back(xChild);
+
+ if (sName == "constrain-to")
+ xRemoveList.push_back(xChild);
+
+ if (sName == "activates-default")
+ {
+ if (GetParentObjectType(xChild) == "GtkSpinButton")
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "width-chars")
+ {
+ if (GetParentObjectType(xChild) == "GtkEntry")
+ {
+ // I don't quite get what the difference should be wrt width-chars and max-width-chars
+ // but glade doesn't write max-width-chars and in gtk4 where we have width-chars, e.g
+ // print dialog, then max-width-chars gives the effect we wanted with width-chars
+ auto xDoc = xChild->getOwnerDocument();
+ auto mMaxWidthChars = CreateProperty(xDoc, "max-width-chars",
+ xChild->getFirstChild()->getNodeValue());
+ xChild->getParentNode()->insertBefore(mMaxWidthChars, xChild);
+ }
+ }
+
+ // remove 'Help' button label and replace with a help icon instead. Unless the toplevel is a message dialog
+ if (sName == "label" && GetParentObjectType(xChild) == "GtkButton"
+ && !ToplevelIsMessageDialog(xChild))
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap
+ = xChild->getParentNode()->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xId = xParentMap->getNamedItem("id");
+ if (xId && xId->getNodeValue() == "help")
+ {
+ auto xDoc = xChild->getOwnerDocument();
+ auto xIconName = CreateProperty(xDoc, "icon-name", "help-browser-symbolic");
+ xChild->getParentNode()->insertBefore(xIconName, xChild);
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (sName == "icon-size")
+ {
+ if (GetParentObjectType(xChild) == "GtkImage")
+ {
+ bHasIconSize = true;
+
+ OUString sSize = xChild->getFirstChild()->getNodeValue();
+ /*
+ old:
+ 3 -> GTK_ICON_SIZE_LARGE_TOOLBAR: Size appropriate for large toolbars (24px)
+ 5 -> GTK_ICON_SIZE_DND: Size appropriate for drag and drop (32px)
+ 6 -> GTK_ICON_SIZE_DIALOG: Size appropriate for dialogs (48px)
+
+ new:
+ 2 -> GTK_ICON_SIZE_LARGE
+ */
+ if (sSize == "3" || sSize == "5" || sSize == "6")
+ {
+ auto xDoc = xChild->getOwnerDocument();
+ auto xIconSize = CreateProperty(xDoc, "icon-size", "2");
+ xChild->getParentNode()->insertBefore(xIconSize, xChild);
+ }
+
+ xRemoveList.push_back(xChild);
+ }
+
+ if (GetParentObjectType(xChild) == "GtkToolbar")
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "truncate-multiline")
+ {
+ if (GetParentObjectType(xChild) == "GtkSpinButton")
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "has-frame")
+ {
+ if (GetParentObjectType(xChild) == "GtkSpinButton")
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "toolbar-style")
+ {
+ // is there an equivalent for this ?
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "shadow-type")
+ {
+ if (GetParentObjectType(xChild) == "GtkFrame")
+ xRemoveList.push_back(xChild);
+ else if (GetParentObjectType(xChild) == "GtkScrolledWindow")
+ {
+ bool bHasFrame = xChild->getFirstChild()->getNodeValue() != "none";
+ auto xDoc = xChild->getOwnerDocument();
+ auto xHasFrame = CreateProperty(
+ xDoc, "has-frame", bHasFrame ? OUString("True") : OUString("False"));
+ xChild->getParentNode()->insertBefore(xHasFrame, xChild);
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (sName == "always-show-image")
+ {
+ if (GetParentObjectType(xChild) == "GtkButton"
+ || GetParentObjectType(xChild) == "GtkMenuButton"
+ || GetParentObjectType(xChild) == "GtkToggleButton")
+ {
+ // we will turn always-show-image into a GtkBox child for
+ // GtkButton and a GtkLabel child for the GtkBox and move
+ // the label property into it.
+ bAlwaysShowImage = toBool(xChild->getFirstChild()->getNodeValue());
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (sName == "image-position")
+ {
+ if (GetParentObjectType(xChild) == "GtkButton")
+ {
+ // we will turn always-show-image into a GtkBox child for
+ // GtkButton and a GtkLabel child for the GtkBox and move
+ // the label property into it.
+ OUString sImagePos = xChild->getFirstChild()->getNodeValue();
+ if (sImagePos == "top")
+ eImagePos = GTK_POS_TOP;
+ else if (sImagePos == "bottom")
+ eImagePos = GTK_POS_BOTTOM;
+ else if (sImagePos == "right")
+ eImagePos = GTK_POS_RIGHT;
+ else
+ assert(sImagePos == "left");
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (sName == "use-underline")
+ bUseUnderline = toBool(xChild->getFirstChild()->getNodeValue());
+
+ if (sName == "orientation")
+ bVertOrientation = xChild->getFirstChild()->getNodeValue() == "vertical";
+
+ if (sName == "relief")
+ {
+ if (GetParentObjectType(xChild) == "GtkToggleButton"
+ || GetParentObjectType(xChild) == "GtkMenuButton"
+ || GetParentObjectType(xChild) == "GtkLinkButton"
+ || GetParentObjectType(xChild) == "GtkButton")
+ {
+ assert(xChild->getFirstChild()->getNodeValue() == "none");
+ auto xDoc = xChild->getOwnerDocument();
+ auto xHasFrame = CreateProperty(xDoc, "has-frame", "False");
+ xChild->getParentNode()->insertBefore(xHasFrame, xChild);
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (sName == "xalign")
+ {
+ if (GetParentObjectType(xChild) == "GtkLinkButton"
+ || GetParentObjectType(xChild) == "GtkMenuButton"
+ || GetParentObjectType(xChild) == "GtkButton")
+ {
+ // TODO expand into a GtkLabel child with alignment on that instead
+ assert(xChild->getFirstChild()->getNodeValue() == "0");
+ bXAlign = true;
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (sName == "use-popover")
+ {
+ if (GetParentObjectType(xChild) == "GtkMenuButton")
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "hscrollbar-policy")
+ {
+ if (GetParentObjectType(xChild) == "GtkScrolledWindow")
+ {
+ if (xChild->getFirstChild()->getNodeValue() == "never")
+ {
+ auto xDoc = xChild->getOwnerDocument();
+ auto xHasFrame = CreateProperty(xDoc, "propagate-natural-width", "True");
+ xChild->getParentNode()->insertBefore(xHasFrame, xChild);
+ }
+ }
+ }
+
+ if (sName == "vscrollbar-policy")
+ {
+ if (GetParentObjectType(xChild) == "GtkScrolledWindow")
+ {
+ if (xChild->getFirstChild()->getNodeValue() == "never")
+ {
+ auto xDoc = xChild->getOwnerDocument();
+ auto xHasFrame = CreateProperty(xDoc, "propagate-natural-height", "True");
+ xChild->getParentNode()->insertBefore(xHasFrame, xChild);
+ }
+ }
+ }
+
+ if (sName == "popup")
+ {
+ if (GetParentObjectType(xChild) == "GtkMenuButton")
+ {
+ OUString sMenuName = xChild->getFirstChild()->getNodeValue();
+ auto xDoc = xChild->getOwnerDocument();
+ auto xPopover = CreateProperty(xDoc, "popover", sMenuName);
+ xChild->getParentNode()->insertBefore(xPopover, xChild);
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (sName == "image")
+ {
+ if (GetParentObjectType(xChild) == "GtkButton"
+ || GetParentObjectType(xChild) == "GtkMenuButton"
+ || GetParentObjectType(xChild) == "GtkToggleButton")
+ {
+ // find the image object, expected to be a child of "interface"
+ auto xObjectCandidate = xChild->getParentNode();
+ if (xObjectCandidate->getNodeName() == "object")
+ {
+ OUString sImageId = xChild->getFirstChild()->getNodeValue();
+
+ css::uno::Reference<css::xml::dom::XNode> xRootCandidate
+ = xChild->getParentNode();
+ while (xRootCandidate)
+ {
+ if (xRootCandidate->getNodeName() == "interface")
+ break;
+ xRootCandidate = xRootCandidate->getParentNode();
+ }
+
+ css::uno::Reference<css::xml::dom::XNode> xImageNode;
+
+ for (auto xImageCandidate = xRootCandidate->getFirstChild();
+ xImageCandidate.is();
+ xImageCandidate = xImageCandidate->getNextSibling())
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xImageCandidateMap
+ = xImageCandidate->getAttributes();
+ if (!xImageCandidateMap.is())
+ continue;
+ css::uno::Reference<css::xml::dom::XNode> xId
+ = xImageCandidateMap->getNamedItem("id");
+ if (xId && xId->getNodeValue() == sImageId)
+ {
+ xImageNode = xImageCandidate;
+ break;
+ }
+ }
+
+ auto xDoc = xChild->getOwnerDocument();
+
+ // relocate it to be a child of this GtkButton
+ xGeneratedImageChild = xDoc->createElement("child");
+ xGeneratedImageChild->appendChild(
+ xImageNode->getParentNode()->removeChild(xImageNode));
+ xObjectCandidate->appendChild(xGeneratedImageChild);
+ }
+
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (sName == "draw-indicator")
+ {
+ assert(toBool(xChild->getFirstChild()->getNodeValue()));
+ if (GetParentObjectType(xChild) == "GtkMenuButton" && gtk_get_minor_version() >= 4)
+ {
+ auto xDoc = xChild->getOwnerDocument();
+ auto xAlwaysShowArrow = CreateProperty(xDoc, "always-show-arrow", "True");
+ xChild->getParentNode()->insertBefore(xAlwaysShowArrow, xChild);
+ }
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "type-hint" || sName == "skip-taskbar-hint" || sName == "can-default"
+ || sName == "border-width" || sName == "layout-style" || sName == "no-show-all"
+ || sName == "ignore-hidden" || sName == "window-position")
+ {
+ xRemoveList.push_back(xChild);
+ }
+
+ if (sName == "AtkObject::accessible-description")
+ xName->setNodeValue("description");
+
+ if (sName == "AtkObject::accessible-name")
+ xName->setNodeValue("label");
+ }
+ else if (xChild->getNodeName() == "child")
+ {
+ bool bContentArea = false;
+
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xName = xMap->getNamedItem("internal-child");
+ if (xName)
+ {
+ OUString sName(xName->getNodeValue());
+ if (sName == "vbox")
+ {
+ xName->setNodeValue("content_area");
+ bContentArea = true;
+ }
+ else if (sName == "accessible")
+ {
+ xRemoveList.push_back(xChild);
+ }
+ }
+
+ if (bContentArea)
+ {
+ css::uno::Reference<css::xml::dom::XNode> xObject = GetChildObject(xChild);
+ if (xObject)
+ {
+ auto xDoc = xChild->getOwnerDocument();
+
+ auto xVExpand = CreateProperty(xDoc, "vexpand", "True");
+ insertAsFirstChild(xObject, xVExpand);
+
+ if (!sBorderWidth.isEmpty())
+ {
+ AddBorderAsMargins(xObject, sBorderWidth);
+ sBorderWidth.clear();
+ }
+ }
+ }
+ }
+ else if (xChild->getNodeName() == "packing")
+ {
+ // remove "packing" and if its grid packing insert a replacement "layout" into
+ // the associated "object"
+ auto xDoc = xChild->getOwnerDocument();
+ css::uno::Reference<css::xml::dom::XElement> xNew = xDoc->createElement("layout");
+
+ bool bGridPacking = false;
+
+ // iterate over all children and append them to the new element
+ for (css::uno::Reference<css::xml::dom::XNode> xCurrent = xChild->getFirstChild();
+ xCurrent.is(); xCurrent = xChild->getFirstChild())
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xCurrent->getAttributes();
+ if (xMap.is())
+ {
+ css::uno::Reference<css::xml::dom::XNode> xName = xMap->getNamedItem("name");
+ OUString sName(xName->getNodeValue().replace('_', '-'));
+ if (sName == "left-attach")
+ {
+ xName->setNodeValue("column");
+ bGridPacking = true;
+ }
+ else if (sName == "top-attach")
+ {
+ xName->setNodeValue("row");
+ bGridPacking = true;
+ }
+ else if (sName == "width")
+ {
+ xName->setNodeValue("column-span");
+ bGridPacking = true;
+ }
+ else if (sName == "height")
+ {
+ xName->setNodeValue("row-span");
+ bGridPacking = true;
+ }
+ else if (sName == "secondary")
+ {
+ // turn parent tag of <child> into <child type="start">
+ auto xParent = xChild->getParentNode();
+ css::uno::Reference<css::xml::dom::XAttr> xTypeStart
+ = xDoc->createAttribute("type");
+ xTypeStart->setValue("start");
+ css::uno::Reference<css::xml::dom::XElement> xElem(
+ xParent, css::uno::UNO_QUERY_THROW);
+ xElem->setAttributeNode(xTypeStart);
+ }
+ else if (sName == "pack-type")
+ {
+ // turn parent tag of <child> into <child type="start">
+ auto xParent = xChild->getParentNode();
+
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xParentMap
+ = xParent->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xParentType
+ = xParentMap->getNamedItem("type");
+ assert(!xParentType || xParentType->getNodeValue() == "titlebar");
+ if (!xParentType)
+ {
+ css::uno::Reference<css::xml::dom::XAttr> xTypeStart
+ = xDoc->createAttribute("type");
+ xTypeStart->setValue(xCurrent->getFirstChild()->getNodeValue());
+ css::uno::Reference<css::xml::dom::XElement> xElem(
+ xParent, css::uno::UNO_QUERY_THROW);
+ xElem->setAttributeNode(xTypeStart);
+ }
+ }
+ }
+ xNew->appendChild(xChild->removeChild(xCurrent));
+ }
+
+ if (bGridPacking)
+ {
+ // go back to parent and find the object child and insert this "layout" as a
+ // new child of the object
+ auto xParent = xChild->getParentNode();
+ css::uno::Reference<css::xml::dom::XNode> xObject = GetChildObject(xParent);
+ if (xObject)
+ xObject->appendChild(xNew);
+ }
+
+ xRemoveList.push_back(xChild);
+ }
+ else if (xChild->getNodeName() == "accelerator")
+ {
+ // TODO is anything like this supported anymore in .ui files
+ xRemoveList.push_back(xChild);
+ }
+ else if (xChild->getNodeName() == "relation")
+ {
+ auto xDoc = xChild->getOwnerDocument();
+
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xType = xMap->getNamedItem("type");
+ if (xType->getNodeValue() == "label-for")
+ {
+ // there will be a matching labelled-by which should be sufficient in the gtk4 world
+ xRemoveList.push_back(xChild);
+ }
+ if (xType->getNodeValue() == "controlled-by")
+ {
+ // there will be a matching controller-for converted to -> controls
+ // which should be sufficient in the gtk4 world
+ xRemoveList.push_back(xChild);
+ }
+ else
+ {
+ css::uno::Reference<css::xml::dom::XNode> xTarget = xMap->getNamedItem("target");
+
+ css::uno::Reference<css::xml::dom::XText> xValue
+ = xDoc->createTextNode(xTarget->getNodeValue());
+ xChild->appendChild(xValue);
+
+ css::uno::Reference<css::xml::dom::XAttr> xName = xDoc->createAttribute("name");
+ if (xType->getNodeValue() == "controller-for")
+ xName->setValue("controls");
+ else
+ xName->setValue(xType->getNodeValue());
+
+ css::uno::Reference<css::xml::dom::XElement> xElem(xChild,
+ css::uno::UNO_QUERY_THROW);
+ xElem->setAttributeNode(xName);
+ xElem->removeAttribute("type");
+ xElem->removeAttribute("target");
+ }
+ }
+
+ auto xNextChild = xChild->getNextSibling();
+
+ if (xChild->getNodeName() == "object")
+ {
+ auto xDoc = xChild->getOwnerDocument();
+
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xClass = xMap->getNamedItem("class");
+ OUString sClass(xClass->getNodeValue());
+
+ if (sClass == "GtkMenu")
+ {
+ css::uno::Reference<css::xml::dom::XNode> xId = xMap->getNamedItem("id");
+ OUString sId(xId->getNodeValue() + "-menu-model");
+
+ // <menu id='menubar'>
+ css::uno::Reference<css::xml::dom::XElement> xMenu = xDoc->createElement("menu");
+ css::uno::Reference<css::xml::dom::XAttr> xIdAttr = xDoc->createAttribute("id");
+ xIdAttr->setValue(sId);
+ xMenu->setAttributeNode(xIdAttr);
+ xChild->getParentNode()->insertBefore(xMenu, xChild);
+
+ css::uno::Reference<css::xml::dom::XElement> xSection
+ = xDoc->createElement("section");
+ xMenu->appendChild(xSection);
+
+ css::uno::Reference<css::xml::dom::XNode> xMenuSection(xSection);
+ ConvertMenu(xMenuSection, xChild);
+
+ // now remove GtkMenu contents
+ while (true)
+ {
+ auto xFirstChild = xChild->getFirstChild();
+ if (!xFirstChild.is())
+ break;
+ xChild->removeChild(xFirstChild);
+ }
+
+ // change to GtkPopoverMenu
+ xClass->setNodeValue("GtkPopoverMenu");
+
+ // <property name="menu-model">
+ xChild->appendChild(CreateProperty(xDoc, "menu-model", sId));
+ xChild->appendChild(CreateProperty(xDoc, "visible", "False"));
+ }
+ }
+
+ bool bChildHasIconSize = false;
+ bool bChildHasVisible = false;
+ bool bChildAlwaysShowImage = false;
+ GtkPositionType eChildImagePos = GTK_POS_LEFT;
+ bool bChildUseUnderline = false;
+ bool bChildVertOrientation = false;
+ bool bChildXAlign = false;
+ css::uno::Reference<css::xml::dom::XNode> xChildPropertyLabel;
+ css::uno::Reference<css::xml::dom::XNode> xChildPropertyIconName;
+ if (xChild->hasChildNodes() && xChild != xGeneratedImageChild)
+ {
+ auto aChildRes = Convert3To4(xChild);
+ bChildCanFocus |= aChildRes.m_bChildCanFocus;
+ if (bChildCanFocus && xCantFocus.is())
+ {
+ xNode->removeChild(xCantFocus);
+ xCantFocus.clear();
+ }
+ if (xChild->getNodeName() == "object")
+ {
+ bChildHasVisible = aChildRes.m_bHasVisible;
+ bChildHasIconSize = aChildRes.m_bHasIconSize;
+ bChildAlwaysShowImage = aChildRes.m_bAlwaysShowImage;
+ eChildImagePos = aChildRes.m_eImagePos;
+ bChildUseUnderline = aChildRes.m_bUseUnderline;
+ bChildVertOrientation = aChildRes.m_bVertOrientation;
+ bChildXAlign = aChildRes.m_bXAlign;
+ xChildPropertyLabel = aChildRes.m_xPropertyLabel;
+ xChildPropertyIconName = aChildRes.m_xPropertyIconName;
+ }
+ }
+
+ if (xChild->getNodeName() == "object")
+ {
+ auto xDoc = xChild->getOwnerDocument();
+
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xMap = xChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xClass = xMap->getNamedItem("class");
+ OUString sClass(xClass->getNodeValue());
+
+ auto xInternalChildCandidate = xChild->getParentNode();
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xInternalChildCandidateMap
+ = xInternalChildCandidate->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xInternalChild
+ = xInternalChildCandidateMap->getNamedItem("internal-child");
+
+ // turn default gtk3 invisibility for widget objects into explicit invisible, but ignore internal-children
+ if (!bChildHasVisible && !xInternalChild)
+ {
+ if (sClass == "GtkBox" || sClass == "GtkButton" || sClass == "GtkCalendar"
+ || sClass == "GtkCheckButton" || sClass == "GtkRadioButton"
+ || sClass == "GtkComboBox" || sClass == "GtkComboBoxText"
+ || sClass == "GtkDrawingArea" || sClass == "GtkEntry" || sClass == "GtkExpander"
+ || sClass == "GtkFrame" || sClass == "GtkGrid" || sClass == "GtkImage"
+ || sClass == "GtkLabel" || sClass == "GtkMenuButton" || sClass == "GtkNotebook"
+ || sClass == "GtkOverlay" || sClass == "GtkPaned" || sClass == "GtkProgressBar"
+ || sClass == "GtkScrolledWindow" || sClass == "GtkSeparator"
+ || sClass == "GtkSpinButton" || sClass == "GtkSpinner"
+ || sClass == "GtkTextView" || sClass == "GtkTreeView" || sClass == "GtkViewport"
+ || sClass == "GtkLinkButton" || sClass == "GtkToggleButton"
+ || sClass == "GtkButtonBox")
+
+ {
+ auto xVisible = CreateProperty(xDoc, "visible", "False");
+ insertAsFirstChild(xChild, xVisible);
+ }
+ }
+
+ if (sClass == "GtkButtonBox")
+ {
+ if (xInternalChild && xInternalChild->getNodeValue() == "action_area"
+ && !ToplevelIsMessageDialog(xChild))
+ {
+ xClass->setNodeValue("GtkHeaderBar");
+ auto xSpacingNode = CreateProperty(xDoc, "show-title-buttons", "False");
+ insertAsFirstChild(xChild, xSpacingNode);
+
+ // move the replacement GtkHeaderBar up to before the content_area
+ auto xContentAreaCandidate = xChild->getParentNode();
+ while (xContentAreaCandidate)
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xChildMap
+ = xContentAreaCandidate->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xName
+ = xChildMap->getNamedItem("internal-child");
+ if (xName && xName->getNodeValue() == "content_area")
+ {
+ auto xActionArea = xChild->getParentNode();
+
+ xActionArea->getParentNode()->removeChild(xActionArea);
+
+ css::uno::Reference<css::xml::dom::XAttr> xTypeTitleBar
+ = xDoc->createAttribute("type");
+ xTypeTitleBar->setValue("titlebar");
+ css::uno::Reference<css::xml::dom::XElement> xElem(
+ xActionArea, css::uno::UNO_QUERY_THROW);
+ xElem->setAttributeNode(xTypeTitleBar);
+ xElem->removeAttribute("internal-child");
+
+ xContentAreaCandidate->getParentNode()->insertBefore(
+ xActionArea, xContentAreaCandidate);
+
+ std::vector<named_node> aChildren;
+
+ css::uno::Reference<css::xml::dom::XNode> xTitleChild
+ = xChild->getFirstChild();
+ while (xTitleChild.is())
+ {
+ auto xNextTitleChild = xTitleChild->getNextSibling();
+ if (xTitleChild->getNodeName() == "child")
+ {
+ OUString sNodeId;
+
+ css::uno::Reference<css::xml::dom::XNode> xObject
+ = GetChildObject(xTitleChild);
+ if (xObject)
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xObjectMap
+ = xObject->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xObjectId
+ = xObjectMap->getNamedItem("id");
+ sNodeId = xObjectId->getNodeValue();
+ }
+
+ aChildren.push_back(std::make_pair(xTitleChild, sNodeId));
+ }
+ else if (xTitleChild->getNodeName() == "property")
+ {
+ // remove any <property name="homogeneous"> tag
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xTitleChildMap
+ = xTitleChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xPropName
+ = xTitleChildMap->getNamedItem("name");
+ OUString sPropName(xPropName->getNodeValue().replace('_', '-'));
+ if (sPropName == "homogeneous")
+ xChild->removeChild(xTitleChild);
+ }
+
+ xTitleChild = xNextTitleChild;
+ }
+
+ //sort child order within parent so that we match the platform button order
+ std::stable_sort(aChildren.begin(), aChildren.end(), sortButtonNodes);
+
+ int nNonHelpButtonCount = 0;
+
+ for (const auto& rTitleChild : aChildren)
+ {
+ xChild->removeChild(rTitleChild.first);
+ if (rTitleChild.second != "help")
+ ++nNonHelpButtonCount;
+ }
+
+ std::reverse(aChildren.begin(), aChildren.end());
+
+ for (const auto& rTitleChild : aChildren)
+ {
+ xChild->appendChild(rTitleChild.first);
+
+ css::uno::Reference<css::xml::dom::XElement> xChildElem(
+ rTitleChild.first, css::uno::UNO_QUERY_THROW);
+ if (!xChildElem->hasAttribute("type"))
+ {
+ // turn parent tag of <child> into <child type="end"> except for cancel/close which we'll
+ // put at start unless there is nothing at end
+ css::uno::Reference<css::xml::dom::XAttr> xTypeEnd
+ = xDoc->createAttribute("type");
+ if (nNonHelpButtonCount >= 2
+ && (rTitleChild.second == "cancel"
+ || rTitleChild.second == "close"))
+ xTypeEnd->setValue("start");
+ else
+ xTypeEnd->setValue("end");
+ xChildElem->setAttributeNode(xTypeEnd);
+ }
+ }
+
+ auto xUseHeaderBar = CreateProperty(xDoc, "use-header-bar", "1");
+ SetPropertyOnTopLevel(xContentAreaCandidate, xUseHeaderBar);
+
+ break;
+ }
+ xContentAreaCandidate = xContentAreaCandidate->getParentNode();
+ }
+ }
+ else // GtkMessageDialog
+ xClass->setNodeValue("GtkBox");
+ }
+ else if (sClass == "GtkToolbar")
+ {
+ xClass->setNodeValue("GtkBox");
+ css::uno::Reference<css::xml::dom::XElement> xStyle = xDoc->createElement("style");
+ css::uno::Reference<css::xml::dom::XElement> xToolbarClass
+ = xDoc->createElement("class");
+ css::uno::Reference<css::xml::dom::XAttr> xPropName = xDoc->createAttribute("name");
+ xPropName->setValue("toolbar");
+ xToolbarClass->setAttributeNode(xPropName);
+ xStyle->appendChild(xToolbarClass);
+ xChild->appendChild(xStyle);
+ }
+ else if (sClass == "GtkToolButton")
+ {
+ xClass->setNodeValue("GtkButton");
+ }
+ else if (sClass == "GtkToolItem")
+ {
+ xClass->setNodeValue("GtkBox");
+ }
+ else if (sClass == "GtkMenuToolButton")
+ {
+ xClass->setNodeValue("GtkMenuButton");
+ if (gtk_get_minor_version() >= 4)
+ {
+ auto xAlwaysShowArrow = CreateProperty(xDoc, "always-show-arrow", "True");
+ insertAsFirstChild(xChild, xAlwaysShowArrow);
+ }
+ }
+ else if (sClass == "GtkRadioToolButton")
+ {
+ xClass->setNodeValue("GtkCheckButton");
+ }
+ else if (sClass == "GtkToggleToolButton")
+ {
+ xClass->setNodeValue("GtkToggleButton");
+ }
+ else if (sClass == "GtkSeparatorToolItem")
+ {
+ xClass->setNodeValue("GtkSeparator");
+ }
+ else if (sClass == "GtkBox")
+ {
+ // reverse the order of the pack-type=end widgets
+ std::vector<css::uno::Reference<css::xml::dom::XNode>> aPackEnds;
+ std::vector<css::uno::Reference<css::xml::dom::XNode>> aPackStarts;
+ css::uno::Reference<css::xml::dom::XNode> xBoxChild = xChild->getFirstChild();
+ while (xBoxChild.is())
+ {
+ auto xNextBoxChild = xBoxChild->getNextSibling();
+
+ if (xBoxChild->getNodeName() == "child")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xBoxChildMap
+ = xBoxChild->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xType
+ = xBoxChildMap->getNamedItem("type");
+ if (xType && xType->getNodeValue() == "end")
+ aPackEnds.push_back(xChild->removeChild(xBoxChild));
+ else
+ aPackStarts.push_back(xBoxChild);
+ }
+
+ xBoxChild = xNextBoxChild;
+ }
+
+ if (!aPackEnds.empty())
+ {
+ std::reverse(aPackEnds.begin(), aPackEnds.end());
+
+ if (!bChildVertOrientation)
+ {
+ bool bHasStartObject = false;
+ bool bLastStartExpands = false;
+ if (!aPackStarts.empty())
+ {
+ css::uno::Reference<css::xml::dom::XNode> xLastStartObject;
+ for (auto it = aPackStarts.rbegin(); it != aPackStarts.rend(); ++it)
+ {
+ xLastStartObject = GetChildObject(*it);
+ if (xLastStartObject.is())
+ {
+ bHasStartObject = true;
+ for (css::uno::Reference<css::xml::dom::XNode> xExpandCandidate
+ = xLastStartObject->getFirstChild();
+ xExpandCandidate.is();
+ xExpandCandidate = xExpandCandidate->getNextSibling())
+ {
+ if (xExpandCandidate->getNodeName() == "property")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap>
+ xExpandMap = xExpandCandidate->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xName
+ = xExpandMap->getNamedItem("name");
+ OUString sPropName(xName->getNodeValue());
+ if (sPropName == "hexpand")
+ {
+ bLastStartExpands
+ = toBool(xExpandCandidate->getFirstChild()
+ ->getNodeValue());
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ if (bHasStartObject && !bLastStartExpands)
+ {
+ auto xAlign = CreateProperty(xDoc, "halign", "end");
+ insertAsFirstChild(GetChildObject(aPackEnds[0]), xAlign);
+ auto xExpand = CreateProperty(xDoc, "hexpand", "True");
+ insertAsFirstChild(GetChildObject(aPackEnds[0]), xExpand);
+ }
+ }
+
+ for (auto& xPackEnd : aPackEnds)
+ xChild->appendChild(xPackEnd);
+ }
+ }
+ else if (sClass == "GtkRadioButton")
+ {
+ xClass->setNodeValue("GtkCheckButton");
+ }
+ else if (sClass == "GtkImage")
+ {
+ /* a) keep symbolic icon-names as GtkImage, e.g. writer, format, columns, next/prev
+ buttons
+ b) assume that an explicit icon-size represents a request for a scaled icon
+ so keep those as GtkImage. e.g. hyperlink dialog notebook tab images and
+ calc paste special button images
+ c) turn everything else into a GtkPicture, e.g. help, about. If a GtkPicture
+ ends up used to display just a simple icon that's generally not a problem.
+ */
+ bool bKeepAsImage = false;
+ if (bChildHasIconSize)
+ bKeepAsImage = true;
+ else if (xChildPropertyIconName.is())
+ {
+ OUString sIconName(xChildPropertyIconName->getFirstChild()->getNodeValue());
+ bool bHasSymbolicIconName = IsAllowedBuiltInIcon(sIconName);
+ if (bHasSymbolicIconName)
+ {
+ if (sIconName != "missing-image")
+ bKeepAsImage = true;
+ else
+ {
+ // If the symbolic icon-name is missing-image then decide to make
+ // it a GtkPicture if it has a parent widget and keep it as GtkImage
+ // if it has just the root "interface" as parent.
+ // for e.g. view, user interface
+ css::uno::Reference<css::xml::dom::XNode> xParent
+ = xChild->getParentNode();
+ bKeepAsImage = xParent->getNodeName() == "interface";
+ if (!bKeepAsImage)
+ xChild->removeChild(xChildPropertyIconName);
+ }
+ }
+ else
+ {
+ // private:graphicrepository/ would be turned by gio (?) into private:///graphicrepository/
+ // so use private:///graphicrepository/ here. At the moment we just want this to be transported
+ // as-is to postprocess_widget. Though it might be nice to register a protocol handler with gio
+ // to avoid us doing the load in that second pass.
+ auto xUri = CreateProperty(xDoc, "file",
+ "private:///graphicrepository/" + sIconName);
+ xChild->insertBefore(xUri, xChildPropertyIconName);
+ // calc, insert, header and footer, custom header menubutton icon
+ auto xCanShrink = CreateProperty(xDoc, "can-shrink", "False");
+ xChild->insertBefore(xCanShrink, xChildPropertyIconName);
+ xChild->removeChild(xChildPropertyIconName);
+ }
+ }
+ if (!bKeepAsImage)
+ xClass->setNodeValue("GtkPicture");
+ }
+ else if (sClass == "GtkPopover" && !bHasVisible)
+ {
+ auto xVisible = CreateProperty(xDoc, "visible", "False");
+ insertAsFirstChild(xChild, xVisible);
+ }
+ else if (sClass == "AtkObject")
+ {
+ css::uno::Reference<css::xml::dom::XNode> xParentObject = xNode->getParentNode();
+ css::uno::Reference<css::xml::dom::XElement> xAccessibility
+ = xDoc->createElement("accessibility");
+ xParentObject->insertBefore(xAccessibility, xNode);
+
+ // move the converted old AtkObject:: properties into a new accessibility parent
+ for (css::uno::Reference<css::xml::dom::XNode> xCurrent = xChild->getFirstChild();
+ xCurrent.is(); xCurrent = xChild->getFirstChild())
+ {
+ xAccessibility->appendChild(xChild->removeChild(xCurrent));
+ }
+ }
+
+ // only create the child box for GtkButton/GtkToggleButton
+ if (bChildAlwaysShowImage)
+ {
+ auto xImageCandidateNode = xChild->getLastChild();
+ if (xImageCandidateNode && xImageCandidateNode->getNodeName() != "child")
+ xImageCandidateNode.clear();
+ if (xImageCandidateNode)
+ xChild->removeChild(xImageCandidateNode);
+
+ // for GtkMenuButton if this is a gearmenu with just an icon
+ // then "icon-name" is used for the indicator and there is
+ // expected to be no text. If there is a GtkPicture then treat
+ // this like a GtkButton and presumably it's a ToggleMenuButton
+ // and the relocation of contents happens in the builder
+ if (sClass == "GtkMenuButton")
+ {
+ bChildAlwaysShowImage = false;
+ if (xImageCandidateNode)
+ {
+ bChildAlwaysShowImage = true;
+ auto xImageObject = GetChildObject(xImageCandidateNode);
+ auto xProp = xImageObject->getFirstChild();
+ while (xProp.is())
+ {
+ if (xProp->getNodeName() == "property")
+ {
+ css::uno::Reference<css::xml::dom::XNamedNodeMap> xPropMap
+ = xProp->getAttributes();
+ css::uno::Reference<css::xml::dom::XNode> xPropName
+ = xPropMap->getNamedItem("name");
+ OUString sPropName(xPropName->getNodeValue().replace('_', '-'));
+ if (sPropName == "icon-name")
+ {
+ OUString sIconName(xProp->getFirstChild()->getNodeValue());
+ auto xIconName = CreateProperty(xDoc, "icon-name", sIconName);
+ insertAsFirstChild(xChild, xIconName);
+ bChildAlwaysShowImage = false;
+ break;
+ }
+ }
+
+ xProp = xProp->getNextSibling();
+ }
+ }
+ }
+
+ if (bChildAlwaysShowImage)
+ {
+ css::uno::Reference<css::xml::dom::XElement> xNewChildNode
+ = xDoc->createElement("child");
+ css::uno::Reference<css::xml::dom::XElement> xNewObjectNode
+ = xDoc->createElement("object");
+ css::uno::Reference<css::xml::dom::XAttr> xBoxClassName
+ = xDoc->createAttribute("class");
+ xBoxClassName->setValue("GtkBox");
+ xNewObjectNode->setAttributeNode(xBoxClassName);
+
+ if (eChildImagePos == GTK_POS_TOP || eChildImagePos == GTK_POS_BOTTOM)
+ {
+ auto xOrientation = CreateProperty(xDoc, "orientation", "vertical");
+ xNewObjectNode->appendChild(xOrientation);
+ }
+
+ xNewObjectNode->appendChild(CreateProperty(xDoc, "spacing", "6"));
+ if (!bChildXAlign)
+ xNewObjectNode->appendChild(CreateProperty(xDoc, "halign", "center"));
+
+ xNewChildNode->appendChild(xNewObjectNode);
+
+ xChild->appendChild(xNewChildNode);
+
+ css::uno::Reference<css::xml::dom::XElement> xNewLabelChildNode
+ = xDoc->createElement("child");
+ css::uno::Reference<css::xml::dom::XElement> xNewChildObjectNode
+ = xDoc->createElement("object");
+ css::uno::Reference<css::xml::dom::XAttr> xLabelClassName
+ = xDoc->createAttribute("class");
+ xLabelClassName->setValue("GtkLabel");
+ xNewChildObjectNode->setAttributeNode(xLabelClassName);
+ if (xChildPropertyLabel)
+ {
+ xNewChildObjectNode->appendChild(
+ xChildPropertyLabel->getParentNode()->removeChild(xChildPropertyLabel));
+ }
+ else
+ {
+ auto xNotVisible = CreateProperty(xDoc, "visible", "False");
+ xNewChildObjectNode->appendChild(xNotVisible);
+ }
+ if (bChildUseUnderline)
+ {
+ auto xUseUnderline = CreateProperty(xDoc, "use-underline", "True");
+ xNewChildObjectNode->appendChild(xUseUnderline);
+ }
+ xNewLabelChildNode->appendChild(xNewChildObjectNode);
+
+ if (eChildImagePos == GTK_POS_LEFT || eChildImagePos == GTK_POS_TOP)
+ {
+ if (xImageCandidateNode)
+ xNewObjectNode->appendChild(xImageCandidateNode);
+ xNewObjectNode->appendChild(xNewLabelChildNode);
+ }
+ else
+ {
+ xNewObjectNode->appendChild(xNewLabelChildNode);
+ if (xImageCandidateNode)
+ xNewObjectNode->appendChild(xImageCandidateNode);
+ }
+ }
+ }
+ }
+
+ xChild = xNextChild;
+ }
+
+ if (!sBorderWidth.isEmpty())
+ AddBorderAsMargins(xNode, sBorderWidth);
+
+ for (auto& xRemove : xRemoveList)
+ xNode->removeChild(xRemove);
+
+ // https://gitlab.gnome.org/GNOME/gtk/-/issues/4041 double encode ampersands if use-underline is used
+ if (gtk_check_version(4, 3, 2) != nullptr)
+ {
+ if (xPropertyLabel)
+ {
+ auto xLabelText = xPropertyLabel->getFirstChild();
+ if (xLabelText.is())
+ {
+ OString sText = xLabelText->getNodeValue().toUtf8();
+ gchar* pText = g_markup_escape_text(sText.getStr(), sText.getLength());
+ xLabelText->setNodeValue(OUString(pText, strlen(pText), RTL_TEXTENCODING_UTF8));
+ g_free(pText);
+ }
+ }
+ }
+
+ return ConvertResult(bChildCanFocus, bHasVisible, bHasIconSize, bAlwaysShowImage, bUseUnderline,
+ bVertOrientation, bXAlign, eImagePos, xPropertyLabel, xPropertyIconName);
+}
+
+std::once_flag cairo_surface_type_flag;
+
+void ensure_cairo_surface_type()
+{
+ // cairo_gobject_surface_get_type needs to be called at least once for
+ // g_type_from_name to be able to resolve "CairoSurface". In gtk3 there was fallback
+ // mechanism to attempt to resolve such "missing" types which is not in place for
+ // gtk4 so ensure it can be found explicitly
+ std::call_once(cairo_surface_type_flag, []() { cairo_gobject_surface_get_type(); });
+}
+}
+
+void builder_add_from_gtk3_file(GtkBuilder* pBuilder, const OUString& rUri)
+{
+ GError* err = nullptr;
+
+ // load the xml
+ css::uno::Reference<css::uno::XComponentContext> xContext
+ = ::comphelper::getProcessComponentContext();
+ css::uno::Reference<css::xml::dom::XDocumentBuilder> xBuilder
+ = css::xml::dom::DocumentBuilder::create(xContext);
+ css::uno::Reference<css::xml::dom::XDocument> xDocument = xBuilder->parseURI(rUri);
+
+ // convert it from gtk3 to gtk4
+ Convert3To4(xDocument);
+
+ css::uno::Reference<css::beans::XPropertySet> xTempFile(css::io::TempFile::create(xContext),
+ css::uno::UNO_QUERY_THROW);
+ css::uno::Reference<css::io::XStream> xTempStream(xTempFile, css::uno::UNO_QUERY_THROW);
+ xTempFile->setPropertyValue("RemoveFile", css::uno::Any(false));
+
+ // serialize it back to xml
+ css::uno::Reference<css::xml::sax::XSAXSerializable> xSerializer(xDocument,
+ css::uno::UNO_QUERY_THROW);
+ css::uno::Reference<css::xml::sax::XWriter> xWriter = css::xml::sax::Writer::create(xContext);
+ css::uno::Reference<css::io::XOutputStream> xTempOut = xTempStream->getOutputStream();
+ xWriter->setOutputStream(xTempOut);
+ xSerializer->serialize(
+ css::uno::Reference<css::xml::sax::XDocumentHandler>(xWriter, css::uno::UNO_QUERY_THROW),
+ css::uno::Sequence<css::beans::StringPair>());
+
+ ensure_cairo_surface_type();
+
+ // feed it to GtkBuilder
+ css::uno::Reference<css::io::XSeekable> xTempSeek(xTempStream, css::uno::UNO_QUERY_THROW);
+ xTempSeek->seek(0);
+ auto xInput = xTempStream->getInputStream();
+ css::uno::Sequence<sal_Int8> bytes;
+ sal_Int32 nToRead = xInput->available();
+ while (true)
+ {
+ sal_Int32 nRead = xInput->readBytes(bytes, std::max<sal_Int32>(nToRead, 4096));
+ if (!nRead)
+ break;
+ // fprintf(stderr, "text is %s\n", reinterpret_cast<const gchar*>(bytes.getArray()));
+ bool rc = gtk_builder_add_from_string(
+ pBuilder, reinterpret_cast<const gchar*>(bytes.getArray()), nRead, &err);
+ if (!rc)
+ {
+ SAL_WARN("vcl.gtk",
+ "GtkInstanceBuilder: error when calling gtk_builder_add_from_string: "
+ << err->message);
+ g_error_free(err);
+ }
+ assert(rc && "could not load UI file");
+ // in the real world the first loop has read the entire file because its all 'available' without blocking
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/convert3to4.hxx b/vcl/unx/gtk4/convert3to4.hxx
new file mode 100644
index 000000000..d75d9fd91
--- /dev/null
+++ b/vcl/unx/gtk4/convert3to4.hxx
@@ -0,0 +1,16 @@
+/* -*- 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 <rtl/ustring.hxx>
+#include <gtk/gtk.h>
+
+// convert gtk3 .ui to gtk4 and load it
+void builder_add_from_gtk3_file(GtkBuilder* pBuilder, const OUString& rUri);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/customcellrenderer.cxx b/vcl/unx/gtk4/customcellrenderer.cxx
new file mode 100644
index 000000000..aebe2c89e
--- /dev/null
+++ b/vcl/unx/gtk4/customcellrenderer.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 "../gtk3/customcellrenderer.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/unx/gtk4/fpicker/SalGtkFilePicker.cxx b/vcl/unx/gtk4/fpicker/SalGtkFilePicker.cxx
new file mode 100644
index 000000000..a820b4995
--- /dev/null
+++ b/vcl/unx/gtk4/fpicker/SalGtkFilePicker.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/fpicker/SalGtkFilePicker.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/fpicker/SalGtkFilePicker.hxx b/vcl/unx/gtk4/fpicker/SalGtkFilePicker.hxx
new file mode 100644
index 000000000..44ea7bd44
--- /dev/null
+++ b/vcl/unx/gtk4/fpicker/SalGtkFilePicker.hxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/fpicker/SalGtkFilePicker.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.cxx b/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.cxx
new file mode 100644
index 000000000..41dfdf021
--- /dev/null
+++ b/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/fpicker/SalGtkFolderPicker.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.hxx b/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.hxx
new file mode 100644
index 000000000..129a69933
--- /dev/null
+++ b/vcl/unx/gtk4/fpicker/SalGtkFolderPicker.hxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/fpicker/SalGtkFolderPicker.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/fpicker/SalGtkPicker.cxx b/vcl/unx/gtk4/fpicker/SalGtkPicker.cxx
new file mode 100644
index 000000000..159ef4704
--- /dev/null
+++ b/vcl/unx/gtk4/fpicker/SalGtkPicker.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/fpicker/SalGtkPicker.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/fpicker/SalGtkPicker.hxx b/vcl/unx/gtk4/fpicker/SalGtkPicker.hxx
new file mode 100644
index 000000000..0432004a0
--- /dev/null
+++ b/vcl/unx/gtk4/fpicker/SalGtkPicker.hxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/fpicker/SalGtkPicker.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/fpicker/eventnotification.hxx b/vcl/unx/gtk4/fpicker/eventnotification.hxx
new file mode 100644
index 000000000..b1e16bd65
--- /dev/null
+++ b/vcl/unx/gtk4/fpicker/eventnotification.hxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/fpicker/eventnotification.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/fpicker/resourceprovider.cxx b/vcl/unx/gtk4/fpicker/resourceprovider.cxx
new file mode 100644
index 000000000..d4251cc94
--- /dev/null
+++ b/vcl/unx/gtk4/fpicker/resourceprovider.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/fpicker/resourceprovider.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gloactiongroup.cxx b/vcl/unx/gtk4/gloactiongroup.cxx
new file mode 100644
index 000000000..f147af283
--- /dev/null
+++ b/vcl/unx/gtk4/gloactiongroup.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../gtk3/gloactiongroup.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/glomenu.cxx b/vcl/unx/gtk4/glomenu.cxx
new file mode 100644
index 000000000..cae9bc03a
--- /dev/null
+++ b/vcl/unx/gtk4/glomenu.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../gtk3/glomenu.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtkcairo.cxx b/vcl/unx/gtk4/gtkcairo.cxx
new file mode 100644
index 000000000..3178fdea0
--- /dev/null
+++ b/vcl/unx/gtk4/gtkcairo.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../gtk3/gtkcairo.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtkcairo.hxx b/vcl/unx/gtk4/gtkcairo.hxx
new file mode 100644
index 000000000..97f63101d
--- /dev/null
+++ b/vcl/unx/gtk4/gtkcairo.hxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../../gtk3/gtkdata.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtkdata.cxx b/vcl/unx/gtk4/gtkdata.cxx
new file mode 100644
index 000000000..41d85217c
--- /dev/null
+++ b/vcl/unx/gtk4/gtkdata.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../gtk3/gtkdata.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtkframe.cxx b/vcl/unx/gtk4/gtkframe.cxx
new file mode 100644
index 000000000..e446ba8a5
--- /dev/null
+++ b/vcl/unx/gtk4/gtkframe.cxx
@@ -0,0 +1,14 @@
+/* -*- 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 "transferableprovider.hxx"
+
+#include "../gtk3/gtkframe.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtkinst.cxx b/vcl/unx/gtk4/gtkinst.cxx
new file mode 100644
index 000000000..658c01233
--- /dev/null
+++ b/vcl/unx/gtk4/gtkinst.cxx
@@ -0,0 +1,21 @@
+/* -*- 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/.
+ */
+
+// make gtk4 plug advertise correctly as gtk4
+#define GTK_TOOLKIT_NAME "gtk4"
+
+#include "convert3to4.hxx"
+#include "notifyinglayout.hxx"
+#include "surfacecellrenderer.hxx"
+#include "surfacepaintable.hxx"
+#include "transferableprovider.hxx"
+
+#include "../gtk3/gtkinst.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtkobject.cxx b/vcl/unx/gtk4/gtkobject.cxx
new file mode 100644
index 000000000..8eb5dcf1a
--- /dev/null
+++ b/vcl/unx/gtk4/gtkobject.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../gtk3/gtkobject.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtksalmenu.cxx b/vcl/unx/gtk4/gtksalmenu.cxx
new file mode 100644
index 000000000..895078124
--- /dev/null
+++ b/vcl/unx/gtk4/gtksalmenu.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../gtk3/gtksalmenu.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/gtksys.cxx b/vcl/unx/gtk4/gtksys.cxx
new file mode 100644
index 000000000..dfdcf0a7c
--- /dev/null
+++ b/vcl/unx/gtk4/gtksys.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../gtk3/gtksys.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/hudawareness.cxx b/vcl/unx/gtk4/hudawareness.cxx
new file mode 100644
index 000000000..b2683b2bd
--- /dev/null
+++ b/vcl/unx/gtk4/hudawareness.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../gtk3/hudawareness.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/notifyinglayout.cxx b/vcl/unx/gtk4/notifyinglayout.cxx
new file mode 100644
index 000000000..46b9a2d95
--- /dev/null
+++ b/vcl/unx/gtk4/notifyinglayout.cxx
@@ -0,0 +1,88 @@
+/* -*- 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 "notifyinglayout.hxx"
+
+struct _NotifyingLayout
+{
+ GtkLayoutManager parent_instance;
+
+ GtkWidget* m_pWidget;
+ GtkLayoutManager* m_pOrigManager;
+ Link<void*, void> m_aLink;
+};
+
+G_DEFINE_TYPE(NotifyingLayout, notifying_layout, GTK_TYPE_LAYOUT_MANAGER)
+
+static void notifying_layout_measure(GtkLayoutManager* pLayoutManager, GtkWidget* widget,
+ GtkOrientation orientation, int for_size, int* minimum,
+ int* natural, int* minimum_baseline, int* natural_baseline)
+{
+ NotifyingLayout* self = NOTIFYING_LAYOUT(pLayoutManager);
+ GtkLayoutManagerClass* pKlass
+ = GTK_LAYOUT_MANAGER_CLASS(G_OBJECT_GET_CLASS(self->m_pOrigManager));
+ pKlass->measure(self->m_pOrigManager, widget, orientation, for_size, minimum, natural,
+ minimum_baseline, natural_baseline);
+}
+
+static void notifying_layout_allocate(GtkLayoutManager* pLayoutManager, GtkWidget* widget,
+ int width, int height, int baseline)
+{
+ NotifyingLayout* self = NOTIFYING_LAYOUT(pLayoutManager);
+ GtkLayoutManagerClass* pKlass
+ = GTK_LAYOUT_MANAGER_CLASS(G_OBJECT_GET_CLASS(self->m_pOrigManager));
+ pKlass->allocate(self->m_pOrigManager, widget, width, height, baseline);
+ self->m_aLink.Call(nullptr);
+}
+
+static GtkSizeRequestMode notifying_layout_get_request_mode(GtkLayoutManager* pLayoutManager,
+ GtkWidget* widget)
+{
+ NotifyingLayout* self = NOTIFYING_LAYOUT(pLayoutManager);
+ GtkLayoutManagerClass* pKlass
+ = GTK_LAYOUT_MANAGER_CLASS(G_OBJECT_GET_CLASS(self->m_pOrigManager));
+ return pKlass->get_request_mode(self->m_pOrigManager, widget);
+}
+
+static void notifying_layout_class_init(NotifyingLayoutClass* klass)
+{
+ GtkLayoutManagerClass* layout_class = GTK_LAYOUT_MANAGER_CLASS(klass);
+
+ layout_class->get_request_mode = notifying_layout_get_request_mode;
+ layout_class->measure = notifying_layout_measure;
+ layout_class->allocate = notifying_layout_allocate;
+}
+
+static void notifying_layout_init(NotifyingLayout* self)
+{
+ self->m_pWidget = nullptr;
+ self->m_pOrigManager = nullptr;
+
+ // prevent loplugin:unreffun firing on macro generated function
+ (void)notifying_layout_get_instance_private(self);
+}
+
+void notifying_layout_start_watch(NotifyingLayout* self, GtkWidget* pWidget,
+ const Link<void*, void>& rLink)
+{
+ self->m_pWidget = pWidget;
+ self->m_aLink = rLink;
+
+ self->m_pOrigManager = gtk_widget_get_layout_manager(self->m_pWidget);
+ g_object_ref(self->m_pOrigManager);
+
+ gtk_widget_set_layout_manager(pWidget, GTK_LAYOUT_MANAGER(self));
+}
+
+void notifying_layout_stop_watch(NotifyingLayout* self)
+{
+ gtk_widget_set_layout_manager(self->m_pWidget, self->m_pOrigManager);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/notifyinglayout.hxx b/vcl/unx/gtk4/notifyinglayout.hxx
new file mode 100644
index 000000000..a717a3061
--- /dev/null
+++ b/vcl/unx/gtk4/notifyinglayout.hxx
@@ -0,0 +1,36 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <tools/link.hxx>
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE(NotifyingLayout, notifying_layout, NOTIFYING, LAYOUT, GtkLayoutManager)
+
+/*
+ Replace the existing GtkLayoutManager of pWidget with pLayout instead which will
+ forward all requests to the original GtkLayoutManager but additionally call
+ rLink when a size is allocated to the pWidget.
+
+ This provides a workaround for the removal of the size-allocate signal in gtk4
+*/
+void notifying_layout_start_watch(NotifyingLayout* pLayout, GtkWidget* pWidget,
+ const Link<void*, void>& rLink);
+
+/*
+ Undo a previous notifying_layout_start_watch.
+*/
+void notifying_layout_stop_watch(NotifyingLayout* pLayout);
+
+G_END_DECLS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/salnativewidgets-gtk.cxx b/vcl/unx/gtk4/salnativewidgets-gtk.cxx
new file mode 100644
index 000000000..ed036e98c
--- /dev/null
+++ b/vcl/unx/gtk4/salnativewidgets-gtk.cxx
@@ -0,0 +1,12 @@
+/* -*- 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 "../gtk3/salnativewidgets-gtk.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/surfacecellrenderer.cxx b/vcl/unx/gtk4/surfacecellrenderer.cxx
new file mode 100644
index 000000000..2ae0782b9
--- /dev/null
+++ b/vcl/unx/gtk4/surfacecellrenderer.cxx
@@ -0,0 +1,241 @@
+/* -*- 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 <vcl/svapp.hxx>
+#include "surfacecellrenderer.hxx"
+#include <cairo/cairo-gobject.h>
+
+namespace
+{
+struct _SurfaceCellRendererClass : public GtkCellRendererClass
+{
+};
+
+enum
+{
+ PROP_SURFACE = 10000,
+};
+}
+
+G_DEFINE_TYPE(SurfaceCellRenderer, surface_cell_renderer, GTK_TYPE_CELL_RENDERER)
+
+static void surface_cell_renderer_init(SurfaceCellRenderer* self)
+{
+ self->surface = nullptr;
+ // prevent loplugin:unreffun firing on macro generated function
+ (void)surface_cell_renderer_get_instance_private(self);
+}
+
+static void surface_cell_renderer_get_property(GObject* object, guint param_id, GValue* value,
+ GParamSpec* pspec)
+{
+ SurfaceCellRenderer* cellsurface = SURFACE_CELL_RENDERER(object);
+
+ switch (param_id)
+ {
+ case PROP_SURFACE:
+ g_value_set_boxed(value, cellsurface->surface);
+ break;
+ default:
+ G_OBJECT_CLASS(surface_cell_renderer_parent_class)
+ ->get_property(object, param_id, value, pspec);
+ break;
+ }
+}
+
+static void surface_cell_renderer_set_property(GObject* object, guint param_id, const GValue* value,
+ GParamSpec* pspec)
+{
+ SurfaceCellRenderer* cellsurface = SURFACE_CELL_RENDERER(object);
+
+ switch (param_id)
+ {
+ case PROP_SURFACE:
+ if (cellsurface->surface)
+ cairo_surface_destroy(cellsurface->surface);
+ cellsurface->surface = static_cast<cairo_surface_t*>(g_value_get_boxed(value));
+ if (cellsurface->surface)
+ cairo_surface_reference(cellsurface->surface);
+ break;
+ default:
+ G_OBJECT_CLASS(surface_cell_renderer_parent_class)
+ ->set_property(object, param_id, value, pspec);
+ break;
+ }
+}
+
+static bool surface_cell_renderer_get_preferred_size(GtkCellRenderer* cell,
+ GtkOrientation orientation, gint* minimum_size,
+ gint* natural_size);
+
+static void surface_cell_renderer_snapshot(GtkCellRenderer* cell, GtkSnapshot* snapshot,
+ GtkWidget* widget, const GdkRectangle* background_area,
+ const GdkRectangle* cell_area,
+ GtkCellRendererState flags);
+
+static void surface_cell_renderer_render(GtkCellRenderer* cell, cairo_t* cr, GtkWidget* widget,
+ const GdkRectangle* background_area,
+ const GdkRectangle* cell_area, GtkCellRendererState flags);
+
+static void surface_cell_renderer_finalize(GObject* object)
+{
+ SurfaceCellRenderer* cellsurface = SURFACE_CELL_RENDERER(object);
+
+ if (cellsurface->surface)
+ cairo_surface_destroy(cellsurface->surface);
+
+ G_OBJECT_CLASS(surface_cell_renderer_parent_class)->finalize(object);
+}
+
+static void surface_cell_renderer_get_preferred_width(GtkCellRenderer* cell, GtkWidget* widget,
+ gint* minimum_size, gint* natural_size)
+{
+ if (!surface_cell_renderer_get_preferred_size(cell, GTK_ORIENTATION_HORIZONTAL, minimum_size,
+ natural_size))
+ {
+ // fallback to parent if we're empty
+ GTK_CELL_RENDERER_CLASS(surface_cell_renderer_parent_class)
+ ->get_preferred_width(cell, widget, minimum_size, natural_size);
+ }
+}
+
+static void surface_cell_renderer_get_preferred_height(GtkCellRenderer* cell, GtkWidget* widget,
+ gint* minimum_size, gint* natural_size)
+{
+ if (!surface_cell_renderer_get_preferred_size(cell, GTK_ORIENTATION_VERTICAL, minimum_size,
+ natural_size))
+ {
+ // fallback to parent if we're empty
+ GTK_CELL_RENDERER_CLASS(surface_cell_renderer_parent_class)
+ ->get_preferred_height(cell, widget, minimum_size, natural_size);
+ }
+}
+
+static void surface_cell_renderer_get_preferred_height_for_width(GtkCellRenderer* cell,
+ GtkWidget* widget, gint /*width*/,
+ gint* minimum_height,
+ gint* natural_height)
+{
+ gtk_cell_renderer_get_preferred_height(cell, widget, minimum_height, natural_height);
+}
+
+static void surface_cell_renderer_get_preferred_width_for_height(GtkCellRenderer* cell,
+ GtkWidget* widget, gint /*height*/,
+ gint* minimum_width,
+ gint* natural_width)
+{
+ gtk_cell_renderer_get_preferred_width(cell, widget, minimum_width, natural_width);
+}
+
+void surface_cell_renderer_class_init(SurfaceCellRendererClass* klass)
+{
+ GtkCellRendererClass* cell_class = GTK_CELL_RENDERER_CLASS(klass);
+ GObjectClass* object_class = G_OBJECT_CLASS(klass);
+
+ /* Hook up functions to set and get our custom cell renderer properties */
+ object_class->get_property = surface_cell_renderer_get_property;
+ object_class->set_property = surface_cell_renderer_set_property;
+
+ surface_cell_renderer_parent_class = g_type_class_peek_parent(klass);
+ object_class->finalize = surface_cell_renderer_finalize;
+
+ cell_class->get_preferred_width = surface_cell_renderer_get_preferred_width;
+ cell_class->get_preferred_height = surface_cell_renderer_get_preferred_height;
+ cell_class->get_preferred_width_for_height
+ = surface_cell_renderer_get_preferred_width_for_height;
+ cell_class->get_preferred_height_for_width
+ = surface_cell_renderer_get_preferred_height_for_width;
+
+ cell_class->snapshot = surface_cell_renderer_snapshot;
+
+ g_object_class_install_property(
+ object_class, PROP_SURFACE,
+ g_param_spec_boxed("surface", "Surface", "The cairo surface to render",
+ CAIRO_GOBJECT_TYPE_SURFACE, G_PARAM_READWRITE));
+}
+
+GtkCellRenderer* surface_cell_renderer_new()
+{
+ return GTK_CELL_RENDERER(g_object_new(SURFACE_TYPE_CELL_RENDERER, nullptr));
+}
+
+static void get_surface_size(cairo_surface_t* pSurface, int& rWidth, int& rHeight)
+{
+ double x1, x2, y1, y2;
+ cairo_t* cr = cairo_create(pSurface);
+ cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
+ cairo_destroy(cr);
+
+ rWidth = x2 - x1;
+ rHeight = y2 - y1;
+}
+
+bool surface_cell_renderer_get_preferred_size(GtkCellRenderer* cell, GtkOrientation orientation,
+ gint* minimum_size, gint* natural_size)
+{
+ SurfaceCellRenderer* cellsurface = SURFACE_CELL_RENDERER(cell);
+
+ int nWidth = 0;
+ int nHeight = 0;
+
+ if (cellsurface->surface)
+ get_surface_size(cellsurface->surface, nWidth, nHeight);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (minimum_size)
+ *minimum_size = nWidth;
+
+ if (natural_size)
+ *natural_size = nWidth;
+ }
+ else
+ {
+ if (minimum_size)
+ *minimum_size = nHeight;
+
+ if (natural_size)
+ *natural_size = nHeight;
+ }
+
+ return true;
+}
+
+void surface_cell_renderer_render(GtkCellRenderer* cell, cairo_t* cr, GtkWidget* /*widget*/,
+ const GdkRectangle* /*background_area*/,
+ const GdkRectangle* cell_area, GtkCellRendererState /*flags*/)
+{
+ SurfaceCellRenderer* cellsurface = SURFACE_CELL_RENDERER(cell);
+ if (!cellsurface->surface)
+ return;
+
+ int nWidth, nHeight;
+ get_surface_size(cellsurface->surface, nWidth, nHeight);
+ int nXOffset = (cell_area->width - nWidth) / 2;
+ int nYOffset = (cell_area->height - nHeight) / 2;
+
+ cairo_set_source_surface(cr, cellsurface->surface, cell_area->x + nXOffset,
+ cell_area->y + nYOffset);
+ cairo_paint(cr);
+}
+
+static void surface_cell_renderer_snapshot(GtkCellRenderer* cell, GtkSnapshot* snapshot,
+ GtkWidget* widget, const GdkRectangle* background_area,
+ const GdkRectangle* cell_area,
+ GtkCellRendererState flags)
+{
+ graphene_rect_t rect = GRAPHENE_RECT_INIT(
+ static_cast<float>(cell_area->x), static_cast<float>(cell_area->y),
+ static_cast<float>(cell_area->width), static_cast<float>(cell_area->height));
+ cairo_t* cr = gtk_snapshot_append_cairo(GTK_SNAPSHOT(snapshot), &rect);
+ surface_cell_renderer_render(cell, cr, widget, background_area, cell_area, flags);
+ cairo_destroy(cr);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/surfacecellrenderer.hxx b/vcl/unx/gtk4/surfacecellrenderer.hxx
new file mode 100644
index 000000000..d908c04ce
--- /dev/null
+++ b/vcl/unx/gtk4/surfacecellrenderer.hxx
@@ -0,0 +1,42 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <cairo.h>
+
+G_BEGIN_DECLS
+
+struct _SurfaceCellRenderer
+{
+ GtkCellRenderer parent;
+ cairo_surface_t* surface;
+};
+
+/*
+ Provide a mechanism to support rendering a cairo surface in a GtkComboBox
+*/
+
+G_DECLARE_FINAL_TYPE(SurfaceCellRenderer, surface_cell_renderer, SURFACE, CELL_RENDERER,
+ GtkCellRenderer)
+
+#define SURFACE_TYPE_CELL_RENDERER (surface_cell_renderer_get_type())
+
+#define SURFACE_CELL_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SURFACE_TYPE_CELL_RENDERER, SurfaceCellRenderer))
+
+#define SURFACE_IS_CELL_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SURFACE_TYPE_CELL_RENDERER))
+
+GtkCellRenderer* surface_cell_renderer_new();
+
+G_END_DECLS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/surfacepaintable.cxx b/vcl/unx/gtk4/surfacepaintable.cxx
new file mode 100644
index 000000000..2e8aa98b2
--- /dev/null
+++ b/vcl/unx/gtk4/surfacepaintable.cxx
@@ -0,0 +1,100 @@
+/* -*- 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 "surfacepaintable.hxx"
+
+struct _SurfacePaintable
+{
+ GObject parent_instance;
+ int width;
+ int height;
+ cairo_surface_t* surface;
+};
+
+namespace
+{
+struct _SurfacePaintableClass : public GObjectClass
+{
+};
+}
+
+static void surface_paintable_snapshot(GdkPaintable* paintable, GdkSnapshot* snapshot, double width,
+ double height)
+{
+ graphene_rect_t rect
+ = GRAPHENE_RECT_INIT(0.0f, 0.0f, static_cast<float>(width), static_cast<float>(height));
+ SurfacePaintable* self = SURFACE_PAINTABLE(paintable);
+ cairo_t* cr = gtk_snapshot_append_cairo(GTK_SNAPSHOT(snapshot), &rect);
+ cairo_set_source_surface(cr, self->surface, 0, 0);
+ cairo_paint(cr);
+ cairo_destroy(cr);
+}
+
+static GdkPaintableFlags surface_paintable_get_flags(GdkPaintable* /*paintable*/)
+{
+ return static_cast<GdkPaintableFlags>(GDK_PAINTABLE_STATIC_SIZE
+ | GDK_PAINTABLE_STATIC_CONTENTS);
+}
+
+static int surface_paintable_get_intrinsic_width(GdkPaintable* paintable)
+{
+ SurfacePaintable* self = SURFACE_PAINTABLE(paintable);
+ return self->width;
+}
+
+static int surface_paintable_get_intrinsic_height(GdkPaintable* paintable)
+{
+ SurfacePaintable* self = SURFACE_PAINTABLE(paintable);
+ return self->height;
+}
+
+static void surface_paintable_init_interface(GdkPaintableInterface* iface)
+{
+ iface->snapshot = surface_paintable_snapshot;
+ iface->get_flags = surface_paintable_get_flags;
+ iface->get_intrinsic_width = surface_paintable_get_intrinsic_width;
+ iface->get_intrinsic_height = surface_paintable_get_intrinsic_height;
+}
+
+G_DEFINE_TYPE_WITH_CODE(SurfacePaintable, surface_paintable, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE(GDK_TYPE_PAINTABLE,
+ surface_paintable_init_interface));
+
+static void surface_paintable_init(SurfacePaintable* self)
+{
+ self->width = 0;
+ self->height = 0;
+ self->surface = nullptr;
+
+ // prevent loplugin:unreffun firing on macro generated function
+ (void)surface_paintable_get_instance_private(self);
+}
+
+static void surface_paintable_dispose(GObject* object)
+{
+ SurfacePaintable* self = SURFACE_PAINTABLE(object);
+ cairo_surface_destroy(self->surface);
+ G_OBJECT_CLASS(surface_paintable_parent_class)->dispose(object);
+}
+
+static void surface_paintable_class_init(SurfacePaintableClass* klass)
+{
+ GObjectClass* object_class = G_OBJECT_CLASS(klass);
+ object_class->dispose = surface_paintable_dispose;
+}
+
+void surface_paintable_set_source(SurfacePaintable* pPaintable, cairo_surface_t* pSource,
+ int nWidth, int nHeight)
+{
+ pPaintable->surface = pSource;
+ pPaintable->width = nWidth;
+ pPaintable->height = nHeight;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/surfacepaintable.hxx b/vcl/unx/gtk4/surfacepaintable.hxx
new file mode 100644
index 000000000..3e8c79f8a
--- /dev/null
+++ b/vcl/unx/gtk4/surfacepaintable.hxx
@@ -0,0 +1,32 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <cairo.h>
+
+G_BEGIN_DECLS
+
+/*
+ Provide a mechanism to allow continuing to use cairo surface where a GdkPaintable
+ is required
+*/
+
+G_DECLARE_FINAL_TYPE(SurfacePaintable, surface_paintable, SURFACE, PAINTABLE, GObject)
+
+/*
+ Set the surface to paint, takes ownership of pSource
+*/
+void surface_paintable_set_source(SurfacePaintable* pPaintable, cairo_surface_t* pSource,
+ int nWidth, int nHeight);
+
+G_END_DECLS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/transferableprovider.cxx b/vcl/unx/gtk4/transferableprovider.cxx
new file mode 100644
index 000000000..554b80c0d
--- /dev/null
+++ b/vcl/unx/gtk4/transferableprovider.cxx
@@ -0,0 +1,118 @@
+/* -*- 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 <com/sun/star/datatransfer/XTransferable.hpp>
+#include <unx/gtk/gtkinst.hxx>
+#include "transferableprovider.hxx"
+
+#define TRANSFERABLE_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), transerable_content_get_type(), TransferableContent))
+
+struct _TransferableContent
+{
+ GdkContentProvider parent;
+ VclToGtkHelper* m_pConversionHelper;
+ css::datatransfer::XTransferable* m_pContents;
+ Link<void*, void> m_aDetachClipboardLink;
+};
+
+namespace
+{
+struct _TransferableContentClass : public GdkContentProviderClass
+{
+};
+}
+
+G_DEFINE_TYPE(TransferableContent, transerable_content, GDK_TYPE_CONTENT_PROVIDER)
+
+static void transerable_content_write_mime_type_async(GdkContentProvider* provider,
+ const char* mime_type, GOutputStream* stream,
+ int io_priority, GCancellable* cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TransferableContent* self = TRANSFERABLE_CONTENT(provider);
+ if (!self->m_pContents)
+ return;
+ // tdf#129809 take a reference in case m_aContents is replaced during this
+ // call
+ css::uno::Reference<css::datatransfer::XTransferable> xCurrentContents(self->m_pContents);
+ self->m_pConversionHelper->setSelectionData(xCurrentContents, provider, mime_type, stream,
+ io_priority, cancellable, callback, user_data);
+}
+
+static gboolean transerable_content_write_mime_type_finish(GdkContentProvider*,
+ GAsyncResult* result, GError** error)
+{
+ return g_task_propagate_boolean(G_TASK(result), error);
+}
+
+static GdkContentFormats* transerable_content_ref_formats(GdkContentProvider* provider)
+{
+ TransferableContent* self = TRANSFERABLE_CONTENT(provider);
+ css::uno::Reference<css::datatransfer::XTransferable> xCurrentContents(self->m_pContents);
+ if (!xCurrentContents)
+ return nullptr;
+
+ auto aFormats = xCurrentContents->getTransferDataFlavors();
+ std::vector<OString> aGtkTargets(self->m_pConversionHelper->FormatsToGtk(aFormats));
+
+ GdkContentFormatsBuilder* pBuilder = gdk_content_formats_builder_new();
+ for (const auto& rFormat : aGtkTargets)
+ gdk_content_formats_builder_add_mime_type(pBuilder, rFormat.getStr());
+ return gdk_content_formats_builder_free_to_formats(pBuilder);
+}
+
+static void transerable_content_detach_clipboard(GdkContentProvider* provider, GdkClipboard*)
+{
+ TransferableContent* self = TRANSFERABLE_CONTENT(provider);
+ self->m_aDetachClipboardLink.Call(nullptr);
+}
+
+static void transerable_content_class_init(TransferableContentClass* klass)
+{
+ GdkContentProviderClass* provider_class = GDK_CONTENT_PROVIDER_CLASS(klass);
+
+ provider_class->ref_formats = transerable_content_ref_formats;
+ provider_class->detach_clipboard = transerable_content_detach_clipboard;
+ provider_class->write_mime_type_async = transerable_content_write_mime_type_async;
+ provider_class->write_mime_type_finish = transerable_content_write_mime_type_finish;
+}
+
+static void transerable_content_init(TransferableContent* self)
+{
+ self->m_pConversionHelper = nullptr;
+ self->m_pContents = nullptr;
+ // prevent loplugin:unreffun firing on macro generated function
+ (void)transerable_content_get_instance_private(self);
+}
+
+void transerable_content_set_transferable(TransferableContent* pContent,
+ css::datatransfer::XTransferable* pTransferable)
+{
+ pContent->m_pContents = pTransferable;
+}
+
+void transerable_content_set_detach_clipboard_link(TransferableContent* pContent,
+ const Link<void*, void>& rDetachClipboardLink)
+{
+ pContent->m_aDetachClipboardLink = rDetachClipboardLink;
+}
+
+GdkContentProvider* transerable_content_new(VclToGtkHelper* pConversionHelper,
+ css::datatransfer::XTransferable* pTransferable)
+{
+ TransferableContent* content
+ = TRANSFERABLE_CONTENT(g_object_new(transerable_content_get_type(), nullptr));
+ content->m_pConversionHelper = pConversionHelper;
+ content->m_pContents = pTransferable;
+ return GDK_CONTENT_PROVIDER(content);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk4/transferableprovider.hxx b/vcl/unx/gtk4/transferableprovider.hxx
new file mode 100644
index 000000000..df4d643d9
--- /dev/null
+++ b/vcl/unx/gtk4/transferableprovider.hxx
@@ -0,0 +1,51 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <tools/link.hxx>
+
+#include <gtk/gtk.h>
+
+struct VclToGtkHelper;
+
+namespace com::sun::star::datatransfer
+{
+class XTransferable;
+}
+
+G_BEGIN_DECLS
+
+/*
+ Provide a mechanism to provide data from a LibreOffice XTransferable via a
+ GdkContentProvider for gtk clipboard or dnd
+*/
+
+G_DECLARE_FINAL_TYPE(TransferableContent, transerable_content, TRANSFERABLE, CONTENT,
+ GdkContentProvider)
+
+GdkContentProvider* transerable_content_new(VclToGtkHelper* pConversionHelper,
+ css::datatransfer::XTransferable* pTransferable);
+
+/*
+ Change to a new XTransferable
+ */
+void transerable_content_set_transferable(TransferableContent* pContent,
+ css::datatransfer::XTransferable* pTransferable);
+
+/*
+ If the GdkContentProvider is used by a clipboard call rDetachClipboardLink on losing
+ ownership of the clipboard
+*/
+void transerable_content_set_detach_clipboard_link(TransferableContent* pContent,
+ const Link<void*, void>& rDetachClipboardLink);
+
+G_END_DECLS
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kf5/KF5FilePicker.cxx b/vcl/unx/kf5/KF5FilePicker.cxx
new file mode 100644
index 000000000..491ce7e31
--- /dev/null
+++ b/vcl/unx/kf5/KF5FilePicker.cxx
@@ -0,0 +1,180 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "KF5FilePicker.hxx"
+#include <KF5FilePicker.moc>
+
+#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
+#include <cppuhelper/supportsservice.hxx>
+#include <osl/mutex.hxx>
+
+#include <QtInstance.hxx>
+
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QGridLayout>
+#include <QtWidgets/QWidget>
+#include <KFileWidget>
+
+using namespace ::com::sun::star;
+using ::com::sun::star::ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_AUTOEXTENSION;
+
+namespace
+{
+uno::Sequence<OUString> FilePicker_getSupportedServiceNames()
+{
+ return { "com.sun.star.ui.dialogs.FilePicker", "com.sun.star.ui.dialogs.SystemFilePicker",
+ "com.sun.star.ui.dialogs.KF5FilePicker", "com.sun.star.ui.dialogs.KF5FolderPicker" };
+}
+}
+
+// KF5FilePicker
+
+KF5FilePicker::KF5FilePicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode eMode)
+ // Native kf5 filepicker does not add file extension automatically
+ : QtFilePicker(context, eMode, true)
+ , _layout(new QGridLayout(m_pExtraControls))
+{
+ // only columns 0 and 1 are used by controls (s. QtFilePicker::addCustomControl);
+ // set stretch for (unused) column 2 in order for the controls to only take the space
+ // they actually need and avoid empty space in between
+ _layout->setColumnStretch(2, 1);
+
+ // set layout so custom widgets show up in our native file dialog
+ setCustomControlWidgetLayout(_layout.get());
+
+ m_pFileDialog->setSupportedSchemes({
+ QStringLiteral("file"), QStringLiteral("ftp"), QStringLiteral("http"),
+ QStringLiteral("https"), QStringLiteral("webdav"), QStringLiteral("webdavs"),
+ QStringLiteral("smb"),
+ QStringLiteral(""), // this makes removable devices shown
+ });
+
+ // used to set the custom controls
+ qApp->installEventFilter(this);
+}
+
+// XFilePickerControlAccess
+void SAL_CALL KF5FilePicker::setValue(sal_Int16 controlId, sal_Int16 nControlAction,
+ const uno::Any& value)
+{
+ if (CHECKBOX_AUTOEXTENSION == controlId)
+ // We ignore this one and rely on QFileDialog to provide the functionality
+ return;
+
+ QtFilePicker::setValue(controlId, nControlAction, value);
+}
+
+uno::Any SAL_CALL KF5FilePicker::getValue(sal_Int16 controlId, sal_Int16 nControlAction)
+{
+ SolarMutexGuard g;
+ auto* pSalInst(GetQtInstance());
+ assert(pSalInst);
+ if (!pSalInst->IsMainThread())
+ {
+ uno::Any ret;
+ pSalInst->RunInMainThread([&ret, this, controlId, nControlAction]() {
+ ret = getValue(controlId, nControlAction);
+ });
+ return ret;
+ }
+
+ if (CHECKBOX_AUTOEXTENSION == controlId)
+ // We ignore this one and rely on QFileDialog to provide the function.
+ // Always return false, to pretend we do not support this, otherwise
+ // LO core would try to be smart and cut the extension in some places,
+ // interfering with QFileDialog's handling of it. QFileDialog also
+ // saves the value of the setting, so LO core is not needed for that either.
+ return uno::Any(false);
+
+ return QtFilePicker::getValue(controlId, nControlAction);
+}
+
+void SAL_CALL KF5FilePicker::enableControl(sal_Int16 controlId, sal_Bool enable)
+{
+ if (CHECKBOX_AUTOEXTENSION == controlId)
+ // We ignore this one and rely on QFileDialog to provide the functionality
+ return;
+
+ QtFilePicker::enableControl(controlId, enable);
+}
+
+void SAL_CALL KF5FilePicker::setLabel(sal_Int16 controlId, const OUString& label)
+{
+ if (CHECKBOX_AUTOEXTENSION == controlId)
+ // We ignore this one and rely on QFileDialog to provide the functionality
+ return;
+
+ QtFilePicker::setLabel(controlId, label);
+}
+
+OUString SAL_CALL KF5FilePicker::getLabel(sal_Int16 controlId)
+{
+ // We ignore this one and rely on QFileDialog to provide the functionality
+ if (CHECKBOX_AUTOEXTENSION == controlId)
+ return "";
+
+ return QtFilePicker::getLabel(controlId);
+}
+
+void KF5FilePicker::addCustomControl(sal_Int16 controlId)
+{
+ // native kf5 filepicker has its own autoextension checkbox,
+ // therefore avoid adding yet another one
+ if (controlId == CHECKBOX_AUTOEXTENSION)
+ return;
+
+ QtFilePicker::addCustomControl(controlId);
+}
+
+// XServiceInfo
+OUString SAL_CALL KF5FilePicker::getImplementationName()
+{
+ return "com.sun.star.ui.dialogs.KF5FilePicker";
+}
+
+sal_Bool SAL_CALL KF5FilePicker::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+uno::Sequence<OUString> SAL_CALL KF5FilePicker::getSupportedServiceNames()
+{
+ return FilePicker_getSupportedServiceNames();
+}
+
+bool KF5FilePicker::eventFilter(QObject* o, QEvent* e)
+{
+ if (e->type() == QEvent::Show && o->isWidgetType())
+ {
+ auto* w = static_cast<QWidget*>(o);
+ if (!w->parentWidget() && w->isModal())
+ {
+ if (auto* fileWidget = w->findChild<KFileWidget*>({}, Qt::FindDirectChildrenOnly))
+ {
+ fileWidget->setCustomWidget(m_pExtraControls);
+ // remove event filter again; the only purpose was to set the custom widget here
+ qApp->removeEventFilter(this);
+ }
+ }
+ }
+ return QObject::eventFilter(o, e);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kf5/KF5FilePicker.hxx b/vcl/unx/kf5/KF5FilePicker.hxx
new file mode 100644
index 000000000..29989eb1b
--- /dev/null
+++ b/vcl/unx/kf5/KF5FilePicker.hxx
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <QtFilePicker.hxx>
+#include <memory>
+
+class QGridLayout;
+
+class KF5FilePicker final : public QtFilePicker
+{
+ Q_OBJECT
+
+private:
+ //layout for extra custom controls
+ std::unique_ptr<QGridLayout> _layout;
+
+public:
+ explicit KF5FilePicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode);
+
+ // XFilePickerControlAccess functions
+ virtual void SAL_CALL setValue(sal_Int16 nControlId, sal_Int16 nControlAction,
+ const css::uno::Any& rValue) override;
+ virtual css::uno::Any SAL_CALL getValue(sal_Int16 nControlId,
+ sal_Int16 nControlAction) override;
+ virtual void SAL_CALL enableControl(sal_Int16 nControlId, sal_Bool bEnable) override;
+ virtual void SAL_CALL setLabel(sal_Int16 nControlId, const OUString& rLabel) override;
+ virtual OUString SAL_CALL getLabel(sal_Int16 nControlId) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override;
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
+
+private:
+ //add a custom control widget to the file dialog
+ void addCustomControl(sal_Int16 controlId) override;
+ bool eventFilter(QObject* watched, QEvent* event) override;
+
+private Q_SLOTS:
+ // the KF5 file picker has its own automatic extension handling
+ void updateAutomaticFileExtension() override {}
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kf5/KF5SalInstance.cxx b/vcl/unx/kf5/KF5SalInstance.cxx
new file mode 100644
index 000000000..de3e2fe4d
--- /dev/null
+++ b/vcl/unx/kf5/KF5SalInstance.cxx
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <utility>
+
+#include <QtWidgets/QApplication>
+
+#include <sal/log.hxx>
+
+#include <QtData.hxx>
+
+#include "KF5FilePicker.hxx"
+#include "KF5SalInstance.hxx"
+
+using namespace com::sun::star;
+
+KF5SalInstance::KF5SalInstance(std::unique_ptr<QApplication>& pQApp, bool bUseCairo)
+ : QtInstance(pQApp, bUseCairo)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mxToolkitName = constructToolkitID(u"kf5");
+}
+
+bool KF5SalInstance::hasNativeFileSelection() const
+{
+ if (Application::GetDesktopEnvironment() == "PLASMA5")
+ return true;
+ return QtInstance::hasNativeFileSelection();
+}
+
+rtl::Reference<QtFilePicker>
+KF5SalInstance::createPicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode eMode)
+{
+ if (!IsMainThread())
+ {
+ SolarMutexGuard g;
+ rtl::Reference<QtFilePicker> pPicker;
+ RunInMainThread([&, this]() { pPicker = createPicker(context, eMode); });
+ assert(pPicker);
+ return pPicker;
+ }
+
+ // In order to insert custom controls, KF5FilePicker currently relies on KFileWidget
+ // being used in the native file picker, which is only the case for KDE Plasma.
+ // Therefore, return the plain qt5 one in order to not lose custom controls.
+ if (Application::GetDesktopEnvironment() == "PLASMA5")
+ return new KF5FilePicker(context, eMode);
+ return QtInstance::createPicker(context, eMode);
+}
+
+extern "C" {
+VCLPLUG_KF5_PUBLIC SalInstance* create_SalInstance()
+{
+ static const bool bUseCairo = (nullptr == getenv("SAL_VCL_KF5_USE_QFONT"));
+
+ std::unique_ptr<char* []> pFakeArgv;
+ std::unique_ptr<int> pFakeArgc;
+ std::vector<FreeableCStr> aFakeArgvFreeable;
+ QtInstance::AllocFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable);
+
+ std::unique_ptr<QApplication> pQApp
+ = QtInstance::CreateQApplication(*pFakeArgc, pFakeArgv.get());
+
+ KF5SalInstance* pInstance = new KF5SalInstance(pQApp, bUseCairo);
+ pInstance->MoveFakeCmdlineArgs(pFakeArgv, pFakeArgc, aFakeArgvFreeable);
+
+ new QtData();
+
+ return pInstance;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kf5/KF5SalInstance.hxx b/vcl/unx/kf5/KF5SalInstance.hxx
new file mode 100644
index 000000000..3fb6a793a
--- /dev/null
+++ b/vcl/unx/kf5/KF5SalInstance.hxx
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <QtInstance.hxx>
+
+class KF5SalInstance final : public QtInstance
+{
+ bool hasNativeFileSelection() const override;
+ rtl::Reference<QtFilePicker>
+ createPicker(css::uno::Reference<css::uno::XComponentContext> const& context,
+ QFileDialog::FileMode) override;
+
+public:
+ explicit KF5SalInstance(std::unique_ptr<QApplication>& pQApp, bool bUseCairo);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/x11/x11sys.cxx b/vcl/unx/x11/x11sys.cxx
new file mode 100644
index 000000000..c354269b3
--- /dev/null
+++ b/vcl/unx/x11/x11sys.cxx
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <unx/salunx.h>
+#include <unx/salinst.h>
+#include <unx/saldisp.hxx>
+#include <unx/x11/x11sys.hxx>
+
+#include <vcl/weld.hxx>
+
+#include <svdata.hxx>
+
+#include <rtl/ustrbuf.hxx>
+#include <osl/thread.h>
+
+SalSystem* X11SalInstance::CreateSalSystem()
+{
+ return new X11SalSystem();
+}
+
+X11SalSystem::~X11SalSystem()
+{
+}
+
+// for the moment only handle xinerama case
+unsigned int X11SalSystem::GetDisplayScreenCount()
+{
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ return pSalDisp->IsXinerama() ? pSalDisp->GetXineramaScreens().size() :
+ pSalDisp->GetXScreenCount();
+}
+
+bool X11SalSystem::IsUnifiedDisplay()
+{
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ unsigned int nScreenCount = pSalDisp->GetXScreenCount();
+ return pSalDisp->IsXinerama() || (nScreenCount == 1);
+}
+
+unsigned int X11SalSystem::GetDisplayBuiltInScreen()
+{
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ return pSalDisp->GetDefaultXScreen().getXScreen();
+}
+
+tools::Rectangle X11SalSystem::GetDisplayScreenPosSizePixel( unsigned int nScreen )
+{
+ tools::Rectangle aRet;
+ SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData());
+ if( pSalDisp->IsXinerama() )
+ {
+ const std::vector< tools::Rectangle >& rScreens = pSalDisp->GetXineramaScreens();
+
+ // we shouldn't be able to pick a screen > number of screens available
+ assert(nScreen < rScreens.size() );
+
+ if( nScreen < rScreens.size() )
+ aRet = rScreens[nScreen];
+ }
+ else
+ {
+ const SalDisplay::ScreenData& rScreen =
+ pSalDisp->getDataForScreen( SalX11Screen( nScreen ) );
+ aRet = tools::Rectangle( Point( 0, 0 ), rScreen.m_aSize );
+ }
+
+ return aRet;
+}
+
+int X11SalSystem::ShowNativeDialog( const OUString& rTitle, const OUString& rMessage, const std::vector< OUString >& rButtons )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->mpIntroWindow )
+ pSVData->mpIntroWindow->Hide();
+
+ std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Warning, VclButtonsType::NONE,
+ rMessage));
+ xWarn->set_title(rTitle);
+
+ sal_uInt16 nButton = 0;
+ for (auto const& button : rButtons)
+ xWarn->add_button(button, nButton++);
+ xWarn->set_default_response(0);
+
+ return xWarn->run();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/x11/xlimits.cxx b/vcl/unx/x11/xlimits.cxx
new file mode 100644
index 000000000..b8509cbd2
--- /dev/null
+++ b/vcl/unx/x11/xlimits.cxx
@@ -0,0 +1,29 @@
+/* -*- 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/log.hxx>
+#include <unx/x11/xlimits.hxx>
+
+Pixmap limitXCreatePixmap(Display *display, Drawable d, unsigned int width, unsigned int height, unsigned int depth)
+{
+ // The X protocol request CreatePixmap puts an upper bound
+ // of 16 bit to the size. And in practice some drivers
+ // fall over with values close to the max.
+
+ // see, e.g. moz#424333, fdo#48961, rhbz#1086714
+ // we've a duplicate of this in canvas :-(
+ if (width > SAL_MAX_INT16-10 || height > SAL_MAX_INT16-10)
+ {
+ SAL_WARN("vcl", "overlarge pixmap: " << width << " x " << height);
+ return None;
+ }
+ return XCreatePixmap(display, d, width, height, depth);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */