summaryrefslogtreecommitdiffstats
path: root/vcl/unx/gtk3
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /vcl/unx/gtk3
parentInitial commit. (diff)
downloadlibreoffice-upstream.tar.xz
libreoffice-upstream.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--vcl/unx/gtk3/a11y/TODO49
-rw-r--r--vcl/unx/gtk3/a11y/atkfactory.hxx33
-rw-r--r--vcl/unx/gtk3/a11y/atklistener.hxx71
-rw-r--r--vcl/unx/gtk3/a11y/atkregistry.hxx35
-rw-r--r--vcl/unx/gtk3/a11y/atktextattributes.hxx52
-rw-r--r--vcl/unx/gtk3/a11y/atkutil.hxx29
-rw-r--r--vcl/unx/gtk3/a11y/atkwrapper.hxx125
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkaction.cxx275
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkbridge.cxx34
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkcomponent.cxx387
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkeditabletext.cxx193
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkfactory.cxx186
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkhypertext.cxx277
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkimage.cxx131
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atklistener.cxx748
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkregistry.cxx66
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkselection.cxx190
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atktable.cxx580
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atktext.cxx897
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atktextattributes.cxx1390
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkutil.cxx708
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkvalue.cxx138
-rw-r--r--vcl/unx/gtk3/a11y/gtk3atkwrapper.cxx942
-rw-r--r--vcl/unx/gtk3/cairo_gtk3_cairo.cxx128
-rw-r--r--vcl/unx/gtk3/cairo_gtk3_cairo.hxx49
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFilePicker.cxx1991
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx240
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFolderPicker.cxx177
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkFolderPicker.hxx66
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkPicker.cxx266
-rw-r--r--vcl/unx/gtk3/fpicker/SalGtkPicker.hxx117
-rw-r--r--vcl/unx/gtk3/fpicker/eventnotification.hxx43
-rw-r--r--vcl/unx/gtk3/fpicker/resourceprovider.cxx80
-rw-r--r--vcl/unx/gtk3/gtk3gloactiongroup.cxx405
-rw-r--r--vcl/unx/gtk3/gtk3glomenu.cxx692
-rw-r--r--vcl/unx/gtk3/gtk3gtkdata.cxx778
-rw-r--r--vcl/unx/gtk3/gtk3gtkframe.cxx4609
-rw-r--r--vcl/unx/gtk3/gtk3gtkinst.cxx16272
-rw-r--r--vcl/unx/gtk3/gtk3gtkobject.cxx463
-rw-r--r--vcl/unx/gtk3/gtk3gtkprintwrapper.cxx153
-rw-r--r--vcl/unx/gtk3/gtk3gtksalmenu.cxx1409
-rw-r--r--vcl/unx/gtk3/gtk3gtksys.cxx275
-rw-r--r--vcl/unx/gtk3/gtk3hudawareness.cxx110
-rw-r--r--vcl/unx/gtk3/gtk3salnativewidgets-gtk.cxx3731
-rw-r--r--vcl/unx/gtk3/gtk3salprn-gtk.cxx954
-rw-r--r--vcl/unx/gtk3/gtkprintwrapper.hxx17
-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_filepicker.cxx456
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.hxx131
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.cxx275
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.hxx134
-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_printwrapper.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_salnativewidgets-gtk.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/gtk3_kde5_salprn-gtk.cxx22
-rw-r--r--vcl/unx/gtk3_kde5/kde5_filepicker.cxx292
-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
89 files changed, 43333 insertions, 0 deletions
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/atkfactory.hxx b/vcl/unx/gtk3/a11y/atkfactory.hxx
new file mode 100644
index 000000000..ac72b5897
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkfactory.hxx
@@ -0,0 +1,33 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_A11Y_ATKFACTORY_HXX
+#define INCLUDED_VCL_UNX_GTK_A11Y_ATKFACTORY_HXX
+
+#include <atk/atk.h>
+
+extern "C" {
+
+GType wrapper_factory_get_type();
+
+} // extern "C"
+
+#endif
+
+/* 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..58798d443
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atklistener.hxx
@@ -0,0 +1,71 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_A11Y_ATKLISTENER_HXX
+#define INCLUDED_VCL_UNX_GTK_A11Y_ATKLISTENER_HXX
+
+#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);
+};
+
+#endif // INCLUDED_VCL_UNX_GTK_A11Y_ATKLISTENER_HXX
+
+/* 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..b69229084
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkregistry.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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_A11Y_ATKREGISTRY_HXX
+#define INCLUDED_VCL_UNX_GTK_A11Y_ATKREGISTRY_HXX
+
+#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);
+
+#endif // __ATK_REGISTRY_HXX_
+
+/* 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..f86c82f4f
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atktextattributes.hxx
@@ -0,0 +1,52 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_A11Y_ATKTEXTATTRIBUTES_HXX
+#define INCLUDED_VCL_UNX_GTK_A11Y_ATKTEXTATTRIBUTES_HXX
+
+#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 );
+
+#endif
+
+/* 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..3df45c1ca
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkutil.hxx
@@ -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/.
+ *
+ * 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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_A11Y_ATKUTIL_HXX
+#define INCLUDED_VCL_UNX_GTK_A11Y_ATKUTIL_HXX
+
+#include <atk/atk.h>
+
+void ooo_atk_util_ensure_event_listener();
+
+#endif
+
+/* 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..d9c651a2e
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/atkwrapper.hxx
@@ -0,0 +1,125 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_A11Y_ATKWRAPPER_HXX
+#define INCLUDED_VCL_UNX_GTK_A11Y_ATKWRAPPER_HXX
+
+#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
+{
+ AtkObject aParent;
+ AtkObject* mpOrig; //if we're a GtkDrawingArea acting as a custom LibreOffice widget, this is the toolkit default impl
+
+ 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(const OUString& rString )
+{
+ OString aUtf8 = OUStringToOString( rString, RTL_TEXTENCODING_UTF8 );
+ return g_strdup( aUtf8.getStr() );
+}
+
+#define OUStringToConstGChar( string ) OUStringToOString( string, RTL_TEXTENCODING_UTF8 ).getStr()
+
+#endif // INCLUDED_VCL_UNX_GTK_A11Y_ATKWRAPPER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/a11y/gtk3atkaction.cxx b/vcl/unx/gtk3/a11y/gtk3atkaction.cxx
new file mode 100644
index 000000000..fa9d09660
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkaction.cxx
@@ -0,0 +1,275 @@
+/* -*- 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 "";
+}
+
+#define ACTION_NAME_PAIR( OOoName, AtkName ) \
+ std::pair< const OUString, const gchar * > ( OUString( OOoName ), AtkName )
+
+static const gchar *
+action_wrapper_get_name (AtkAction *action,
+ gint i)
+{
+ static std::map< OUString, const gchar * > aNameMap;
+
+ if( aNameMap.empty() )
+ {
+ aNameMap.insert( ACTION_NAME_PAIR( "click", "click" ) );
+ aNameMap.insert( ACTION_NAME_PAIR( "select", "click" ) );
+ aNameMap.insert( ACTION_NAME_PAIR( "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( OUStringToGChar( OUString( rKeyStroke.KeyChar ) ) );
+ }
+ }
+ }
+}
+
+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/gtk3atkbridge.cxx b/vcl/unx/gtk3/a11y/gtk3atkbridge.cxx
new file mode 100644
index 000000000..ddcc20f20
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkbridge.cxx
@@ -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 .
+ */
+
+#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/gtk3atkcomponent.cxx b/vcl/unx/gtk3/a11y/gtk3atkcomponent.cxx
new file mode 100644
index 000000000..21301d4da
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkcomponent.cxx
@@ -0,0 +1,387 @@
+/* -*- 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 <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
+translatePoint( 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();
+ 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( 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( 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
+ aPos = pComponent->getLocation();
+
+ *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/gtk3atkeditabletext.cxx b/vcl/unx/gtk3/a11y/gtk3atkeditabletext.cxx
new file mode 100644
index 000000000..f240c3232
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkeditabletext.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/gtk3atkfactory.cxx b/vcl/unx/gtk3/a11y/gtk3atkfactory.cxx
new file mode 100644
index 000000000..f92f9a667
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkfactory.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, nullptr );
+
+ 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 nullptr;
+}
+
+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/gtk3atkhypertext.cxx b/vcl/unx/gtk3/a11y/gtk3atkhypertext.cxx
new file mode 100644
index 000000000..83f6817fc
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkhypertext.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/gtk3atkimage.cxx b/vcl/unx/gtk3/a11y/gtk3atkimage.cxx
new file mode 100644
index 000000000..acd43f467
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkimage.cxx
@@ -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 .
+ */
+
+#include "atkwrapper.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleImage.hpp>
+
+using namespace ::com::sun::star;
+
+// FIXME
+static const gchar *
+getAsConst( const OUString& 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 ) )
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ atk_component_get_position( ATK_COMPONENT( image ), x, y, coord_type );
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+ 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/gtk3atklistener.cxx b/vcl/unx/gtk3/a11y/gtk3atklistener.cxx
new file mode 100644
index 000000000..8606b7bde
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atklistener.cxx
@@ -0,0 +1,748 @@
+/* -*- 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 )
+ {
+ 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) )
+ {
+ 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 )
+ {
+ 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:
+ {
+ // TESTME: and remove this comment:
+ // cf. comphelper/source/misc/accessibletexthelper.cxx (implInitTextChangedEvent)
+ accessibility::TextSegment aDeletedText;
+ accessibility::TextSegment aInsertedText;
+
+ // TODO: when GNOME starts to send "update" kind of events, change
+ // we need to re-think this implementation as well
+ if( aEvent.OldValue >>= aDeletedText )
+ {
+ /* Remember the text segment here to be able to return removed text in get_text().
+ * This is clearly a hack to be used until appropriate API exists in atk to pass
+ * the string value directly or we find a compelling reason to start caching the
+ * UTF-8 converted strings in the atk wrapper object.
+ */
+
+ g_object_set_data( G_OBJECT(atk_obj), "ooo::text_changed::delete", &aDeletedText);
+
+ g_signal_emit_by_name( atk_obj, "text_changed::delete",
+ static_cast<gint>(aDeletedText.SegmentStart),
+ static_cast<gint>( aDeletedText.SegmentEnd - aDeletedText.SegmentStart ) );
+
+ g_object_steal_data( G_OBJECT(atk_obj), "ooo::text_changed::delete" );
+ }
+
+ if( aEvent.NewValue >>= aInsertedText )
+ g_signal_emit_by_name( atk_obj, "text_changed::insert",
+ static_cast<gint>(aInsertedText.SegmentStart),
+ static_cast<gint>( aInsertedText.SegmentEnd - aInsertedText.SegmentStart ) );
+ 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;
+
+ static const struct {
+ const char *row;
+ const char *col;
+ } aSignalNames[] =
+ {
+ { nullptr, nullptr }, // dummy
+ { "row_inserted", "column_inserted" }, // INSERT = 1
+ { "row_deleted", "column_deleted" } // DELETE = 2
+ };
+ switch( aChange.Type )
+ {
+ case accessibility::AccessibleTableModelChangeType::INSERT:
+ case accessibility::AccessibleTableModelChangeType::DELETE:
+ if( nRowsChanged > 0 )
+ g_signal_emit_by_name( G_OBJECT( atk_obj ),
+ aSignalNames[aChange.Type].row,
+ aChange.FirstRow, nRowsChanged );
+ if( nColumnsChanged > 0 )
+ g_signal_emit_by_name( G_OBJECT( atk_obj ),
+ aSignalNames[aChange.Type].col,
+ aChange.FirstColumn, nColumnsChanged );
+ 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/gtk3atkregistry.cxx b/vcl/unx/gtk3/a11y/gtk3atkregistry.cxx
new file mode 100644
index 000000000..ff96378c4
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkregistry.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/gtk3atkselection.cxx b/vcl/unx/gtk3/a11y/gtk3atkselection.cxx
new file mode 100644
index 000000000..91759e8d0
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkselection.cxx
@@ -0,0 +1,190 @@
+/* -*- 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() )
+ {
+ pSelection->deselectAccessibleChild( i );
+ 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/gtk3atktable.cxx b/vcl/unx/gtk3/a11y/gtk3atktable.cxx
new file mode 100644
index 000000000..221e55d2b
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atktable.cxx
@@ -0,0 +1,580 @@
+/* -*- 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/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( const OUString& 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/gtk3atktext.cxx b/vcl/unx/gtk3/a11y/gtk3atktext.cxx
new file mode 100644
index 000000000..713b03325
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atktext.cxx
@@ -0,0 +1,897 @@
+/* -*- 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 );
+
+ /* at-spi expects the delete event to be send before the deletion happened
+ * so we save the deleted string object in the UNO event notification and
+ * fool libatk-bridge.so here ..
+ */
+ void * pData = g_object_get_data( G_OBJECT(text), "ooo::text_changed::delete" );
+ if( pData != nullptr )
+ {
+ accessibility::TextSegment * pTextSegment =
+ static_cast <accessibility::TextSegment *> (pData);
+
+ if( pTextSegment->SegmentStart == start_offset &&
+ pTextSegment->SegmentEnd == end_offset )
+ {
+ OString aUtf8 = OUStringToOString( pTextSegment->SegmentText, RTL_TEXTENCODING_UTF8 );
+ return g_strdup( aUtf8.getStr() );
+ }
+ }
+
+ 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 )
+ {
+ g_return_if_fail( ATK_IS_COMPONENT( text ) );
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ atk_component_get_position( ATK_COMPONENT( text ), &origin_x, &origin_y, coords);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+
+ *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 )
+ {
+ g_return_val_if_fail( ATK_IS_COMPONENT( text ), -1 );
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ atk_component_get_position( ATK_COMPONENT( text ), &origin_x, &origin_y, coords);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+
+ 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/gtk3atktextattributes.cxx b/vcl/unx/gtk3/a11y/gtk3atktextattributes.cxx
new file mode 100644
index 000000000..81115fb6a
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atktextattributes.cxx
@@ -0,0 +1,1390 @@
+/* -*- 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 <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 double toPoint(sal_Int16 n)
+{
+ // 100th mm -> pt
+ return static_cast<double>(n * 72) / 2540;
+}
+
+/*****************************************************************************/
+
+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 && n < sal_Int16(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" };
+static 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", toPoint(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( sProperty.getToken( 0, ':', nColonPos ),
+ RTL_TEXTENCODING_UTF8 );
+ OString sPropertyValue = OUStringToOString( sProperty.getToken( 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));
+
+ 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( aAttributeList[nIndex].Value, attribute->value) )
+ return false;
+
+ aAttributeList[nIndex].Name = OUString::createFromAscii( g_TextAttrMap[text_attr].name );
+ aAttributeList[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/gtk3atkutil.cxx b/vcl/unx/gtk3/a11y/gtk3atkutil.cxx
new file mode 100644
index 000000000..1d1337d6e
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkutil.cxx
@@ -0,0 +1,708 @@
+/* -*- 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/XAccessibleContext.hpp>
+#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
+{
+ struct theNextFocusObject :
+ public rtl::Static< 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::get();
+ 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::get() = xAccessible;
+
+ focus_notify_handler = g_idle_add (atk_wrapper_focus_idle_handler, xAccessible.get());
+}
+
+/*****************************************************************************/
+
+class DocumentFocusListener :
+ public ::cppu::WeakImplHelper< accessibility::XAccessibleEventListener >
+{
+
+ std::set< uno::Reference< uno::XInterface > > m_aRefList;
+
+public:
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible,
+ const uno::Reference< accessibility::XAccessibleContext >& xContext
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void attachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible,
+ const uno::Reference< accessibility::XAccessibleContext >& xContext,
+ const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void detachRecursive(
+ const uno::Reference< accessibility::XAccessible >& xAccessible
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void detachRecursive(
+ const uno::Reference< accessibility::XAccessibleContext >& xContext
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ void detachRecursive(
+ const uno::Reference< accessibility::XAccessibleContext >& xContext,
+ const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
+ );
+
+ /// @throws lang::IndexOutOfBoundsException
+ /// @throws uno::RuntimeException
+ static uno::Reference< accessibility::XAccessible > getAccessible(const lang::EventObject& aEvent );
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const lang::EventObject& Source ) override;
+
+ // XAccessibleEventListener
+ virtual void SAL_CALL notifyEvent( const accessibility::AccessibleEventObject& aEvent ) override;
+};
+
+/*****************************************************************************/
+
+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("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 )
+ {
+ 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) )
+ {
+ 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_pDocumentFocusListener)
+ {
+ m_pDocumentFocusListener = new DocumentFocusListener;
+ m_xDocumentFocusListener.set(m_pDocumentFocusListener);
+ }
+ return *m_pDocumentFocusListener;
+}
+
+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);
+ }
+ else if (const VclAccessibleEvent* pAccEvent = dynamic_cast<const VclAccessibleEvent*>(&rEvent))
+ {
+ const uno::Reference< accessibility::XAccessible >& xAccessible = pAccEvent->GetAccessible();
+ if (xAccessible.is())
+ atk_wrapper_focus_tracker_notify_when_idle(xAccessible);
+ }
+ 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("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/gtk3atkvalue.cxx b/vcl/unx/gtk3/a11y/gtk3atkvalue.cxx
new file mode 100644
index 000000000..f5e45d3b2
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkvalue.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/gtk3atkwrapper.cxx b/vcl/unx/gtk3/a11y/gtk3atkwrapper.cxx
new file mode 100644
index 000000000..ab011d9d0
--- /dev/null
+++ b/vcl/unx/gtk3/a11y/gtk3atkwrapper.cxx
@@ -0,0 +1,942 @@
+/* -*- 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 <rtl/strbuf.hxx>
+#include <osl/diagnose.h>
+
+#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 getRoleForName( const gchar * name )
+{
+ AtkRole ret = atk_role_for_name( name );
+ if( ATK_ROLE_INVALID == ret )
+ {
+ // this should only happen in old ATK versions
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ ret = atk_role_register( name );
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+
+ return ret;
+}
+
+static AtkRole mapToAtkRole( sal_Int16 nRole )
+{
+ AtkRole role = ATK_ROLE_UNKNOWN;
+
+ static AtkRole roleMap[] = {
+ ATK_ROLE_UNKNOWN,
+ ATK_ROLE_ALERT,
+ ATK_ROLE_COLUMN_HEADER,
+ ATK_ROLE_CANVAS,
+ ATK_ROLE_CHECK_BOX,
+ ATK_ROLE_CHECK_MENU_ITEM,
+ ATK_ROLE_COLOR_CHOOSER,
+ ATK_ROLE_COMBO_BOX,
+ ATK_ROLE_DATE_EDITOR,
+ ATK_ROLE_DESKTOP_ICON,
+ ATK_ROLE_DESKTOP_FRAME, // ? pane
+ ATK_ROLE_DIRECTORY_PANE,
+ ATK_ROLE_DIALOG,
+ ATK_ROLE_UNKNOWN, // DOCUMENT - registered below
+ ATK_ROLE_UNKNOWN, // EMBEDDED_OBJECT - registered below
+ ATK_ROLE_UNKNOWN, // END_NOTE - registered below
+ ATK_ROLE_FILE_CHOOSER,
+ ATK_ROLE_FILLER,
+ ATK_ROLE_FONT_CHOOSER,
+ ATK_ROLE_FOOTER,
+ ATK_ROLE_UNKNOWN, // FOOTNOTE - registered below
+ ATK_ROLE_FRAME,
+ ATK_ROLE_GLASS_PANE,
+ ATK_ROLE_IMAGE, // GRAPHIC
+ ATK_ROLE_UNKNOWN, // GROUP_BOX - registered below
+ ATK_ROLE_HEADER,
+ ATK_ROLE_HEADING,
+ ATK_ROLE_TEXT, // HYPER_LINK - registered below
+ ATK_ROLE_ICON,
+ ATK_ROLE_INTERNAL_FRAME,
+ ATK_ROLE_LABEL,
+ ATK_ROLE_LAYERED_PANE,
+ ATK_ROLE_LIST,
+ ATK_ROLE_LIST_ITEM,
+ ATK_ROLE_MENU,
+ ATK_ROLE_MENU_BAR,
+ ATK_ROLE_MENU_ITEM,
+ ATK_ROLE_OPTION_PANE,
+ ATK_ROLE_PAGE_TAB,
+ ATK_ROLE_PAGE_TAB_LIST,
+ ATK_ROLE_PANEL,
+ ATK_ROLE_PARAGRAPH,
+ ATK_ROLE_PASSWORD_TEXT,
+ ATK_ROLE_POPUP_MENU,
+ ATK_ROLE_PUSH_BUTTON,
+ ATK_ROLE_PROGRESS_BAR,
+ ATK_ROLE_RADIO_BUTTON,
+ ATK_ROLE_RADIO_MENU_ITEM,
+ ATK_ROLE_ROW_HEADER,
+ ATK_ROLE_ROOT_PANE,
+ ATK_ROLE_SCROLL_BAR,
+ ATK_ROLE_SCROLL_PANE,
+ ATK_ROLE_PANEL, // SHAPE
+ ATK_ROLE_SEPARATOR,
+ ATK_ROLE_SLIDER,
+ ATK_ROLE_SPIN_BUTTON, // SPIN_BOX ?
+ ATK_ROLE_SPLIT_PANE,
+ ATK_ROLE_STATUSBAR,
+ ATK_ROLE_TABLE,
+ ATK_ROLE_TABLE_CELL,
+ ATK_ROLE_TEXT,
+ ATK_ROLE_PANEL, // TEXT_FRAME
+ ATK_ROLE_TOGGLE_BUTTON,
+ ATK_ROLE_TOOL_BAR,
+ ATK_ROLE_TOOL_TIP,
+ ATK_ROLE_TREE,
+ ATK_ROLE_VIEWPORT,
+ ATK_ROLE_WINDOW,
+ ATK_ROLE_PUSH_BUTTON, // BUTTON_DROPDOWN
+ ATK_ROLE_PUSH_BUTTON, // BUTTON_MENU
+ ATK_ROLE_UNKNOWN, // CAPTION - registered below
+ ATK_ROLE_UNKNOWN, // CHART - registered below
+ ATK_ROLE_UNKNOWN, // EDIT_BAR - registered below
+ ATK_ROLE_UNKNOWN, // FORM - registered below
+ ATK_ROLE_UNKNOWN, // IMAGE_MAP - registered below
+ ATK_ROLE_UNKNOWN, // NOTE - registered below
+ ATK_ROLE_UNKNOWN, // PAGE - registered below
+ ATK_ROLE_RULER,
+ ATK_ROLE_UNKNOWN, // SECTION - registered below
+ ATK_ROLE_UNKNOWN, // TREE_ITEM - registered below
+ ATK_ROLE_TREE_TABLE,
+ ATK_ROLE_SCROLL_PANE, // COMMENT - mapped to atk_role_scroll_pane
+ ATK_ROLE_UNKNOWN // COMMENT_END - mapped to atk_role_unknown
+#if defined(ATK_CHECK_VERSION)
+ //older ver that doesn't define ATK_CHECK_VERSION doesn't have the following
+ , ATK_ROLE_DOCUMENT_PRESENTATION
+ , ATK_ROLE_DOCUMENT_SPREADSHEET
+ , ATK_ROLE_DOCUMENT_TEXT
+#if ATK_CHECK_VERSION(2,15,2)
+ , ATK_ROLE_STATIC
+#else
+ , ATK_ROLE_LABEL
+#endif
+#else
+ //older version should fallback to DOCUMENT_FRAME role
+ , ATK_ROLE_DOCUMENT_FRAME
+ , ATK_ROLE_DOCUMENT_FRAME
+ , ATK_ROLE_DOCUMENT_FRAME
+ , ATK_ROLE_LABEL
+#endif
+ };
+
+ static bool initialized = false;
+
+ if( ! initialized )
+ {
+ // the accessible roles below were added to ATK in later versions,
+ // with role_for_name we will know if they exist in runtime.
+ roleMap[accessibility::AccessibleRole::EDIT_BAR] = getRoleForName("edit bar");
+ roleMap[accessibility::AccessibleRole::EMBEDDED_OBJECT] = getRoleForName("embedded");
+ roleMap[accessibility::AccessibleRole::CHART] = getRoleForName("chart");
+ roleMap[accessibility::AccessibleRole::CAPTION] = getRoleForName("caption");
+ roleMap[accessibility::AccessibleRole::DOCUMENT] = getRoleForName("document frame");
+ roleMap[accessibility::AccessibleRole::PAGE] = getRoleForName("page");
+ roleMap[accessibility::AccessibleRole::SECTION] = getRoleForName("section");
+ roleMap[accessibility::AccessibleRole::FORM] = getRoleForName("form");
+ roleMap[accessibility::AccessibleRole::GROUP_BOX] = getRoleForName("grouping");
+ roleMap[accessibility::AccessibleRole::COMMENT] = getRoleForName("comment");
+ roleMap[accessibility::AccessibleRole::IMAGE_MAP] = getRoleForName("image map");
+ roleMap[accessibility::AccessibleRole::TREE_ITEM] = getRoleForName("tree item");
+ roleMap[accessibility::AccessibleRole::HYPER_LINK] = getRoleForName("link");
+ roleMap[accessibility::AccessibleRole::END_NOTE] = getRoleForName("footnote");
+ roleMap[accessibility::AccessibleRole::FOOTNOTE] = getRoleForName("footnote");
+ roleMap[accessibility::AccessibleRole::NOTE] = getRoleForName("comment");
+
+ initialized = true;
+ }
+
+ static const sal_Int32 nMapSize = SAL_N_ELEMENTS(roleMap);
+ if( 0 <= nRole && nMapSize > nRole )
+ role = roleMap[nRole];
+
+ return role;
+}
+
+/*****************************************************************************/
+
+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);
+ gint n = 0;
+
+ if( obj->mpContext.is() )
+ {
+ try {
+ n = obj->mpContext->getAccessibleChildCount();
+ }
+ catch(const uno::Exception&) {
+ OSL_FAIL("Exception in getAccessibleChildCount()" );
+ }
+ }
+
+ return n;
+}
+
+/*****************************************************************************/
+
+static AtkObject *
+wrapper_ref_child( AtkObject *atk_obj,
+ gint i )
+{
+ AtkObjectWrapper *obj = ATK_OBJECT_WRAPPER (atk_obj);
+ 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&) {
+ OSL_FAIL("Exception in getAccessibleChild");
+ }
+ }
+
+ return child;
+}
+
+/*****************************************************************************/
+
+static gint
+wrapper_get_index_in_parent( AtkObject *atk_obj )
+{
+ 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 );
+ 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;
+}
+
+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 (ATK_TYPE_OBJECT,
+ "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( rxAccessible.get() != nullptr, 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( rxAccessible.get() != nullptr, nullptr );
+
+ AtkObjectWrapper *pWrap = nullptr;
+
+ try {
+ uno::Reference< accessibility::XAccessibleContext > xContext(rxAccessible->getAccessibleContext());
+
+ g_return_val_if_fail( xContext.get() != nullptr, 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());
+ }
+ }
+
+ 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/cairo_gtk3_cairo.cxx b/vcl/unx/gtk3/cairo_gtk3_cairo.cxx
new file mode 100644
index 000000000..2e882ded6
--- /dev/null
+++ b/vcl/unx/gtk3/cairo_gtk3_cairo.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 "cairo_gtk3_cairo.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(false))
+ {
+ 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/cairo_gtk3_cairo.hxx b/vcl/unx/gtk3/cairo_gtk3_cairo.hxx
new file mode 100644
index 000000000..59ed1437f
--- /dev/null
+++ b/vcl/unx/gtk3/cairo_gtk3_cairo.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/.
+ */
+
+#ifndef INCLUDED_CANVAS_SOURCE_CAIRO_CAIRO_GTK3_CAIRO_HXX
+#define INCLUDED_CANVAS_SOURCE_CAIRO_CAIRO_GTK3_CAIRO_HXX
+
+#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;
+ };
+
+}
+
+#endif
+
+/* 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..1015fcf67
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.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 .
+ */
+
+#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 <tools/urlobj.hxx>
+
+#include <algorithm>
+#include <set>
+#include <string.h>
+
+#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 );
+ gtk_file_chooser_set_preview_widget_active( GTK_FILE_CHOOSER( m_pDialog ), false);
+ }
+ gtk_widget_set_size_request (m_pPreview, -1, -1);
+}
+
+SalGtkFilePicker::SalGtkFilePicker( const uno::Reference< uno::XComponentContext >& xContext ) :
+ SalGtkPicker( xContext ),
+ SalGtkFilePicker_Base( m_rbHelperMtx ),
+ m_pParentWidget ( nullptr ),
+ m_pVBox ( nullptr ),
+ mnHID_FolderChange( 0 ),
+ mnHID_SelectionChange( 0 ),
+ bVersionWidthUnset( false ),
+ mbPreviewState( 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_pAligns[i] = nullptr;
+ m_pLists[i] = nullptr;
+ m_pListLabels[i] = nullptr;
+ mbListVisibility[i] = false;
+ }
+
+ OUString aFilePickerTitle = getResString( FILE_PICKER_TITLE_OPEN );
+
+ m_pDialog = gtk_file_chooser_dialog_new(
+ OUStringToOString( aFilePickerTitle, RTL_TEXTENCODING_UTF8 ).getStr(),
+ nullptr,
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+ nullptr );
+
+ gtk_dialog_set_default_response( GTK_DIALOG (m_pDialog), GTK_RESPONSE_ACCEPT );
+
+#if ENABLE_GIO
+ gtk_file_chooser_set_local_only( GTK_FILE_CHOOSER( m_pDialog ), false );
+#endif
+ gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER( m_pDialog ), false );
+
+ m_pVBox = gtk_vbox_new( false, 0 );
+
+ // We don't want clickable items to have a huge hit-area
+ GtkWidget *pHBox = gtk_hbox_new( false, 0 );
+ GtkWidget *pThinVBox = gtk_vbox_new( false, 0 );
+
+ gtk_box_pack_end (GTK_BOX( m_pVBox ), pHBox, false, false, 0);
+ gtk_box_pack_start (GTK_BOX( pHBox ), pThinVBox, false, false, 0);
+ 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;
+ }
+
+ gtk_box_pack_end( GTK_BOX( pThinVBox ), m_pToggles[i], false, false, 0 );
+ }
+
+ for( i = 0; i < LIST_LAST; i++ )
+ {
+ m_pHBoxs[i] = gtk_hbox_new( false, 0 );
+
+ m_pAligns[i] = gtk_alignment_new(0, 0, 0, 1);
+
+ 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;
+ }
+
+ gtk_container_add( GTK_CONTAINER( m_pAligns[i]), m_pLists[i] );
+ gtk_box_pack_end( GTK_BOX( m_pHBoxs[i] ), m_pAligns[i], false, false, 0 );
+ gtk_box_pack_end( GTK_BOX( m_pHBoxs[i] ), m_pListLabels[i], false, false, 0 );
+ gtk_label_set_mnemonic_widget( GTK_LABEL(m_pListLabels[i]), m_pLists[i] );
+ gtk_box_set_spacing( GTK_BOX( m_pHBoxs[i] ), 12 );
+
+ gtk_box_pack_end( GTK_BOX( m_pVBox ), m_pHBoxs[i], false, false, 0 );
+ }
+
+ aLabel = getResString( FILE_PICKER_FILE_TYPE );
+ m_pFilterExpander = gtk_expander_new_with_mnemonic(
+ OUStringToOString( aLabel, RTL_TEXTENCODING_UTF8 ).getStr());
+
+ gtk_box_pack_end( GTK_BOX( m_pVBox ), m_pFilterExpander, false, true, 0 );
+
+ GtkWidget *scrolled_window = gtk_scrolled_window_new (nullptr, nullptr);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_SHADOW_IN);
+ gtk_container_add (GTK_CONTAINER (m_pFilterExpander), scrolled_window);
+ 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);
+ gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(m_pFilterView), true);
+
+ 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);
+ }
+
+ gtk_container_add (GTK_CONTAINER (scrolled_window), m_pFilterView);
+ gtk_widget_show (m_pFilterView);
+
+ gtk_file_chooser_set_extra_widget( GTK_FILE_CHOOSER( m_pDialog ), m_pVBox );
+
+ m_pPreview = gtk_image_new();
+ gtk_file_chooser_set_preview_widget( GTK_FILE_CHOOSER( m_pDialog ), m_pPreview );
+
+ 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);
+
+ gtk_file_chooser_set_preview_widget_active( GTK_FILE_CHOOSER( m_pDialog ), true);
+}
+
+// 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( const OUString &rFilterString, const char *pMatch )
+{
+ sal_Int32 nIndex = 0;
+ OUString aToken;
+ bool bIsFilter = true;
+
+ OUString aMatch(OUString::createFromAscii(pMatch));
+
+ do
+ {
+ aToken = rFilterString.getToken( 0, ';', nIndex );
+ if( !aToken.match( 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.copy( i + 1, nBracketLen - 1 ), "*." ) )
+ aRealName = aRealName.replaceAt( i, nBracketLen + 1, OUString() );
+ else if (bAllowNoStar)
+ {
+ if( isFilterString( rFilterName.copy( i + 1, nBracketLen - 1 ), ".") )
+ aRealName = aRealName.replaceAt( i, nBracketLen + 1, OUString() );
+ }
+ }
+ }
+
+ return aRealName;
+}
+
+static void
+dialog_remove_buttons(GtkWidget *pActionArea)
+{
+ GtkContainer *pContainer = GTK_CONTAINER( pActionArea );
+
+ g_return_if_fail( pContainer != nullptr );
+
+ GList *pChildren = gtk_container_get_children( pContainer );
+
+ for( GList *p = pChildren; p; p = p->next )
+ {
+ GtkWidget *pWidget = GTK_WIDGET( p->data );
+ if ( GTK_IS_BUTTON( pWidget ) )
+ gtk_widget_destroy( pWidget );
+ }
+
+ g_list_free( pChildren );
+}
+
+static void
+dialog_remove_buttons( GtkDialog *pDialog )
+{
+ g_return_if_fail( GTK_IS_DIALOG( pDialog ) );
+
+ GtkWidget *pHeaderBar = gtk_dialog_get_header_bar(pDialog);
+ if( pHeaderBar != nullptr )
+ dialog_remove_buttons( pHeaderBar );
+ else
+ dialog_remove_buttons(gtk_dialog_get_action_area(pDialog));
+}
+
+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 );
+
+ GSList* pPathList = gtk_file_chooser_get_uris( GTK_FILE_CHOOSER(m_pDialog) );
+
+ int nCount = g_slist_length( pPathList );
+ 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);
+
+ // Convert to OOo
+ for( GSList *pElem = pPathList; pElem; pElem = pElem->next)
+ {
+ gchar *pURI = static_cast<gchar*>(pElem->data);
+ aSelectedFiles[ 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 )
+ {
+ OUString sExtension;
+ nTokenIndex = 0;
+ do
+ sExtension = aSelectedFiles[nIndex].getToken( 0, '.', nTokenIndex );
+ while( nTokenIndex >= 0 );
+
+ if( sExtension.getLength() >= 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(), "*." + 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 = aSelectedFiles[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 );
+ }
+
+ g_slist_free( pPathList );
+
+ return aSelectedFiles;
+}
+
+// XExecutableDialog functions
+
+void SAL_CALL SalGtkFilePicker::setTitle( const OUString& rTitle )
+{
+ SolarMutexGuard g;
+
+ implsetTitle(rTitle);
+}
+
+sal_Int16 SAL_CALL SalGtkFilePicker::execute()
+{
+ SolarMutexGuard g;
+
+ 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);
+
+ mnHID_FolderChange =
+ g_signal_connect( GTK_FILE_CHOOSER( m_pDialog ), "current-folder-changed",
+ G_CALLBACK( folder_changed_cb ), static_cast<gpointer>(this) );
+
+ mnHID_SelectionChange =
+ g_signal_connect( GTK_FILE_CHOOSER( m_pDialog ), "selection-changed",
+ G_CALLBACK( selection_changed_cb ), static_cast<gpointer>(this) );
+
+ 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);
+ RunDialog* pRunDialog = new RunDialog(m_pDialog, xToolkit, xDesktop);
+ uno::Reference < awt::XTopWindowListener > xLifeCycle(pRunDialog);
+ 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 )
+ {
+ OString sFileName = unicodetouri( aPathSeq[0] );
+ gchar *gFileName = g_filename_from_uri ( sFileName.getStr(), nullptr, nullptr );
+ if( g_file_test( gFileName, G_FILE_TEST_IS_REGULAR ) )
+ {
+ INetURLObject aFileObj( OStringToOUString(sFileName, RTL_TEXTENCODING_UTF8) );
+
+ 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()
+ );
+
+ 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));
+ RunDialog* pAnotherDialog = new RunDialog(dlg, xToolkit, xDesktop);
+ uno::Reference < awt::XTopWindowListener > xAnotherLifeCycle(pAnotherDialog);
+ btn = pAnotherDialog->run();
+
+ gtk_widget_destroy( dlg );
+ }
+ g_free (gFileName);
+
+ 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_TOGGLE_BUTTON; //prevent warning by initializing
+ GtkWidget *pWidget = nullptr;
+
+#define MAP_TOGGLE( elem ) \
+ case ExtendedFilePickerElementIds::CHECKBOX_##elem: \
+ pWidget = m_pToggles[elem]; tType = GTK_TYPE_TOGGLE_BUTTON; \
+ break
+#define MAP_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( 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;
+ gtk_widget_size_request(GTK_WIDGET(pWidget), &requisition);
+ gtk_widget_set_size_request(GTK_WIDGET(pWidget), requisition.width, -1);
+}
+
+static void ComboBoxAppendText(GtkComboBox *pCombo, const OUString &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);
+ for (sal_Int32 i=0; i < nSize; ++i)
+ {
+ gchar *item;
+ gtk_tree_model_get(gtk_combo_box_get_model(pWidget),
+ &iter, 0, &item, -1);
+ aItemList[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_TOGGLE_BUTTON )
+ {
+ bool bChecked = false;
+ rValue >>= bChecked;
+ gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( pWidget ), bChecked );
+ }
+ 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_TOGGLE_BUTTON )
+ aRetval <<= bool( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( pWidget ) ) );
+ 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 on unknown control " << nControlId);
+ return;
+ }
+
+ OString aTxt = OUStringToOString( rLabel.replace('~', '_'), RTL_TEXTENCODING_UTF8 );
+ if (nControlId == ExtendedFilePickerElementIds::PUSHBUTTON_PLAY)
+ {
+#ifdef GTK_STOCK_MEDIA_PLAY
+ if (msPlayLabel.isEmpty())
+ msPlayLabel = rLabel;
+ if (msPlayLabel == rLabel)
+ gtk_button_set_label(GTK_BUTTON(pWidget), GTK_STOCK_MEDIA_PLAY);
+ else
+ gtk_button_set_label(GTK_BUTTON(pWidget), GTK_STOCK_MEDIA_STOP);
+#else
+ gtk_button_set_label(GTK_BUTTON(pWidget), aTxt.getStr());
+#endif
+ }
+ else if( tType == GTK_TYPE_TOGGLE_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_TOGGLE_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 )
+{
+ 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 );
+}
+
+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;
+}
+
+// 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;
+
+ css::uno::Reference<css::awt::XWindow> xParentWindow;
+ if (aArguments.getLength() > 1)
+ {
+ aArguments[1] >>= xParentWindow;
+ }
+
+ if (xParentWindow.is())
+ {
+ if (SalGtkXWindow* pGtkXWindow = dynamic_cast<SalGtkXWindow*>(xParentWindow.get()))
+ m_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()));
+ aAny = xSysDepWin->getWindowHandle(aProcessIdent, css::lang::SystemDependent::SYSTEM_XWINDOW);
+ css::awt::SystemDependentXWindow tmp;
+ aAny >>= tmp;
+ m_pParentWidget = GetGtkSalData()->GetGtkDisplay()->findGtkWidgetForNativeHandle(tmp.WindowHandle);
+ }
+ }
+ }
+
+ GtkFileChooserAction eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ const gchar *first_button_text = GTK_STOCK_OPEN;
+
+ 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 = GTK_STOCK_OPEN;
+ break;
+ case FILESAVE_SIMPLE:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = GTK_STOCK_SAVE;
+ break;
+ case FILESAVE_AUTOEXTENSION_PASSWORD:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = GTK_STOCK_SAVE;
+ mbToggleVisibility[PASSWORD] = true;
+ mbToggleVisibility[GPGENCRYPTION] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = GTK_STOCK_SAVE;
+ 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 = GTK_STOCK_SAVE;
+ mbToggleVisibility[SELECTION] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION_TEMPLATE:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = GTK_STOCK_SAVE;
+ mbListVisibility[TEMPLATE] = true;
+ // TODO
+ break;
+ case FILEOPEN_LINK_PREVIEW_IMAGE_TEMPLATE:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = GTK_STOCK_OPEN;
+ 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 = GTK_STOCK_OPEN;
+ mbToggleVisibility[LINK] = true;
+ mbToggleVisibility[PREVIEW] = true;
+ mbListVisibility[IMAGE_ANCHOR] = true;
+ // TODO
+ break;
+ case FILEOPEN_PLAY:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = GTK_STOCK_OPEN;
+ mbButtonVisibility[PLAY] = true;
+ // TODO
+ break;
+ case FILEOPEN_LINK_PLAY:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = GTK_STOCK_OPEN;
+ mbToggleVisibility[LINK] = true;
+ mbButtonVisibility[PLAY] = true;
+ // TODO
+ break;
+ case FILEOPEN_READONLY_VERSION:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = GTK_STOCK_OPEN;
+ mbToggleVisibility[READONLY] = true;
+ mbListVisibility[VERSION] = true;
+ break;
+ case FILEOPEN_LINK_PREVIEW:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = GTK_STOCK_OPEN;
+ mbToggleVisibility[LINK] = true;
+ mbToggleVisibility[PREVIEW] = true;
+ // TODO
+ break;
+ case FILESAVE_AUTOEXTENSION:
+ eAction = GTK_FILE_CHOOSER_ACTION_SAVE;
+ first_button_text = GTK_STOCK_SAVE;
+ // TODO
+ break;
+ case FILEOPEN_PREVIEW:
+ eAction = GTK_FILE_CHOOSER_ACTION_OPEN;
+ first_button_text = GTK_STOCK_OPEN;
+ 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);
+ dialog_remove_buttons( GTK_DIALOG( m_pDialog ) );
+ gtk_dialog_add_button( GTK_DIALOG( m_pDialog ), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL );
+ for( int nTVIndex = 0; nTVIndex < BUTTON_LAST; nTVIndex++ )
+ {
+ if( mbButtonVisibility[nTVIndex] )
+ {
+#ifdef GTK_STOCK_MEDIA_PLAY
+ m_pButtons[ nTVIndex ] = gtk_dialog_add_button( GTK_DIALOG( m_pDialog ), GTK_STOCK_MEDIA_PLAY, 1 );
+#else
+ OString aPlay = OUStringToOString( getResString( PUSHBUTTON_PLAY ), RTL_TEXTENCODING_UTF8 );
+ m_pButtons[ nTVIndex ] = gtk_dialog_add_button( GTK_DIALOG( m_pDialog ), aPlay.getStr(), 1 );
+#endif
+ }
+ }
+ gtk_dialog_add_button( GTK_DIALOG( m_pDialog ), first_button_text, GTK_RESPONSE_ACCEPT );
+
+ 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_pAligns[ nTVIndex ] );
+ gtk_widget_show( m_pHBoxs[ nTVIndex ] );
+ }
+ }
+}
+
+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
+ GSList *filters = gtk_file_chooser_list_filters ( GTK_FILE_CHOOSER( m_pDialog ) );
+ bool bFound = false;
+
+ for( GSList *iter = filters; !bFound && iter; iter = iter->next )
+ {
+ GtkFileFilter* pFilter = static_cast<GtkFileFilter *>( iter->data );
+ 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 );
+ bFound = true;
+ }
+ }
+
+ g_slist_free( filters );
+}
+
+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;
+}
+}
+
+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);
+ gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_URI,
+ case_insensitive_filter,
+ g_strdup( OUStringToOString(aToken, RTL_TEXTENCODING_UTF8).getStr() ),
+ g_free );
+
+ SAL_INFO( "vcl.gtk", "fustering with " << aToken );
+ }
+#if OSL_DEBUG_LEVEL > 0
+ else
+ {
+ g_warning( "Duff filter token '%s'\n",
+ OUStringToOString(
+ rType.getToken( 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.makeStringAndClear(), RTL_TEXTENCODING_UTF8).getStr(),
+ 2, aFilterName.getStr(),
+ 3, OUStringToOString(rType, RTL_TEXTENCODING_UTF8).getStr(),
+ -1);
+ }
+ return filter;
+}
+
+void SalGtkFilePicker::implAddFilterGroup( const OUString& /*_rFilter*/, 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( filter.getTitle(), 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()
+{
+ 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_pAligns[i] ); //m_pAligns[i] owns m_pLists[i]
+ gtk_widget_destroy( m_pHBoxs[i] );
+ }
+
+ m_pFilterVector.reset();
+
+ gtk_widget_destroy( m_pVBox );
+}
+
+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..39bb6815b
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFilePicker.hxx
@@ -0,0 +1,240 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_FPICKER_SALGTKFILEPICKER_HXX
+#define INCLUDED_VCL_UNX_GTK_FPICKER_SALGTKFILEPICKER_HXX
+
+#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 );
+
+ private:
+ css::uno::Reference< css::ui::dialogs::XFilePickerListener >
+ m_xListener;
+ OUString msPlayLabel;
+ std::unique_ptr<std::vector<FilterEntry>> m_pFilterVector;
+ GtkWidget *m_pParentWidget;
+ 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 {
+ PLAY,
+ BUTTON_LAST };
+
+ GtkWidget *m_pButtons[ BUTTON_LAST ];
+
+ enum {
+ VERSION,
+ TEMPLATE,
+ IMAGE_TEMPLATE,
+ IMAGE_ANCHOR,
+ LIST_LAST
+ };
+
+ GtkWidget *m_pHBoxs[ LIST_LAST ];
+ GtkWidget *m_pAligns[ 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;
+ 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 OUString& rFilter,
+ 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;
+
+};
+#endif // INCLUDED_VCL_UNX_GTK_FPICKER_SALGTKFILEPICKER_HXX
+
+/* 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..bb940cf5f
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkFolderPicker.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 .
+ */
+
+#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, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, nullptr );
+
+ gtk_dialog_set_default_response( GTK_DIALOG (m_pDialog), GTK_RESPONSE_ACCEPT );
+#if ENABLE_GIO
+ gtk_file_chooser_set_local_only( GTK_FILE_CHOOSER( m_pDialog ), false );
+#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 );
+
+ gtk_file_chooser_set_current_folder_uri( GTK_FILE_CHOOSER( m_pDialog ),
+ aTxt.getStr() );
+}
+
+OUString SAL_CALL SalGtkFolderPicker::getDisplayDirectory()
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+ gchar* pCurrentFolder =
+ gtk_file_chooser_get_current_folder_uri( GTK_FILE_CHOOSER( m_pDialog ) );
+ OUString aCurrentFolderName = uritounicode(pCurrentFolder);
+ g_free( pCurrentFolder );
+
+ return aCurrentFolderName;
+}
+
+OUString SAL_CALL SalGtkFolderPicker::getDirectory()
+{
+ SolarMutexGuard g;
+
+ assert( m_pDialog != nullptr );
+
+ gchar* pSelectedFolder =
+ gtk_file_chooser_get_uri( GTK_FILE_CHOOSER( m_pDialog ) );
+ 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 = RunDialog::GetTransientFor();
+ if (pParent)
+ gtk_window_set_transient_for(GTK_WINDOW(m_pDialog), pParent);
+ RunDialog* pRunDialog = new RunDialog(m_pDialog, xToolkit, xDesktop);
+ uno::Reference < awt::XTopWindowListener > xLifeCycle(pRunDialog);
+ 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;
+}
+
+// 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..229bbe8b8
--- /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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_FPICKER_SALGTKFOLDERPICKER_HXX
+#define INCLUDED_VCL_UNX_GTK_FPICKER_SALGTKFOLDERPICKER_HXX
+
+#include <list>
+#include <memory>
+#include <rtl/ustring.hxx>
+#include <cppuhelper/implbase.hxx>
+
+#include "SalGtkPicker.hxx"
+
+class SalGtkFolderPicker :
+ public SalGtkPicker,
+ public cppu::WeakImplHelper< css::ui::dialogs::XFolderPicker2 >
+{
+ 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;
+
+ // XCancellable
+
+ virtual void SAL_CALL cancel( ) override;
+
+ private:
+ SalGtkFolderPicker( const SalGtkFolderPicker& ) = delete;
+ SalGtkFolderPicker& operator=( const SalGtkFolderPicker& ) = delete;
+};
+
+#endif // INCLUDED_VCL_UNX_GTK_FPICKER_SALGTKFOLDERPICKER_HXX
+
+/* 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..c847774d1
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkPicker.cxx
@@ -0,0 +1,266 @@
+/* -*- 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/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)
+{
+ 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)
+{
+ // 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()
+{
+ GtkWindow *pParent = nullptr;
+
+ vcl::Window * pWindow = ::Application::GetActiveTopWindow();
+ if( pWindow )
+ {
+ GtkSalFrame *pFrame = dynamic_cast<GtkSalFrame *>( pWindow->ImplGetFrame() );
+ if( pFrame )
+ pParent = GTK_WINDOW(gtk_widget_get_toplevel(pFrame->getWindow()));
+ }
+
+ return pParent;
+}
+
+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);
+ gint nStatus = gtk_dialog_run(GTK_DIALOG(mpDialog));
+ 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_pDialog( nullptr ), m_xContext( xContext )
+{
+}
+
+SalGtkPicker::~SalGtkPicker()
+{
+ SolarMutexGuard g;
+
+ if (m_pDialog)
+ {
+ gtk_widget_destroy(m_pDialog);
+ }
+}
+
+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 );
+
+ gtk_file_chooser_set_current_folder_uri( GTK_FILE_CHOOSER( m_pDialog ),
+ aTxt.getStr() );
+}
+
+OUString SalGtkPicker::implgetDisplayDirectory()
+{
+ OSL_ASSERT( m_pDialog != nullptr );
+
+ gchar* pCurrentFolder =
+ gtk_file_chooser_get_current_folder_uri( GTK_FILE_CHOOSER( m_pDialog ) );
+ OUString aCurrentFolderName = uritounicode(pCurrentFolder);
+ g_free( pCurrentFolder );
+
+ return aCurrentFolderName;
+}
+
+void SalGtkPicker::implsetTitle( const OUString& 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..46d6d9278
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/SalGtkPicker.hxx
@@ -0,0 +1,117 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_FPICKER_SALGTKPICKER_HXX
+#define INCLUDED_VCL_UNX_GTK_FPICKER_SALGTKPICKER_HXX
+
+#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 <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_pDialog;
+ protected:
+ /// @throws css::uno::RuntimeException
+ void implsetTitle( const OUString& 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);
+ OString unicodetouri(const OUString &rURL);
+
+ // to instantiate own services
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+ 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();
+};
+
+#endif
+
+/* 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..88a472e0e
--- /dev/null
+++ b/vcl/unx/gtk3/fpicker/eventnotification.hxx
@@ -0,0 +1,43 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK_FPICKER_EVENTNOTIFICATION_HXX
+#define INCLUDED_VCL_UNX_GTK_FPICKER_EVENTNOTIFICATION_HXX
+
+#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;
+};
+
+#endif
+
+/* 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..5ad801a6e
--- /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 <strings.hrc>
+#include <svdata.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
+
+static const struct
+{
+ sal_Int32 ctrlId;
+ const char *resId;
+} CtrlIdToResIdTable[] = {
+ { CHECKBOX_AUTOEXTENSION, STR_FPICKER_AUTO_EXTENSION },
+ { CHECKBOX_PASSWORD, STR_FPICKER_PASSWORD },
+ { CHECKBOX_GPGENCRYPTION, STR_FPICKER_GPGENCRYPT },
+ { CHECKBOX_FILTEROPTIONS, STR_FPICKER_FILTER_OPTIONS },
+ { CHECKBOX_READONLY, STR_FPICKER_READONLY },
+ { CHECKBOX_LINK, STR_FPICKER_INSERT_AS_LINK },
+ { CHECKBOX_PREVIEW, STR_FPICKER_SHOW_PREVIEW },
+ { PUSHBUTTON_PLAY, STR_FPICKER_PLAY },
+ { LISTBOX_VERSION_LABEL, STR_FPICKER_VERSION },
+ { LISTBOX_TEMPLATE_LABEL, STR_FPICKER_TEMPLATES },
+ { LISTBOX_IMAGE_TEMPLATE_LABEL, STR_FPICKER_IMAGE_TEMPLATE },
+ { LISTBOX_IMAGE_ANCHOR_LABEL, STR_FPICKER_IMAGE_ANCHOR },
+ { CHECKBOX_SELECTION, STR_FPICKER_SELECTION },
+ { FOLDERPICKER_TITLE, STR_FPICKER_FOLDER_DEFAULT_TITLE },
+ { FOLDER_PICKER_DEF_DESCRIPTION, STR_FPICKER_FOLDER_DEFAULT_DESCRIPTION },
+ { FILE_PICKER_OVERWRITE_PRIMARY, STR_FPICKER_ALREADYEXISTOVERWRITE_PRIMARY },
+ { FILE_PICKER_OVERWRITE_SECONDARY, STR_FPICKER_ALREADYEXISTOVERWRITE_SECONDARY },
+ { FILE_PICKER_ALLFORMATS, STR_FPICKER_ALLFORMATS },
+ { FILE_PICKER_TITLE_OPEN, STR_FPICKER_OPEN },
+ { FILE_PICKER_TITLE_SAVE, STR_FPICKER_SAVE },
+ { FILE_PICKER_FILE_TYPE, STR_FPICKER_TYPE }
+};
+
+static const char* CtrlIdToResId( sal_Int32 aControlId )
+{
+ for (auto & i : CtrlIdToResIdTable)
+ {
+ if ( i.ctrlId == aControlId )
+ return i.resId;
+ }
+ return nullptr;
+}
+
+OUString SalGtkPicker::getResString( sal_Int32 aId )
+{
+ OUString aResString;
+ // translate the control id to a resource id
+ const char *pResId = CtrlIdToResId( aId );
+ if (pResId)
+ aResString = VclResId(pResId);
+ return aResString.replace('~', '_');
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3gloactiongroup.cxx b/vcl/unx/gtk3/gtk3gloactiongroup.cxx
new file mode 100644
index 000000000..c6d5eaf03
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3gloactiongroup.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)
+ {
+ 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/gtk3glomenu.cxx b/vcl/unx/gtk3/gtk3glomenu.cxx
new file mode 100644
index 000000000..ca6887cb9
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3glomenu.cxx
@@ -0,0 +1,692 @@
+/* -*- 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/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 && section < static_cast<gint>(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 && section < static_cast<gint>(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 || position >= static_cast<gint>(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 || position > static_cast<gint>(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 && section < static_cast<gint>(menu->items->len));
+
+ GLOMenu* model = g_lo_menu_get_section (menu, section);
+
+ g_return_if_fail (model != nullptr);
+
+ if (0 <= position && position < static_cast<gint>(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 && section < static_cast<gint>(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 && position < static_cast<gint>(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 && position < static_cast<gint>(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 && section < static_cast<gint>(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/gtk3gtkdata.cxx b/vcl/unx/gtk3/gtk3gtkdata.cxx
new file mode 100644
index 000000000..3182d1551
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3gtkdata.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 <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 <osl/thread.h>
+#include <osl/process.h>
+
+#include <vcl/svapp.hxx>
+#include <sal/log.hxx>
+
+#include <chrono>
+
+
+
+using namespace vcl_sal;
+
+/***************************************************************
+ * class GtkSalDisplay *
+ ***************************************************************/
+extern "C" {
+static GdkFilterReturn call_filterGdkEvent( GdkXEvent* sys_event,
+ GdkEvent* /*event*/,
+ gpointer data )
+{
+ GtkSalDisplay *pDisplay = static_cast<GtkSalDisplay *>(data);
+ return pDisplay->filterGdkEvent( sys_event );
+}
+}
+
+GtkSalDisplay::GtkSalDisplay( GdkDisplay* pDisplay ) :
+ m_pSys( GtkSalSystem::GetSingleton() ),
+ m_pGdkDisplay( pDisplay ),
+ m_bStartupCompleted( false )
+{
+ for(GdkCursor* & rpCsr : m_aCursors)
+ rpCsr = nullptr;
+
+ // FIXME: unify this with SalInst's filter too ?
+ gdk_window_add_filter( nullptr, call_filterGdkEvent, this );
+
+ if ( getenv( "SAL_IGNOREXERRORS" ) )
+ GetGenericUnixSalData()->ErrorTrapPush(); // and leak the trap
+
+ m_bX11Display = DLSYM_GDK_IS_X11_DISPLAY( m_pGdkDisplay );
+
+ gtk_widget_set_default_direction(AllSettings::GetLayoutRTL() ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
+}
+
+GtkSalDisplay::~GtkSalDisplay()
+{
+ gdk_window_remove_filter( nullptr, call_filterGdkEvent, this );
+
+ if( !m_bStartupCompleted )
+ gdk_notify_startup_complete();
+
+ for(GdkCursor* & rpCsr : m_aCursors)
+ if( rpCsr )
+ gdk_cursor_unref( rpCsr );
+}
+
+extern "C" {
+
+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 );
+}
+
+}
+
+GdkFilterReturn GtkSalDisplay::filterGdkEvent( GdkXEvent* )
+{
+ (void) this; // loplugin:staticmethods
+ //FIXME: implement filterGdkEvent ...
+ return GDK_FILTER_CONTINUE;
+}
+
+void GtkSalDisplay::screenSizeChanged( GdkScreen const * pScreen )
+{
+ m_pSys->countScreenMonitors();
+ if (pScreen)
+ emitDisplayChanged();
+}
+
+void GtkSalDisplay::monitorsChanged( GdkScreen const * pScreen )
+{
+ m_pSys->countScreenMonitors();
+ if (pScreen)
+ emitDisplayChanged();
+}
+
+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;
+
+ 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);
+ GdkCursor* pCursor = gdk_cursor_new_from_pixbuf(m_pGdkDisplay, pScaledPixBuf,
+ nXHot * fScalefactor, nYHot * fScalefactor);
+ return pCursor;
+}
+
+#define MAKE_CURSOR( vcl_name, name, name2 ) \
+ case vcl_name: \
+ pCursor = getFromSvg(name2, name##curs_x_hot, name##curs_y_hot); \
+ break
+#define MAP_BUILTIN( vcl_name, gdk_name ) \
+ case vcl_name: \
+ pCursor = gdk_cursor_new_for_display( m_pGdkDisplay, gdk_name ); \
+ break
+
+GdkCursor *GtkSalDisplay::getCursor( PointerStyle ePointerStyle )
+{
+ if ( !m_aCursors[ ePointerStyle ] )
+ {
+ GdkCursor *pCursor = nullptr;
+
+ switch( ePointerStyle )
+ {
+ MAP_BUILTIN( PointerStyle::Arrow, GDK_LEFT_PTR );
+ MAP_BUILTIN( PointerStyle::Text, GDK_XTERM );
+ MAP_BUILTIN( PointerStyle::Help, GDK_QUESTION_ARROW );
+ MAP_BUILTIN( PointerStyle::Cross, GDK_CROSSHAIR );
+ MAP_BUILTIN( PointerStyle::Wait, GDK_WATCH );
+
+ MAP_BUILTIN( PointerStyle::NSize, GDK_SB_V_DOUBLE_ARROW );
+ MAP_BUILTIN( PointerStyle::SSize, GDK_SB_V_DOUBLE_ARROW );
+ MAP_BUILTIN( PointerStyle::WSize, GDK_SB_H_DOUBLE_ARROW );
+ MAP_BUILTIN( PointerStyle::ESize, GDK_SB_H_DOUBLE_ARROW );
+
+ MAP_BUILTIN( PointerStyle::NWSize, GDK_TOP_LEFT_CORNER );
+ MAP_BUILTIN( PointerStyle::NESize, GDK_TOP_RIGHT_CORNER );
+ MAP_BUILTIN( PointerStyle::SWSize, GDK_BOTTOM_LEFT_CORNER );
+ MAP_BUILTIN( PointerStyle::SESize, GDK_BOTTOM_RIGHT_CORNER );
+
+ MAP_BUILTIN( PointerStyle::WindowNSize, GDK_TOP_SIDE );
+ MAP_BUILTIN( PointerStyle::WindowSSize, GDK_BOTTOM_SIDE );
+ MAP_BUILTIN( PointerStyle::WindowWSize, GDK_LEFT_SIDE );
+ MAP_BUILTIN( PointerStyle::WindowESize, GDK_RIGHT_SIDE );
+
+ MAP_BUILTIN( PointerStyle::WindowNWSize, GDK_TOP_LEFT_CORNER );
+ MAP_BUILTIN( PointerStyle::WindowNESize, GDK_TOP_RIGHT_CORNER );
+ MAP_BUILTIN( PointerStyle::WindowSWSize, GDK_BOTTOM_LEFT_CORNER );
+ MAP_BUILTIN( PointerStyle::WindowSESize, GDK_BOTTOM_RIGHT_CORNER );
+
+ MAP_BUILTIN( PointerStyle::HSizeBar, GDK_SB_H_DOUBLE_ARROW );
+ MAP_BUILTIN( PointerStyle::VSizeBar, GDK_SB_V_DOUBLE_ARROW );
+
+ MAP_BUILTIN( PointerStyle::RefHand, GDK_HAND2 );
+ MAP_BUILTIN( PointerStyle::Hand, GDK_HAND2 );
+ MAP_BUILTIN( PointerStyle::Pen, GDK_PENCIL );
+
+ MAP_BUILTIN( PointerStyle::HSplit, GDK_SB_H_DOUBLE_ARROW );
+ MAP_BUILTIN( PointerStyle::VSplit, GDK_SB_V_DOUBLE_ARROW );
+
+ MAP_BUILTIN( PointerStyle::Move, GDK_FLEUR );
+
+ MAKE_CURSOR( PointerStyle::Null, null, RID_CURSOR_NULL );
+ 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 );
+ MAKE_CURSOR( PointerStyle::NotAllowed, nodrop_, RID_CURSOR_NOT_ALLOWED );
+ 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 );
+
+ default:
+ SAL_WARN( "vcl.gtk", "pointer " << static_cast<int>(ePointerStyle) << "not implemented" );
+ break;
+ }
+ if( !pCursor )
+ pCursor = gdk_cursor_new_for_display( m_pGdkDisplay, GDK_LEFT_PTR );
+
+ 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( SalInstance *pInstance )
+ : GenericUnixSalData( SAL_DATA_GTK3, pInstance )
+ , m_aDispatchMutex()
+ , m_aDispatchCondition()
+ , m_pDocumentFocusListener(nullptr)
+{
+ 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()
+{
+ Yield( true, true );
+ g_warning ("TESTME: We used to have a stop-timer here, but the central code should do this");
+
+ // 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;
+}
+
+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
+ gtk_init_check( &nParams, &pCmdLineAry );
+ gdk_error_trap_push();
+
+ 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);
+ }
+
+#if defined(GDK_WINDOWING_X11)
+ if (DLSYM_GDK_IS_X11_DISPLAY(pGdkDisp))
+ aOrigXIOErrorHandler = XSetIOErrorHandler(XIOErrorHdl);
+#endif
+
+ GtkSalDisplay *pDisplay = new GtkSalDisplay( pGdkDisp );
+ SetDisplay( pDisplay );
+
+ //FIXME: unwind keyboard extension bits
+
+ // add signal handler to notify screen size changes
+ 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 )
+ {
+ pDisplay->screenSizeChanged( pScreen );
+ pDisplay->monitorsChanged( pScreen );
+ g_signal_connect( G_OBJECT(pScreen), "size-changed",
+ G_CALLBACK(signalScreenSizeChanged), pDisplay );
+ g_signal_connect( G_OBJECT(pScreen), "monitors-changed",
+ G_CALLBACK(signalMonitorsChanged), GetGtkDisplay() );
+ }
+ }
+}
+
+void GtkSalData::ErrorTrapPush()
+{
+ gdk_error_trap_push ();
+}
+
+bool GtkSalData::ErrorTrapPop( bool bIgnoreError )
+{
+ if (bIgnoreError)
+ {
+ gdk_error_trap_pop_ignored (); // faster
+ return false;
+ }
+ return gdk_error_trap_pop () != 0;
+}
+
+#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()
+{
+ GtkInstance *pInstance = static_cast<GtkInstance *>(GetSalData()->m_pInstance);
+ pInstance->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->aWindow == 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 );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3gtkframe.cxx b/vcl/unx/gtk3/gtk3gtkframe.cxx
new file mode 100644
index 000000000..69a554108
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3gtkframe.cxx
@@ -0,0 +1,4609 @@
+/* -*- 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/keycodes.hxx>
+#include <unx/geninst.h>
+#include <headless/svpgdi.hxx>
+#include <sal/log.hxx>
+#include <tools/diagnose_ex.h>
+#include <vcl/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/accessibility/XAccessibleEditableText.hpp>
+#include <com/sun/star/awt/MouseButton.hpp>
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+
+using namespace com::sun::star;
+
+int GtkSalFrame::m_nFloats = 0;
+
+static GDBusConnection* pSessionBus = 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_MOD1_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;
+}
+
+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( keyval >= GDK_KEY_F1 && keyval <= GDK_KEY_F26 )
+ // KEY_F26 is the last function key known to keycodes.hxx
+ {
+ switch( keyval )
+ {
+ // - - - - - Sun keyboard, see vcl/unx/source/app/saldisp.cxx
+ // 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;
+}
+
+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;
+}
+
+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_pFixedContainer));
+ 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( aEvent.mnCode == 0 )
+ {
+ gint best_group = SAL_MAX_INT32;
+
+ // Try and find Latin layout
+ GdkKeymap* keymap = gdk_keymap_get_default();
+ GdkKeymapKey *keys;
+ gint n_keys;
+ if (gdk_keymap_get_entries_for_keyval(keymap, GDK_KEY_A, &keys, &n_keys))
+ {
+ // Find the lowest group that supports Latin layout
+ for (gint i = 0; i < n_keys; ++i)
+ {
+ if (keys[i].level != 0 && keys[i].level != 1)
+ continue;
+ best_group = std::min(best_group, keys[i].group);
+ if (best_group == 0)
+ break;
+ }
+ g_free(keys);
+ }
+
+ //Unavailable, go with original group then I suppose
+ if (best_group == SAL_MAX_INT32)
+ best_group = group;
+
+ guint updated_keyval = GetKeyValFor(keymap, hardware_keycode, best_group);
+ aEvent.mnCode = GetKeyCode(updated_keyval);
+ }
+
+ aEvent.mnCode |= GetKeyModCode( state );
+
+ 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)
+{
+ 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)
+{
+ 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 );
+ }
+}
+
+static void hud_activated( gboolean hud_active, gpointer user_data )
+{
+ if ( hud_active )
+ {
+ SolarMutexGuard aGuard;
+ GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
+ GtkSalMenu* pSalMenu = reinterpret_cast< GtkSalMenu* >( pSalFrame->GetMenu() );
+
+ if ( pSalMenu )
+ pSalMenu->UpdateFull();
+ }
+}
+
+static bool ensure_dbus_setup( gpointer data )
+{
+ GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( data );
+ GdkWindow* gdkWindow = gtk_widget_get_window( pSalFrame->getWindow() );
+
+ if ( gdkWindow != nullptr && g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) == nullptr )
+ {
+ // Get a DBus session connection.
+ if(!pSessionBus)
+ pSessionBus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, nullptr);
+ if( !pSessionBus )
+ {
+ return false;
+ }
+
+ // Create menu model and action group attached to this frame.
+ GMenuModel* pMenuModel = G_MENU_MODEL( g_lo_menu_new() );
+ GActionGroup* pActionGroup = reinterpret_cast<GActionGroup*>(g_lo_action_group_new());
+
+ // Generate menu paths.
+ sal_uIntPtr windowId = pSalFrame->GetNativeWindowHandle(pSalFrame->getWindow());
+ gchar* aDBusWindowPath = g_strdup_printf( "/org/libreoffice/window/%lu", windowId );
+ gchar* aDBusMenubarPath = g_strdup_printf( "/org/libreoffice/window/%lu/menus/menubar", windowId );
+
+ // Set window properties.
+ g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-menubar", pMenuModel, ObjectDestroyedNotify );
+ g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-action-group", pActionGroup, ObjectDestroyedNotify );
+
+ GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
+#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 );
+ }
+
+ return false;
+}
+
+void on_registrar_available( GDBusConnection * /*connection*/,
+ const gchar * /*name*/,
+ const gchar * /*name_owner*/,
+ gpointer user_data )
+{
+ SolarMutexGuard aGuard;
+
+ GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
+
+ SalMenu* pSalMenu = pSalFrame->GetMenu();
+
+ if ( pSalMenu != nullptr )
+ {
+ GtkSalMenu* pGtkSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
+ pGtkSalMenu->EnableUnity(true);
+ }
+}
+
+// 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 )
+ {
+ // Get a DBus session connection.
+ if ( pSessionBus == nullptr )
+ {
+ pSessionBus = g_bus_get_sync( G_BUS_TYPE_SESSION, nullptr, nullptr );
+
+ if ( pSessionBus == nullptr )
+ return;
+ }
+
+ // Publish the menu only if AppMenu registrar is available.
+ m_nWatcherId = g_bus_watch_name_on_connection( pSessionBus,
+ "com.canonical.AppMenu.Registrar",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ on_registrar_available,
+ on_registrar_unavailable,
+ this,
+ nullptr );
+ }
+}
+
+void GtkSalFrame::InvalidateGraphics()
+{
+ if( m_pGraphics )
+ {
+ m_bGraphics = false;
+ }
+}
+
+GtkSalFrame::~GtkSalFrame()
+{
+ m_aSmoothScrollIdle.Stop();
+ m_aSmoothScrollIdle.ClearInvokeHandler();
+
+ 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();
+
+ GtkWidget *pEventWidget = getMouseEventWidget();
+ for (auto handler_id : m_aMouseSignalIds)
+ g_signal_handler_disconnect(G_OBJECT(pEventWidget), handler_id);
+ if( m_pFixedContainer )
+ gtk_widget_destroy( GTK_WIDGET( m_pFixedContainer ) );
+ if( m_pEventBox )
+ gtk_widget_destroy( GTK_WIDGET(m_pEventBox) );
+ if( m_pTopLevelGrid )
+ gtk_widget_destroy( GTK_WIDGET(m_pTopLevelGrid) );
+ {
+ SolarMutexGuard aGuard;
+
+ if(m_nWatcherId)
+ g_bus_unwatch_name(m_nWatcherId);
+
+ 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 );
+ }
+ gtk_widget_destroy( m_pWindow );
+ }
+ }
+ if( m_pForeignParent )
+ g_object_unref( G_OBJECT( m_pForeignParent ) );
+ if( m_pForeignTopLevel )
+ g_object_unref( G_OBJECT( m_pForeignTopLevel) );
+
+ m_pGraphics.reset();
+
+ if (m_pSurface)
+ cairo_surface_destroy(m_pSurface);
+}
+
+void GtkSalFrame::moveWindow( long nX, long nY )
+{
+ if( isChild( false ) )
+ {
+ GtkWidget* pParent = m_pParent ? gtk_widget_get_parent(m_pWindow) : nullptr;
+ // tdf#130414 its 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 );
+ }
+ }
+ else
+ gtk_window_move( GTK_WINDOW(m_pWindow), nX, nY );
+}
+
+void GtkSalFrame::widget_set_size_request(long nWidth, long nHeight)
+{
+ gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), nWidth, nHeight );
+}
+
+void GtkSalFrame::window_resize(long nWidth, long nHeight)
+{
+ m_nWidthRequest = nWidth;
+ m_nHeightRequest = nHeight;
+ gtk_window_set_default_size(GTK_WINDOW(m_pWindow), nWidth, nHeight);
+ if (gtk_widget_get_visible(m_pWindow))
+ gtk_window_resize(GTK_WINDOW(m_pWindow), nWidth, nHeight);
+}
+
+void GtkSalFrame::resizeWindow( long nWidth, long nHeight )
+{
+ if( isChild( false ) )
+ {
+ widget_set_size_request(nWidth, nHeight);
+ }
+ else if( ! isChild( true, false ) )
+ window_resize(nWidth, nHeight);
+}
+
+// tdf#124694 GtkFixed takes the max size of all its children as its
+// preferred size, causing it to not clip its child, but grow instead.
+
+static void
+ooo_fixed_get_preferred_height(GtkWidget*, gint *minimum, gint *natural)
+{
+ *minimum = 0;
+ *natural = 0;
+}
+
+static void
+ooo_fixed_get_preferred_width(GtkWidget*, gint *minimum, gint *natural)
+{
+ *minimum = 0;
+ *natural = 0;
+}
+
+static void
+ooo_fixed_class_init(GtkFixedClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+ widget_class->get_accessible = ooo_fixed_get_accessible;
+ widget_class->get_preferred_height = ooo_fixed_get_preferred_height;
+ widget_class->get_preferred_width = ooo_fixed_get_preferred_width;
+}
+
+/*
+ * Always use a sub-class of GtkFixed we can tag for a11y. This allows us to
+ * utilize GAIL for the toplevel window and toolkit implementation incl.
+ * key event listener support ..
+ */
+
+GType
+ooo_fixed_get_type()
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo tinfo =
+ {
+ sizeof (GtkFixedClass),
+ nullptr, /* base init */
+ nullptr, /* base finalize */
+ reinterpret_cast<GClassInitFunc>(ooo_fixed_class_init), /* class init */
+ nullptr, /* class finalize */
+ nullptr, /* class data */
+ sizeof (GtkFixed), /* instance size */
+ 0, /* nb preallocs */
+ nullptr, /* instance init */
+ nullptr /* value table */
+ };
+
+ type = g_type_register_static( GTK_TYPE_FIXED, "OOoFixed",
+ &tinfo, GTypeFlags(0));
+ }
+
+ return type;
+}
+
+void GtkSalFrame::updateScreenNumber()
+{
+ int nScreen = 0;
+ GdkScreen *pScreen = gtk_widget_get_screen( m_pWindow );
+ if( pScreen )
+ nScreen = getDisplay()->getSystem()->getScreenMonitorIdx( pScreen, maGeometry.nX, maGeometry.nY );
+ maGeometry.nDisplayScreenNumber = nScreen;
+}
+
+GtkWidget *GtkSalFrame::getMouseEventWidget() const
+{
+ return GTK_WIDGET(m_pEventBox);
+}
+
+static void damaged(void *handle,
+ sal_Int32 nExtentsX, sal_Int32 nExtentsY,
+ sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(handle);
+ pThis->damaged(nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight);
+}
+
+void GtkSalFrame::InitCommon()
+{
+ m_pSurface = nullptr;
+ m_nGrabLevel = 0;
+ m_bSalObjectSetPosSize = false;
+
+ m_aDamageHandler.handle = this;
+ m_aDamageHandler.damaged = ::damaged;
+
+ m_aSmoothScrollIdle.SetInvokeHandler(LINK(this, GtkSalFrame, AsyncScroll));
+
+ m_pTopLevelGrid = GTK_GRID(gtk_grid_new());
+ gtk_container_add(GTK_CONTAINER(m_pWindow), GTK_WIDGET(m_pTopLevelGrid));
+
+ m_pEventBox = GTK_EVENT_BOX(gtk_event_box_new());
+ gtk_widget_add_events( GTK_WIDGET(m_pEventBox),
+ GDK_ALL_EVENTS_MASK );
+ gtk_widget_set_vexpand(GTK_WIDGET(m_pEventBox), true);
+ gtk_widget_set_hexpand(GTK_WIDGET(m_pEventBox), true);
+ gtk_grid_attach(m_pTopLevelGrid, GTK_WIDGET(m_pEventBox), 0, 0, 1, 1);
+
+ // add the fixed container child,
+ // fixed is needed since we have to position plugin windows
+ m_pFixedContainer = GTK_FIXED(g_object_new( ooo_fixed_get_type(), nullptr ));
+ gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), true);
+ gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), 1, 1);
+ gtk_container_add( GTK_CONTAINER(m_pEventBox), GTK_WIDGET(m_pFixedContainer) );
+
+ GtkWidget *pEventWidget = getMouseEventWidget();
+
+ gtk_widget_set_app_paintable(GTK_WIDGET(m_pFixedContainer), true);
+ gtk_widget_set_redraw_on_allocate(GTK_WIDGET(m_pFixedContainer), false);
+
+ // connect signals
+ // 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 );
+ gtk_widget_set_has_tooltip(pEventWidget, true);
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "query-tooltip", G_CALLBACK(signalTooltipQuery), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-press-event", G_CALLBACK(signalButton), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "motion-notify-event", G_CALLBACK(signalMotion), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-release-event", G_CALLBACK(signalButton), this ));
+ 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 ));
+
+
+ //Drop Target Stuff
+ gtk_drag_dest_set(GTK_WIDGET(pEventWidget), GtkDestDefaults(0), nullptr, 0, GdkDragAction(0));
+ gtk_drag_dest_set_track_motion(GTK_WIDGET(pEventWidget), true);
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-motion", G_CALLBACK(signalDragMotion), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-drop", G_CALLBACK(signalDragDrop), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-received", G_CALLBACK(signalDragDropReceived), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-leave", G_CALLBACK(signalDragLeave), this ));
+
+ //Drag Source Stuff
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-end", G_CALLBACK(signalDragEnd), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-failed", G_CALLBACK(signalDragFailed), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-delete", G_CALLBACK(signalDragDelete), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-get", G_CALLBACK(signalDragDataGet), this ));
+ m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "scroll-event", G_CALLBACK(signalScroll), this ));
+
+ g_signal_connect( G_OBJECT(m_pFixedContainer), "draw", G_CALLBACK(signalDraw), this );
+ g_signal_connect( G_OBJECT(m_pFixedContainer), "realize", G_CALLBACK(signalRealize), this );
+ g_signal_connect( G_OBJECT(m_pFixedContainer), "size-allocate", G_CALLBACK(sizeAllocated), this );
+
+ GtkGesture *pSwipe = gtk_gesture_swipe_new(pEventWidget);
+ g_signal_connect(pSwipe, "swipe", G_CALLBACK(gestureSwipe), this);
+ gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pSwipe), GTK_PHASE_TARGET);
+ g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pSwipe);
+
+ GtkGesture *pLongPress = gtk_gesture_long_press_new(pEventWidget);
+ g_signal_connect(pLongPress, "pressed", G_CALLBACK(gestureLongPress), this);
+ gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pLongPress), GTK_PHASE_TARGET);
+ g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pLongPress);
+
+ 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
+ g_signal_connect( G_OBJECT(m_pWindow), "set-focus", G_CALLBACK(signalSetFocus), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "map-event", G_CALLBACK(signalMap), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "unmap-event", G_CALLBACK(signalUnmap), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "configure-event", G_CALLBACK(signalConfigure), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "key-press-event", G_CALLBACK(signalKey), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "key-release-event", G_CALLBACK(signalKey), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "delete-event", G_CALLBACK(signalDelete), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "window-state-event", G_CALLBACK(signalWindowState), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "visibility-notify-event", G_CALLBACK(signalVisibility), this );
+ g_signal_connect( G_OBJECT(m_pWindow), "destroy", G_CALLBACK(signalDestroy), this );
+
+ // init members
+ m_pCurrentCursor = nullptr;
+ m_nKeyModifiers = ModKeyFlags::NONE;
+ m_bFullscreen = false;
+ m_bSpanMonitorsWhenFullscreen = false;
+ m_nState = GDK_WINDOW_STATE_WITHDRAWN;
+ 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;
+
+ gtk_widget_add_events( m_pWindow,
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
+ GDK_VISIBILITY_NOTIFY_MASK | GDK_SCROLL_MASK
+ );
+
+ // show the widgets
+ gtk_widget_show_all(GTK_WIDGET(m_pTopLevelGrid));
+
+ // realize the window, we need an XWindow id
+ gtk_widget_realize( m_pWindow );
+
+ //system data
+ m_aSystemData.aWindow = GetNativeWindowHandle(m_pWindow);
+ m_aSystemData.aShellWindow = reinterpret_cast<sal_IntPtr>(this);
+ m_aSystemData.pSalFrame = this;
+ m_aSystemData.pWidget = m_pWindow;
+ m_aSystemData.nScreen = m_nXScreen.getXScreen();
+ m_aSystemData.toolkit = SystemEnvData::Toolkit::Gtk3;
+
+#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;
+ 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
+#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::Init( SalFrame* pParent, SalFrameStyleFlags nStyle )
+{
+ if( nStyle & SalFrameStyleFlags::DEFAULT ) // ensure default style
+ {
+ nStyle |= SalFrameStyleFlags::MOVEABLE | SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::CLOSEABLE;
+ nStyle &= ~SalFrameStyleFlags::FLOAT;
+ }
+
+ m_pParent = static_cast<GtkSalFrame*>(pParent);
+ m_pForeignParent = nullptr;
+ m_aForeignParentWindow = None;
+ m_pForeignTopLevel = nullptr;
+ m_aForeignTopLevelWindow = None;
+ m_nStyle = nStyle;
+
+ GtkWindowType eWinType = ( (nStyle & SalFrameStyleFlags::FLOAT) &&
+ ! (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION)
+ )
+ ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL;
+
+ if( nStyle & SalFrameStyleFlags::SYSTEMCHILD )
+ {
+ m_pWindow = gtk_event_box_new();
+ if( m_pParent )
+ {
+ // insert into container
+ gtk_fixed_put( m_pParent->getFixedContainer(),
+ m_pWindow, 0, 0 );
+ }
+ }
+ else
+ {
+ m_pWindow = gtk_window_new(eWinType);
+
+ // 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);
+ }
+
+ 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 = gtk_widget_get_toplevel(m_pParent->m_pWindow);
+ if (!isChild())
+ gtk_window_set_screen(GTK_WINDOW(m_pWindow), gtk_widget_get_screen(pTopLevel));
+
+ 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 )
+ {
+ GdkWindowTypeHint eType = GDK_WINDOW_TYPE_HINT_NORMAL;
+ if( (nStyle & SalFrameStyleFlags::DIALOG) && m_pParent != nullptr )
+ eType = GDK_WINDOW_TYPE_HINT_DIALOG;
+ if( nStyle & SalFrameStyleFlags::INTRO )
+ {
+ gtk_window_set_role( GTK_WINDOW(m_pWindow), "splashscreen" );
+ eType = GDK_WINDOW_TYPE_HINT_SPLASHSCREEN;
+ }
+ else if( nStyle & SalFrameStyleFlags::TOOLWINDOW )
+ {
+ eType = GDK_WINDOW_TYPE_HINT_DIALOG;
+ gtk_window_set_skip_taskbar_hint( GTK_WINDOW(m_pWindow), true );
+ }
+ else if( nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
+ {
+ eType = GDK_WINDOW_TYPE_HINT_TOOLBAR;
+ gtk_window_set_focus_on_map(GTK_WINDOW(m_pWindow), false);
+ gtk_window_set_decorated(GTK_WINDOW(m_pWindow), false);
+ }
+ gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), eType );
+ gtk_window_set_gravity( GTK_WINDOW(m_pWindow), GDK_GRAVITY_STATIC );
+ gtk_window_set_resizable( GTK_WINDOW(m_pWindow), bool(nStyle & SalFrameStyleFlags::SIZEABLE) );
+
+#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::getSystemUILanguage());
+ 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
+ }
+ else if( nStyle & SalFrameStyleFlags::FLOAT )
+ gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), GDK_WINDOW_TYPE_HINT_POPUP_MENU );
+
+ InitCommon();
+
+ if( eWinType == GTK_WINDOW_TOPLEVEL )
+ {
+ // Enable DBus native menu if available.
+ ensure_dbus_setup( this );
+
+ }
+}
+
+GdkNativeWindow GtkSalFrame::findTopLevelSystemWindow( GdkNativeWindow )
+{
+ //FIXME: no findToplevelSystemWindow
+ return 0;
+}
+
+void GtkSalFrame::Init( SystemParentData* pSysData )
+{
+ m_pParent = nullptr;
+ m_aForeignParentWindow = pSysData->aWindow;
+ m_pForeignParent = nullptr;
+ m_aForeignTopLevelWindow = findTopLevelSystemWindow(pSysData->aWindow);
+ m_pForeignTopLevel = gdk_window_foreign_new_for_display( getGdkDisplay(), m_aForeignTopLevelWindow );
+ gdk_window_set_events( m_pForeignTopLevel, GDK_STRUCTURE_MASK );
+
+ if( pSysData->nSize > sizeof(pSysData->nSize)+sizeof(pSysData->aWindow) && pSysData->bXEmbedSupport )
+ {
+ m_pWindow = gtk_plug_new_for_display( getGdkDisplay(), pSysData->aWindow );
+ 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 );
+ }
+ m_nStyle = SalFrameStyleFlags::PLUG;
+ InitCommon();
+
+ m_pForeignParent = gdk_window_foreign_new_for_display( getGdkDisplay(), m_aForeignParentWindow );
+ gdk_window_set_events( m_pForeignParent, GDK_STRUCTURE_MASK );
+
+ //FIXME: Handling embedded windows, is going to be fun ...
+}
+
+void GtkSalFrame::SetExtendedFrameStyle(SalExtStyle)
+{
+}
+
+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 (m_pHeaderBar)
+ gtk_header_bar_set_title(m_pHeaderBar, sTitle.getStr());
+ }
+}
+
+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()))
+ {
+ static auto set_application_id = reinterpret_cast<void (*) (GdkWindow*, const char*)>(
+ dlsym(nullptr, "gdk_wayland_window_set_application_id"));
+ if (set_application_id)
+ {
+ GdkWindow* gdkWindow = gtk_widget_get_window(m_pWindow);
+ set_application_id(gdkWindow, appicon);
+
+ // 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 (m_pParent)
+ gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER_ON_PARENT);
+ else
+ gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER);
+}
+
+Size GtkSalFrame::calcDefaultSize()
+{
+ 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 )
+ {
+ 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)
+ /*
+ 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();
+ m_pParent->grabPointer(false, true, false);
+ }
+ }
+ gtk_widget_hide( m_pWindow );
+ if( m_pIMHandler )
+ m_pIMHandler->focusChanged( false );
+ }
+ }
+}
+
+void GtkSalFrame::setMinMaxSize()
+{
+ /* #i34504# metacity (and possibly others) do not treat
+ * _NET_WM_STATE_FULLSCREEN and max_width/height independently;
+ * whether they should is undefined. So don't set the max size hint
+ * for a full screen window.
+ */
+ if( m_pWindow && ! isChild() )
+ {
+ GdkGeometry aGeo;
+ int aHints = 0;
+ if( m_nStyle & SalFrameStyleFlags::SIZEABLE )
+ {
+ if( m_aMinSize.Width() && m_aMinSize.Height() && ! m_bFullscreen )
+ {
+ aGeo.min_width = m_aMinSize.Width();
+ aGeo.min_height = m_aMinSize.Height();
+ aHints |= GDK_HINT_MIN_SIZE;
+ }
+ if( m_aMaxSize.Width() && m_aMaxSize.Height() && ! m_bFullscreen )
+ {
+ aGeo.max_width = m_aMaxSize.Width();
+ aGeo.max_height = m_aMaxSize.Height();
+ aHints |= GDK_HINT_MAX_SIZE;
+ }
+ }
+ else
+ {
+ if (!m_bFullscreen && m_nWidthRequest && m_nHeightRequest)
+ {
+ aGeo.min_width = m_nWidthRequest;
+ aGeo.min_height = m_nHeightRequest;
+ aHints |= GDK_HINT_MIN_SIZE;
+
+ aGeo.max_width = m_nWidthRequest;
+ aGeo.max_height = m_nHeightRequest;
+ aHints |= GDK_HINT_MAX_SIZE;
+ }
+ }
+
+ if( m_bFullscreen && m_aMaxSize.Width() && m_aMaxSize.Height() )
+ {
+ aGeo.max_width = m_aMaxSize.Width();
+ aGeo.max_height = m_aMaxSize.Height();
+ aHints |= GDK_HINT_MAX_SIZE;
+ }
+ if( aHints )
+ {
+ gtk_window_set_geometry_hints( GTK_WINDOW(m_pWindow),
+ nullptr,
+ &aGeo,
+ GdkWindowHints( aHints ) );
+ }
+ }
+}
+
+void GtkSalFrame::SetMaxClientSize( long nWidth, long nHeight )
+{
+ if( ! isChild() )
+ {
+ m_aMaxSize = Size( nWidth, nHeight );
+ setMinMaxSize();
+ }
+}
+void GtkSalFrame::SetMinClientSize( long nWidth, long nHeight )
+{
+ if( ! isChild() )
+ {
+ m_aMinSize = Size( nWidth, nHeight );
+ if( m_pWindow )
+ {
+ widget_set_size_request(nWidth, nHeight);
+ setMinMaxSize();
+ }
+ }
+}
+
+void GtkSalFrame::AllocateFrame()
+{
+ basegfx::B2IVector aFrameSize( maGeometry.nWidth, maGeometry.nHeight );
+ if (!m_pSurface || m_aFrameSize.getX() != aFrameSize.getX() ||
+ m_aFrameSize.getY() != aFrameSize.getY() )
+ {
+ if( aFrameSize.getX() == 0 )
+ aFrameSize.setX( 1 );
+ if( aFrameSize.getY() == 0 )
+ aFrameSize.setY( 1 );
+
+ if (m_pSurface)
+ cairo_surface_destroy(m_pSurface);
+
+ m_pSurface = gdk_window_create_similar_surface(gtk_widget_get_window(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( long nX, long nY, long nWidth, long nHeight, sal_uInt16 nFlags )
+{
+ if( !m_pWindow || isChild( true, false ) )
+ return;
+
+ if( (nFlags & ( SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT )) &&
+ (nWidth > 0 && nHeight > 0 ) // sometimes stupid things happen
+ )
+ {
+ m_bDefaultSize = false;
+
+ maGeometry.nWidth = nWidth;
+ maGeometry.nHeight = nHeight;
+
+ if( isChild( false ) )
+ widget_set_size_request(nWidth, nHeight);
+ else if( ! ( m_nState & GDK_WINDOW_STATE_MAXIMIZED ) )
+ window_resize(nWidth, nHeight);
+
+ setMinMaxSize();
+ }
+ else if( m_bDefaultSize )
+ SetDefaultSize();
+
+ m_bDefaultSize = false;
+
+ if( nFlags & ( SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ) )
+ {
+ if( m_pParent )
+ {
+ if( AllSettings::GetLayoutRTL() )
+ nX = m_pParent->maGeometry.nWidth-m_nWidthRequest-1-nX;
+ nX += m_pParent->maGeometry.nX;
+ nY += m_pParent->maGeometry.nY;
+ }
+
+ 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( long& rWidth, long& rHeight )
+{
+ if( m_pWindow && !(m_nState & GDK_WINDOW_STATE_ICONIFIED) )
+ {
+ rWidth = maGeometry.nWidth;
+ rHeight = maGeometry.nHeight;
+ }
+ else
+ rWidth = rHeight = 0;
+}
+
+void GtkSalFrame::GetWorkArea( tools::Rectangle& rRect )
+{
+ 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)
+ {
+ GdkRectangle aRect;
+ 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;
+}
+
+SalFrame* GtkSalFrame::GetParent() const
+{
+ return m_pParent;
+}
+
+void GtkSalFrame::SetWindowState( const SalFrameState* pState )
+{
+ if( ! m_pWindow || ! pState || isChild( true, false ) )
+ return;
+
+ const WindowStateMask nMaxGeometryMask =
+ WindowStateMask::X | WindowStateMask::Y |
+ WindowStateMask::Width | WindowStateMask::Height |
+ WindowStateMask::MaximizedX | WindowStateMask::MaximizedY |
+ WindowStateMask::MaximizedWidth | WindowStateMask::MaximizedHeight;
+
+ if( (pState->mnMask & WindowStateMask::State) &&
+ ! ( m_nState & GDK_WINDOW_STATE_MAXIMIZED ) &&
+ (pState->mnState & WindowStateState::Maximized) &&
+ (pState->mnMask & nMaxGeometryMask) == nMaxGeometryMask )
+ {
+ resizeWindow( pState->mnWidth, pState->mnHeight );
+ moveWindow( pState->mnX, pState->mnY );
+ m_bDefaultPos = m_bDefaultSize = false;
+
+ updateScreenNumber();
+
+ m_nState = GdkWindowState( m_nState | GDK_WINDOW_STATE_MAXIMIZED );
+ m_aRestorePosSize = 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;
+ long nX = pState->mnX - (m_pParent ? m_pParent->maGeometry.nX : 0);
+ long nY = pState->mnY - (m_pParent ? m_pParent->maGeometry.nY : 0);
+ if( pState->mnMask & WindowStateMask::X )
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
+ else
+ nX = maGeometry.nX - (m_pParent ? m_pParent->maGeometry.nX : 0);
+ if( pState->mnMask & WindowStateMask::Y )
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
+ else
+ nY = maGeometry.nY - (m_pParent ? m_pParent->maGeometry.nY : 0);
+ if( pState->mnMask & WindowStateMask::Width )
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
+ if( pState->mnMask & WindowStateMask::Height )
+ nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
+ SetPosSize( nX, nY, pState->mnWidth, pState->mnHeight, nPosSizeFlags );
+ }
+ if( pState->mnMask & WindowStateMask::State && ! isChild() )
+ {
+ if( pState->mnState & WindowStateState::Maximized )
+ gtk_window_maximize( GTK_WINDOW(m_pWindow) );
+ else
+ gtk_window_unmaximize( GTK_WINDOW(m_pWindow) );
+ /* #i42379# there is no rollup state in GDK; and rolled up windows are
+ * (probably depending on the WM) reported as iconified. If we iconify a
+ * window here that was e.g. a dialog, then it will be unmapped but still
+ * not be displayed in the task list, so it's an iconified window that
+ * the user cannot get out of this state. So do not set the iconified state
+ * on windows with a parent (that is transient frames) since these tend
+ * to not be represented in an icon task list.
+ */
+ if( (pState->mnState & WindowStateState::Minimized)
+ && ! m_pParent )
+ gtk_window_iconify( GTK_WINDOW(m_pWindow) );
+ else
+ gtk_window_deiconify( GTK_WINDOW(m_pWindow) );
+ }
+ TriggerPaintEvent();
+}
+
+namespace
+{
+ void GetPosAndSize(GtkWindow *pWindow, long& rX, long &rY, long &rWidth, long &rHeight)
+ {
+ gint root_x, root_y;
+ gtk_window_get_position(GTK_WINDOW(pWindow), &root_x, &root_y);
+ rX = root_x;
+ rY = root_y;
+ gint width, height;
+ gtk_window_get_size(GTK_WINDOW(pWindow), &width, &height);
+ rWidth = width;
+ rHeight = height;
+ }
+
+ tools::Rectangle GetPosAndSize(GtkWindow *pWindow)
+ {
+ 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_WINDOW_STATE_ICONIFIED )
+ pState->mnState |= WindowStateState::Minimized;
+ if( m_nState & GDK_WINDOW_STATE_MAXIMIZED )
+ {
+ pState->mnState |= WindowStateState::Maximized;
+ pState->mnX = m_aRestorePosSize.Left();
+ pState->mnY = m_aRestorePosSize.Top();
+ pState->mnWidth = m_aRestorePosSize.GetWidth();
+ pState->mnHeight = m_aRestorePosSize.GetHeight();
+ GetPosAndSize(GTK_WINDOW(m_pWindow), pState->mnMaximizedX, pState->mnMaximizedY,
+ pState->mnMaximizedWidth, pState->mnMaximizedHeight);
+ pState->mnMask |= WindowStateMask::MaximizedX |
+ WindowStateMask::MaximizedY |
+ WindowStateMask::MaximizedWidth |
+ WindowStateMask::MaximizedHeight;
+ }
+ else
+ {
+ GetPosAndSize(GTK_WINDOW(m_pWindow), pState->mnX, pState->mnY,
+ pState->mnWidth, pState->mnHeight);
+ }
+ pState->mnMask |= WindowStateMask::X |
+ WindowStateMask::Y |
+ WindowStateMask::Width |
+ WindowStateMask::Height;
+
+ return true;
+}
+
+void GtkSalFrame::SetScreen( unsigned int nNewScreen, SetType eType, tools::Rectangle const *pSize )
+{
+ if( !m_pWindow )
+ return;
+
+ if (maGeometry.nDisplayScreenNumber == nNewScreen && eType == SetType::RetainSize)
+ return;
+
+ int nX = maGeometry.nX, nY = maGeometry.nY,
+ nWidth = maGeometry.nWidth, nHeight = maGeometry.nHeight;
+ GdkScreen *pScreen = nullptr;
+ GdkRectangle aNewMonitor;
+
+ bool bSpanAllScreens = nNewScreen == static_cast<unsigned int>(-1);
+ m_bSpanMonitorsWhenFullscreen = bSpanAllScreens && getDisplay()->getSystem()->GetDisplayScreenCount() > 1;
+ gint nMonitor = -1;
+ if (m_bSpanMonitorsWhenFullscreen) //span all screens
+ {
+ pScreen = gtk_widget_get_screen( m_pWindow );
+ aNewMonitor.x = 0;
+ aNewMonitor.y = 0;
+ aNewMonitor.width = gdk_screen_get_width(pScreen);
+ aNewMonitor.height = gdk_screen_get_height(pScreen);
+ }
+ else
+ {
+ bool bSameMonitor = false;
+
+ if (!bSpanAllScreens)
+ {
+ pScreen = getDisplay()->getSystem()->getScreenMonitorFromIdx( nNewScreen, nMonitor );
+ if (!pScreen)
+ {
+ g_warning ("Attempt to move GtkSalFrame to invalid screen %d => "
+ "fallback to current\n", nNewScreen);
+ }
+ }
+
+ if (!pScreen)
+ {
+ pScreen = gtk_widget_get_screen( m_pWindow );
+ bSameMonitor = true;
+ }
+
+ // Heavy lifting, need to move screen ...
+ if( pScreen != gtk_widget_get_screen( m_pWindow ))
+ gtk_window_set_screen( GTK_WINDOW( m_pWindow ), pScreen );
+
+ gint nOldMonitor = gdk_screen_get_monitor_at_window(
+ pScreen, gtk_widget_get_window( m_pWindow ) );
+ if (bSameMonitor)
+ nMonitor = nOldMonitor;
+
+ #if OSL_DEBUG_LEVEL > 1
+ if( nMonitor == nOldMonitor )
+ g_warning( "An apparently pointless SetScreen - should we elide it ?" );
+ #endif
+
+ GdkRectangle aOldMonitor;
+ gdk_screen_get_monitor_geometry( pScreen, nOldMonitor, &aOldMonitor );
+ gdk_screen_get_monitor_geometry( pScreen, nMonitor, &aNewMonitor );
+
+ nX = aNewMonitor.x + nX - aOldMonitor.x;
+ nY = aNewMonitor.y + nY - aOldMonitor.y;
+ }
+
+ bool bResize = false;
+ bool bVisible = 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);
+
+ gdk_window_set_fullscreen_mode( gtk_widget_get_window(m_pWindow), m_bSpanMonitorsWhenFullscreen
+ ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR );
+
+ GtkWidget* pMenuBarContainerWidget = m_pSalMenu ? m_pSalMenu->GetMenuBarContainerWidget() : nullptr;
+ if( eType == SetType::Fullscreen )
+ {
+ if (pMenuBarContainerWidget)
+ gtk_widget_hide(pMenuBarContainerWidget);
+ if (m_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 );
+}
+
+void GtkSalFrame::SetScreenNumber( unsigned int nNewScreen )
+{
+ SetScreen( nNewScreen, SetType::RetainSize );
+}
+
+void GtkSalFrame::updateWMClass()
+{
+ OString aResClass = OUStringToOString(m_sWMClass, RTL_TEXTENCODING_ASCII_US);
+ const char *pResClass = !aResClass.isEmpty() ? aResClass.getStr() :
+ SalGenericSystem::getFrameClassName();
+ Display *display;
+
+ if (!getDisplay()->IsX11Display())
+ return;
+
+ display = GDK_DISPLAY_XDISPLAY(getGdkDisplay());
+
+ if( gtk_widget_get_realized( m_pWindow ) )
+ {
+ XClassHint* pClass = XAllocClassHint();
+ OString aResName = SalGenericSystem::getFrameResName();
+ pClass->res_name = const_cast<char*>(aResName.getStr());
+ pClass->res_class = const_cast<char*>(pResClass);
+ XSetClassHint( display,
+ widget_get_xid(m_pWindow),
+ pClass );
+ XFree( pClass );
+ }
+}
+
+void GtkSalFrame::SetApplicationID( const OUString &rWMClass )
+{
+ if( rWMClass != m_sWMClass && ! isChild() )
+ {
+ m_sWMClass = rWMClass;
+ updateWMClass();
+
+ for (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;
+ if( getDisplay()->IsX11Display() )
+ {
+ aWindow = widget_get_xid(m_pWindow);
+ aDisplay = GDK_DISPLAY_XDISPLAY( getGdkDisplay() );
+ }
+
+ m_ScreenSaverInhibitor.inhibit( bStart,
+ "presentation",
+ getDisplay()->IsX11Display(),
+ aWindow,
+ aDisplay );
+}
+
+void GtkSalFrame::SetAlwaysOnTop( bool bOnTop )
+{
+ if( m_pWindow )
+ gtk_window_set_keep_above( GTK_WINDOW( m_pWindow ), bOnTop );
+}
+
+static guint32 nLastUserInputTime = GDK_CURRENT_TIME;
+
+guint32 GtkSalFrame::GetLastInputEventTime()
+{
+ return nLastUserInputTime;
+}
+
+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 )
+ {
+ if( isChild( false ) )
+ GrabFocus();
+ else if( gtk_widget_get_mapped( m_pWindow ) )
+ {
+ if (!(nFlags & SalFrameToTop::GrabFocusOnly))
+ gtk_window_present_with_time(GTK_WINDOW(m_pWindow), GetLastInputEventTime());
+ else
+ gdk_window_focus(gtk_widget_get_window(m_pWindow), GetLastInputEventTime());
+ GrabFocus();
+ }
+ else
+ {
+ if( nFlags & SalFrameToTop::RestoreWhenMin )
+ gtk_window_present( GTK_WINDOW(m_pWindow) );
+ }
+ }
+}
+
+void GtkSalFrame::SetPointer( PointerStyle ePointerStyle )
+{
+ if( m_pWindow && ePointerStyle != m_ePointerStyle )
+ {
+ m_ePointerStyle = ePointerStyle;
+ GdkCursor *pCursor = getDisplay()->getCursor( ePointerStyle );
+ gdk_window_set_cursor( gtk_widget_get_window(m_pWindow), pCursor );
+ m_pCurrentCursor = pCursor;
+
+ // #i80791# use grabPointer the same way as CaptureMouse, respective float grab
+ if( getDisplay()->MouseCaptured( this ) )
+ grabPointer( true, false, false );
+ else if( m_nFloats > 0 )
+ grabPointer( true, false, true );
+ }
+}
+
+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(3, 20, 0)
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ 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, gtk_widget_get_window(getMouseEventWidget()), eCapability,
+ bOwnerEvents, nullptr, nullptr, nullptr, nullptr);
+ }
+ else
+ {
+ gdk_seat_ungrab(pSeat);
+ }
+ return;
+ }
+#endif
+
+ //else older gtk3
+ GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(getGdkDisplay());
+ GdkDevice* pPointer = gdk_device_manager_get_client_pointer(pDeviceManager);
+ GdkDevice* pKeyboard = bKeyboardAlso ? gdk_device_get_associated_device(pPointer) : nullptr;
+ GdkWindow* pWindow = gtk_widget_get_window(getMouseEventWidget());
+ guint32 nCurrentTime = gtk_get_current_event_time();
+ if (bGrab)
+ {
+ gdk_device_grab(pPointer, pWindow, GDK_OWNERSHIP_NONE,
+ bOwnerEvents, GDK_ALL_EVENTS_MASK, m_pCurrentCursor, nCurrentTime);
+ if (pKeyboard)
+ gdk_device_grab(pKeyboard, pWindow, GDK_OWNERSHIP_NONE, true, GDK_ALL_EVENTS_MASK, nullptr, nCurrentTime);
+ }
+ else
+ {
+ gdk_device_ungrab(pPointer, nCurrentTime);
+ if (pKeyboard)
+ gdk_device_ungrab(pKeyboard, nCurrentTime);
+ }
+}
+
+void GtkSalFrame::CaptureMouse( bool bCapture )
+{
+ getDisplay()->CaptureMouse( bCapture ? this : nullptr );
+}
+
+void GtkSalFrame::SetPointerPos( long nX, long nY )
+{
+ GtkSalFrame* pFrame = this;
+ while( pFrame && pFrame->isChild( false ) )
+ pFrame = pFrame->m_pParent;
+ if( ! pFrame )
+ return;
+
+ GdkScreen *pScreen = gtk_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( gtk_widget_get_window(pFrame->m_pWindow) , &x, &y, &mask );
+}
+
+void GtkSalFrame::Flush()
+{
+ gdk_display_flush( getGdkDisplay() );
+}
+
+void GtkSalFrame::KeyCodeToGdkKey(const vcl::KeyCode& rKeyCode,
+ guint* pGdkKeyCode, GdkModifierType *pGdkModifiers)
+{
+ if ( pGdkKeyCode == nullptr || pGdkModifiers == nullptr )
+ return;
+
+ // Get GDK key modifiers
+ GdkModifierType nModifiers = GdkModifierType(0);
+
+ if ( rKeyCode.IsShift() )
+ nModifiers = 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_MOD1_MASK );
+
+ *pGdkModifiers = nModifiers;
+
+ // Get GDK keycode.
+ guint nKeyCode = 0;
+
+ guint nCode = rKeyCode.GetCode();
+
+ if ( nCode >= KEY_0 && nCode <= KEY_9 )
+ nKeyCode = ( nCode - KEY_0 ) + GDK_KEY_0;
+ else if ( nCode >= KEY_A && nCode <= KEY_Z )
+ nKeyCode = ( nCode - KEY_A ) + GDK_KEY_A;
+ else if ( nCode >= KEY_F1 && nCode <= KEY_F26 )
+ nKeyCode = ( nCode - KEY_F1 ) + GDK_KEY_F1;
+ else
+ {
+ switch (nCode)
+ {
+ case KEY_DOWN: nKeyCode = GDK_KEY_Down; break;
+ case KEY_UP: nKeyCode = GDK_KEY_Up; break;
+ case KEY_LEFT: nKeyCode = GDK_KEY_Left; break;
+ case KEY_RIGHT: nKeyCode = GDK_KEY_Right; break;
+ case KEY_HOME: nKeyCode = GDK_KEY_Home; break;
+ case KEY_END: nKeyCode = GDK_KEY_End; break;
+ case KEY_PAGEUP: nKeyCode = GDK_KEY_Page_Up; break;
+ case KEY_PAGEDOWN: nKeyCode = GDK_KEY_Page_Down; break;
+ case KEY_RETURN: nKeyCode = GDK_KEY_Return; break;
+ case KEY_ESCAPE: nKeyCode = GDK_KEY_Escape; break;
+ case KEY_TAB: nKeyCode = GDK_KEY_Tab; break;
+ case KEY_BACKSPACE: nKeyCode = GDK_KEY_BackSpace; break;
+ case KEY_SPACE: nKeyCode = GDK_KEY_space; break;
+ case KEY_INSERT: nKeyCode = GDK_KEY_Insert; break;
+ case KEY_DELETE: nKeyCode = GDK_KEY_Delete; break;
+ case KEY_ADD: nKeyCode = GDK_KEY_plus; break;
+ case KEY_SUBTRACT: nKeyCode = GDK_KEY_minus; break;
+ case KEY_MULTIPLY: nKeyCode = GDK_KEY_asterisk; break;
+ case KEY_DIVIDE: nKeyCode = GDK_KEY_slash; break;
+ case KEY_POINT: nKeyCode = GDK_KEY_period; break;
+ case KEY_COMMA: nKeyCode = GDK_KEY_comma; break;
+ case KEY_LESS: nKeyCode = GDK_KEY_less; break;
+ case KEY_GREATER: nKeyCode = GDK_KEY_greater; break;
+ case KEY_EQUAL: nKeyCode = GDK_KEY_equal; break;
+ case KEY_FIND: nKeyCode = GDK_KEY_Find; break;
+ case KEY_CONTEXTMENU: nKeyCode = GDK_KEY_Menu; break;
+ case KEY_HELP: nKeyCode = GDK_KEY_Help; break;
+ case KEY_UNDO: nKeyCode = GDK_KEY_Undo; break;
+ case KEY_REPEAT: nKeyCode = GDK_KEY_Redo; break;
+ case KEY_DECIMAL: nKeyCode = GDK_KEY_KP_Decimal; break;
+ case KEY_TILDE: nKeyCode = GDK_KEY_asciitilde; break;
+ case KEY_QUOTELEFT: nKeyCode = GDK_KEY_quoteleft; break;
+ case KEY_BRACKETLEFT: nKeyCode = GDK_KEY_bracketleft; break;
+ case KEY_BRACKETRIGHT: nKeyCode = GDK_KEY_bracketright; break;
+ case KEY_SEMICOLON: nKeyCode = GDK_KEY_semicolon; break;
+ case KEY_QUOTERIGHT: nKeyCode = GDK_KEY_quoteright; break;
+
+ // Special cases
+ case KEY_COPY: nKeyCode = GDK_KEY_Copy; break;
+ case KEY_CUT: nKeyCode = GDK_KEY_Cut; break;
+ case KEY_PASTE: nKeyCode = GDK_KEY_Paste; break;
+ case KEY_OPEN: nKeyCode = GDK_KEY_Open; break;
+ }
+ }
+
+ *pGdkKeyCode = nKeyCode;
+}
+
+OUString GtkSalFrame::GetKeyName( sal_uInt16 nKeyCode )
+{
+ guint nGtkKeyCode;
+ GdkModifierType nGtkModifiers;
+ KeyCodeToGdkKey(nKeyCode, &nGtkKeyCode, &nGtkModifiers );
+
+ gchar* pName = gtk_accelerator_get_label(nGtkKeyCode, nGtkModifiers);
+ OUString aRet(pName, rtl_str_getLength(pName), RTL_TEXTENCODING_UTF8);
+ g_free(pName);
+ return aRet;
+}
+
+GdkDisplay *GtkSalFrame::getGdkDisplay()
+{
+ return GetGtkSalData()->GetGdkDisplay();
+}
+
+GtkSalDisplay *GtkSalFrame::getDisplay()
+{
+ return GetGtkSalData()->GetGtkDisplay();
+}
+
+SalFrame::SalPointerState GtkSalFrame::GetPointerState()
+{
+ SalPointerState aState;
+ GdkScreen* pScreen;
+ gint x, y;
+ GdkModifierType aMask;
+ gdk_display_get_pointer( getGdkDisplay(), &pScreen, &x, &y, &aMask );
+ aState.maPos = Point( x - maGeometry.nX, y - maGeometry.nY );
+ aState.mnState = GetMouseModCode( aMask );
+ return aState;
+}
+
+KeyIndicatorState GtkSalFrame::GetIndicatorState()
+{
+ KeyIndicatorState nState = KeyIndicatorState::NONE;
+
+ GdkKeymap *pKeyMap = gdk_keymap_get_for_display(getGdkDisplay());
+
+ if (gdk_keymap_get_caps_lock_state(pKeyMap))
+ nState |= KeyIndicatorState::CAPSLOCK;
+ if (gdk_keymap_get_num_lock_state(pKeyMap))
+ nState |= KeyIndicatorState::NUMLOCK;
+ if (gdk_keymap_get_scroll_lock_state(pKeyMap))
+ nState |= KeyIndicatorState::SCROLLLOCK;
+
+ 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::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
+ );
+}
+
+bool GtkSalFrame::SetPluginParent( SystemParentData* )
+{
+ //FIXME: no SetPluginParent impl. for gtk3
+ return false;
+}
+
+void GtkSalFrame::ResetClipRegion()
+{
+ if( m_pWindow )
+ gdk_window_shape_combine_region( gtk_widget_get_window( m_pWindow ), nullptr, 0, 0 );
+}
+
+void GtkSalFrame::BeginSetClipRegion( sal_uInt32 )
+{
+ if( m_pRegion )
+ cairo_region_destroy( m_pRegion );
+ m_pRegion = cairo_region_create();
+}
+
+void GtkSalFrame::UnionClipRegion( long nX, long nY, long nWidth, long nHeight )
+{
+ if( m_pRegion )
+ {
+ GdkRectangle aRect;
+ aRect.x = nX;
+ aRect.y = nY;
+ aRect.width = nWidth;
+ aRect.height = nHeight;
+ cairo_region_union_rectangle( m_pRegion, &aRect );
+ }
+}
+
+void GtkSalFrame::EndSetClipRegion()
+{
+ if( m_pWindow && m_pRegion )
+ gdk_window_shape_combine_region( gtk_widget_get_window(m_pWindow), m_pRegion, 0, 0 );
+}
+
+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)
+{
+ GtkWidget *pWidget = gtk_popover_new(getMouseEventWidget());
+ OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
+ GtkWidget *pLabel = gtk_label_new(sUTF.getStr());
+ gtk_container_add(GTK_CONTAINER(pWidget), pLabel);
+
+ if (nFlags & QuickHelpFlags::Top)
+ gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_BOTTOM);
+ else if (nFlags & QuickHelpFlags::Bottom)
+ gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_TOP);
+ else if (nFlags & QuickHelpFlags::Left)
+ gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_RIGHT);
+ else if (nFlags & QuickHelpFlags::Right)
+ gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_LEFT);
+
+ set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry);
+
+ gtk_popover_set_modal(GTK_POPOVER(pWidget), false);
+
+ gtk_widget_show_all(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);
+
+ GtkWidget *pLabel = gtk_bin_get_child(GTK_BIN(pWidget));
+ OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
+ gtk_label_set_text(GTK_LABEL(pLabel), sUTF.getStr());
+
+ return true;
+}
+
+bool GtkSalFrame::HidePopover(void* nId)
+{
+ GtkWidget *pWidget = static_cast<GtkWidget*>(nId);
+ gtk_widget_destroy(pWidget);
+ return true;
+}
+
+void GtkSalFrame::addGrabLevel()
+{
+ if (m_nGrabLevel == 0)
+ gtk_grab_add(getMouseEventWidget());
+ ++m_nGrabLevel;
+}
+
+void GtkSalFrame::removeGrabLevel()
+{
+ if (m_nGrabLevel > 0)
+ {
+ --m_nGrabLevel;
+ if (m_nGrabLevel == 0)
+ gtk_grab_remove(getMouseEventWidget());
+ }
+}
+
+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);
+}
+
+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_widget_translate_coordinates(pRealEventWidget, pTargetWidget, rEventX, rEventY, &rEventX, &rEventY);
+ }
+ }
+}
+
+void GtkSalFrame::GrabFocus()
+{
+ GtkWidget* pGrabWidget;
+ if (GTK_IS_EVENT_BOX(m_pWindow))
+ pGrabWidget = GTK_WIDGET(m_pWindow);
+ else
+ pGrabWidget = GTK_WIDGET(m_pFixedContainer);
+ if (!gtk_widget_get_can_focus(pGrabWidget))
+ gtk_widget_set_can_focus(pGrabWidget, true);
+ if (!gtk_widget_has_focus(pGrabWidget))
+ gtk_widget_grab_focus(pGrabWidget);
+}
+
+gboolean GtkSalFrame::signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer frame)
+{
+ UpdateLastInputEventTime(pEvent->time);
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ GtkWidget* pEventWidget = pThis->getMouseEventWidget();
+ bool bDifferentEventWindow = pEvent->window != gtk_widget_get_window(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();
+ }
+
+ SalMouseEvent aEvent;
+ SalEvent nEventType = SalEvent::NONE;
+ switch( pEvent->type )
+ {
+ case GDK_BUTTON_PRESS:
+ nEventType = SalEvent::MouseButtonDown;
+ break;
+ case GDK_BUTTON_RELEASE:
+ nEventType = SalEvent::MouseButtonUp;
+ break;
+ default:
+ return false;
+ }
+ switch( pEvent->button )
+ {
+ case 1: aEvent.mnButton = MOUSE_LEFT; break;
+ case 2: aEvent.mnButton = MOUSE_MIDDLE; break;
+ case 3: aEvent.mnButton = MOUSE_RIGHT; break;
+ default: return false;
+ }
+
+ 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())
+ {
+ 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);
+ }
+ }
+
+ if (!aDel.isDeleted())
+ {
+ aEvent.mnTime = pEvent->time;
+ aEvent.mnX = static_cast<long>(pEvent->x_root) - pThis->maGeometry.nX;
+ aEvent.mnY = static_cast<long>(pEvent->y_root) - pThis->maGeometry.nY;
+ aEvent.mnCode = GetMouseModCode( pEvent->state );
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;
+
+ pThis->CallCallbackExc( nEventType, &aEvent );
+ }
+
+ return true;
+}
+
+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();
+}
+
+IMPL_LINK_NOARG(GtkSalFrame, AsyncScroll, Timer *, void)
+{
+ assert(!m_aPendingScrollEvents.empty());
+
+ SalWheelMouseEvent aEvent;
+
+ GdkEvent* pEvent = m_aPendingScrollEvents.back();
+
+ aEvent.mnTime = pEvent->scroll.time;
+ aEvent.mnX = static_cast<sal_uLong>(pEvent->scroll.x);
+ // --- RTL --- (mirror mouse pos)
+ if (AllSettings::GetLayoutRTL())
+ aEvent.mnX = maGeometry.nWidth - 1 - aEvent.mnX;
+ aEvent.mnY = static_cast<sal_uLong>(pEvent->scroll.y);
+ aEvent.mnCode = GetMouseModCode( 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();
+
+ // 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);
+ }
+}
+
+SalWheelMouseEvent GtkSalFrame::GetWheelEvent(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;
+}
+
+void GtkSalFrame::gestureSwipe(GtkGestureSwipe* gesture, gdouble velocity_x, gdouble velocity_y, gpointer frame)
+{
+ gdouble x, y;
+ GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
+ //I feel I want the first point of the sequence, not the last point which
+ //the docs say this gives, but for the moment assume we start and end
+ //within the same vcl window
+ if (gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y))
+ {
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ SalSwipeEvent aEvent;
+ aEvent.mnVelocityX = velocity_x;
+ aEvent.mnVelocityY = velocity_y;
+ aEvent.mnX = x;
+ aEvent.mnY = y;
+
+ pThis->CallCallbackExc(SalEvent::Swipe, &aEvent);
+ }
+}
+
+void GtkSalFrame::gestureLongPress(GtkGestureLongPress* gesture, 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))
+ {
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ SalLongPressEvent aEvent;
+ aEvent.mnX = x;
+ aEvent.mnY = y;
+
+ pThis->CallCallbackExc(SalEvent::LongPress, &aEvent);
+ }
+}
+
+gboolean GtkSalFrame::signalMotion( GtkWidget*, GdkEventMotion* pEvent, gpointer frame )
+{
+ UpdateLastInputEventTime(pEvent->time);
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ GtkWidget* pEventWidget = pThis->getMouseEventWidget();
+ bool bDifferentEventWindow = pEvent->window != gtk_widget_get_window(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 (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())
+ {
+ SalMouseEvent aEvent;
+ aEvent.mnTime = pEvent->time;
+ aEvent.mnX = static_cast<long>(pEvent->x_root) - pThis->maGeometry.nX;
+ aEvent.mnY = static_cast<long>(pEvent->y_root) - pThis->maGeometry.nY;
+ aEvent.mnCode = GetMouseModCode( pEvent->state );
+ aEvent.mnButton = 0;
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;
+
+ pThis->CallCallbackExc( SalEvent::MouseMove, &aEvent );
+ }
+
+ if (!aDel.isDeleted())
+ {
+ // ask for the next hint
+ gint x, y;
+ GdkModifierType mask;
+ gdk_window_get_pointer( gtk_widget_get_window(GTK_WIDGET(pThis->m_pWindow)), &x, &y, &mask );
+ }
+
+ return true;
+}
+
+gboolean GtkSalFrame::signalCrossing( GtkWidget*, GdkEventCrossing* pEvent, gpointer frame )
+{
+ UpdateLastInputEventTime(pEvent->time);
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ SalMouseEvent aEvent;
+ aEvent.mnTime = pEvent->time;
+ aEvent.mnX = static_cast<long>(pEvent->x_root) - pThis->maGeometry.nX;
+ aEvent.mnY = static_cast<long>(pEvent->y_root) - pThis->maGeometry.nY;
+ aEvent.mnCode = GetMouseModCode( pEvent->state );
+ aEvent.mnButton = 0;
+
+ if (AllSettings::GetLayoutRTL())
+ aEvent.mnX = pThis->maGeometry.nWidth-1-aEvent.mnX;
+
+ pThis->CallCallbackExc( (pEvent->type == GDK_ENTER_NOTIFY) ? SalEvent::MouseMove : SalEvent::MouseLeave, &aEvent );
+
+ return true;
+}
+
+cairo_t* GtkSalFrame::getCairoContext() const
+{
+ cairo_t* cr = cairo_create(m_pSurface);
+ assert(cr);
+ return cr;
+}
+
+void GtkSalFrame::damaged(sal_Int32 nExtentsX, sal_Int32 nExtentsY,
+ sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight) const
+{
+#if 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
+
+ gtk_widget_queue_draw_area(GTK_WIDGET(m_pFixedContainer),
+ nExtentsX, nExtentsY,
+ nExtentsWidth, nExtentsHeight);
+}
+
+// blit our backing cairo surface to the target cairo context
+gboolean GtkSalFrame::signalDraw(GtkWidget*, cairo_t *cr, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ cairo_set_source_surface(cr, pThis->m_pSurface, 0, 0);
+ cairo_paint(cr);
+
+ return false;
+}
+
+void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, GdkRectangle *pAllocation, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ // ignore size-allocations that occur during configuring an embedded SalObject
+ if (pThis->m_bSalObjectSetPosSize)
+ return;
+ pThis->maGeometry.nWidth = pAllocation->width;
+ pThis->maGeometry.nHeight = pAllocation->height;
+ bool bRealized = gtk_widget_get_realized(pWidget);
+ if (bRealized)
+ pThis->AllocateFrame();
+ pThis->CallCallbackExc( SalEvent::Resize, nullptr );
+ if (bRealized)
+ pThis->TriggerPaintEvent();
+}
+
+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;
+}
+
+}
+
+void GtkSalFrame::signalRealize(GtkWidget*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->AllocateFrame();
+ if (pThis->m_bSalObjectSetPosSize)
+ return;
+ pThis->TriggerPaintEvent();
+
+ 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)
+ {
+ 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->HasMirroredGraphics() && pVclParent->IsRTLEnabled())
+ {
+ swapDirection(rect_anchor);
+ swapDirection(menu_anchor);
+ }
+
+ tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pVclParent, pThis->m_aFloatRect);
+ if (gdk_window_get_window_type(gtk_widget_get_window(pThis->m_pParent->m_pWindow)) != GDK_WINDOW_TOPLEVEL)
+ aFloatRect.Move(-pThis->m_pParent->maGeometry.nX, -pThis->m_pParent->maGeometry.nY);
+
+ GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
+ static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
+
+ GdkWindow* gdkWindow = gtk_widget_get_window(pThis->m_pWindow);
+ window_move_to_rect(gdkWindow, &rect, rect_anchor, menu_anchor, static_cast<GdkAnchorHints>(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE), 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( gtk_widget_get_window(GTK_WIDGET(pThis->m_pWindow)), &aRect );
+ pThis->maGeometry.nTopDecoration = y - aRect.y;
+ pThis->maGeometry.nBottomDecoration = aRect.y + aRect.height - y - pEvent->height;
+ pThis->maGeometry.nLeftDecoration = x - aRect.x;
+ pThis->maGeometry.nRightDecoration = aRect.x + aRect.width - x - pEvent->width;
+ pThis->updateScreenNumber();
+
+ if (bMoved)
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+ if (pSVData->maNWFData.mbCanDetermineWindowPosition)
+ pThis->CallCallbackExc(SalEvent::Move, nullptr);
+ }
+
+ return false;
+}
+
+void GtkSalFrame::TriggerPaintEvent()
+{
+ //Under gtk2 we can basically paint directly into the XWindow and on
+ //additional "expose-event" events we can re-render the missing pieces
+ //
+ //Under gtk3 we have to keep our own buffer up to date and flush it into
+ //the given cairo context on "draw". So we emit a paint event on
+ //opportune resize trigger events to initially fill our backbuffer and then
+ //keep it up to date with our direct paints and tell gtk those regions
+ //have changed and then blit them into the provided cairo context when
+ //we get the "draw"
+ //
+ //The other alternative was to always paint everything on "draw", but
+ //that duplicates the amount of drawing and is hideously slow
+ SAL_INFO("vcl.gtk3", "force painting" << 0 << "," << 0 << " " << maGeometry.nWidth << "x" << maGeometry.nHeight);
+ SalPaintEvent aPaintEvt(0, 0, maGeometry.nWidth, maGeometry.nHeight, true);
+ CallCallbackExc(SalEvent::Paint, &aPaintEvt);
+ gtk_widget_queue_draw(GTK_WIDGET(m_pFixedContainer));
+}
+
+gboolean GtkSalFrame::signalFocus( GtkWidget*, GdkEventFocus* pEvent, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ SalGenericInstance *pSalInstance =
+ static_cast< SalGenericInstance* >(GetSalData()->m_pInstance);
+
+ // check if printers have changed (analogous to salframe focus handler)
+ pSalInstance->updatePrinterUpdate();
+
+ if( !pEvent->in )
+ pThis->m_nKeyModifiers = ModKeyFlags::NONE;
+
+ if( pThis->m_pIMHandler )
+ 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;
+}
+
+void GtkSalFrame::signalSetFocus(GtkWindow*, GtkWidget* pWidget, gpointer frame)
+{
+ // do not propagate focus get/lose if floats are open
+ if (m_nFloats)
+ return;
+ // change of focus between native widgets within the toplevel
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ // tdf#129634 ignore floating toolbars
+ if (pThis->m_nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION)
+ return;
+
+ // tdf#129634 interpret losing focus as focus passing explicitly to another widget
+ bool bLoseFocus = pWidget && pWidget != GTK_WIDGET(pThis->m_pFixedContainer);
+ pThis->CallCallbackExc(bLoseFocus ? SalEvent::LoseFocus : SalEvent::GetFocus, nullptr);
+ gtk_widget_set_can_focus(GTK_WIDGET(pThis->m_pFixedContainer), !bLoseFocus);
+}
+
+gboolean GtkSalFrame::signalMap(GtkWidget *, GdkEvent*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ if (pThis->m_bIconSetWhileUnmapped)
+ pThis->SetIcon(gtk_window_get_icon_name(GTK_WINDOW(pThis->m_pWindow)));
+
+ pThis->CallCallbackExc( SalEvent::Resize, nullptr );
+ pThis->TriggerPaintEvent();
+
+ return false;
+}
+
+gboolean GtkSalFrame::signalUnmap( GtkWidget*, GdkEvent*, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ pThis->CallCallbackExc( SalEvent::Resize, nullptr );
+
+ if (pThis->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(pThis->m_pWindow);
+ pThis->m_bFloatPositioned = false;
+ }
+
+ return false;
+}
+
+gboolean GtkSalFrame::signalKey(GtkWidget* pWidget, GdkEventKey* pEvent, gpointer frame)
+{
+ UpdateLastInputEventTime(pEvent->time);
+
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ 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)
+ {
+ gpointer pClass = g_type_class_ref(GTK_TYPE_WINDOW);
+ GtkWidgetClass* pWindowClass = GTK_WIDGET_CLASS(pClass);
+ // if the focus is not in our main widget, see if there is a handler
+ // for this key stroke in GtkWindow first
+ bool bHandled = pEvent->type == GDK_KEY_PRESS
+ ? pWindowClass->key_press_event(pThis->m_pWindow, pEvent)
+ : pWindowClass->key_release_event(pThis->m_pWindow, pEvent);
+ g_type_class_unref(pClass);
+ if (bHandled)
+ return true;
+ }
+ }
+
+ 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
+ {
+ 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);
+ if( ! aDel.isDeleted() )
+ pThis->m_nKeyModifiers = ModKeyFlags::NONE;
+ }
+
+ if (!bFocusInAnotherGtkWidget && !aDel.isDeleted() && pThis->m_pIMHandler)
+ pThis->m_pIMHandler->updateIMSpotLocation();
+
+ return bStopProcessingKey;
+}
+
+gboolean GtkSalFrame::signalDelete( GtkWidget*, GdkEvent*, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ pThis->CallCallbackExc( SalEvent::Close, nullptr );
+ return true;
+}
+
+void GtkSalFrame::signalStyleUpdated(GtkWidget*, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+
+ // note: settings changed for multiple frames is avoided in winproc.cxx ImplHandleSettings
+ GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::SettingsChanged );
+
+ // fire off font-changed when the system cairo font hints change
+ GtkInstance *pInstance = static_cast<GtkInstance*>(GetSalData()->m_pInstance);
+ const cairo_font_options_t* pLastCairoFontOptions = pInstance->GetLastSeenCairoFontOptions();
+ const cairo_font_options_t* pCurrentCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
+ bool bFontSettingsChanged = true;
+ if (pLastCairoFontOptions && pCurrentCairoFontOptions)
+ bFontSettingsChanged = !cairo_font_options_equal(pLastCairoFontOptions, pCurrentCairoFontOptions);
+ else if (!pLastCairoFontOptions && !pCurrentCairoFontOptions)
+ bFontSettingsChanged = false;
+ if (bFontSettingsChanged)
+ {
+ pInstance->ResetLastSeenCairoFontOptions(pCurrentCairoFontOptions);
+ GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::FontChanged );
+ }
+}
+
+gboolean GtkSalFrame::signalWindowState( GtkWidget*, GdkEvent* pEvent, gpointer frame )
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if( (pThis->m_nState & GDK_WINDOW_STATE_ICONIFIED) != (pEvent->window_state.new_window_state & GDK_WINDOW_STATE_ICONIFIED ) )
+ {
+ GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize );
+ pThis->TriggerPaintEvent();
+ }
+
+ if ((pEvent->window_state.new_window_state & GDK_WINDOW_STATE_MAXIMIZED) &&
+ !(pThis->m_nState & GDK_WINDOW_STATE_MAXIMIZED))
+ {
+ pThis->m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(pThis->m_pWindow));
+ }
+
+ if ((pEvent->window_state.new_window_state & GDK_WINDOW_STATE_WITHDRAWN) &&
+ !(pThis->m_nState & GDK_WINDOW_STATE_WITHDRAWN))
+ {
+ if (pThis->isFloatGrabWindow())
+ pThis->closePopup();
+ }
+
+ pThis->m_nState = pEvent->window_state.new_window_state;
+
+#if OSL_DEBUG_LEVEL > 1
+ SAL_INFO_IF((pEvent->window_state.changed_mask &
+ GDK_WINDOW_STATE_FULLSCREEN),
+ "vcl.gtk3", "window "
+ << pThis
+ << " "
+ << ((pEvent->window_state.new_window_state &
+ GDK_WINDOW_STATE_FULLSCREEN) ?
+ "enters" :
+ "leaves")
+ << " full screen state.");
+#endif
+
+ return false;
+}
+
+gboolean GtkSalFrame::signalVisibility( GtkWidget*, GdkEventVisibility* /*pEvent*/, gpointer /*frame*/ )
+{
+ return true;
+}
+
+namespace
+{
+ GdkDragAction VclToGdk(sal_Int8 dragOperation)
+ {
+ GdkDragAction eRet(static_cast<GdkDragAction>(0));
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
+ eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_COPY);
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
+ eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_MOVE);
+ if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
+ eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_LINK);
+ return eRet;
+ }
+
+ sal_Int8 GdkToVcl(GdkDragAction dragOperation)
+ {
+ sal_Int8 nRet(0);
+ if (dragOperation & GDK_ACTION_COPY)
+ nRet |= css::datatransfer::dnd::DNDConstants::ACTION_COPY;
+ if (dragOperation & GDK_ACTION_MOVE)
+ nRet |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ if (dragOperation & GDK_ACTION_LINK)
+ nRet |= css::datatransfer::dnd::DNDConstants::ACTION_LINK;
+ return nRet;
+ }
+}
+
+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 {
+
+class GtkDropTargetDropContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDropContext>
+{
+ GdkDragContext *m_pContext;
+ guint m_nTime;
+public:
+ GtkDropTargetDropContext(GdkDragContext *pContext, guint nTime)
+ : m_pContext(pContext)
+ , m_nTime(nTime)
+ {
+ }
+
+ // XDropTargetDropContext
+ virtual void SAL_CALL acceptDrop(sal_Int8 dragOperation) override
+ {
+ gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
+ }
+
+ virtual void SAL_CALL rejectDrop() override
+ {
+ gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
+ }
+
+ virtual void SAL_CALL dropComplete(sal_Bool bSuccess) override
+ {
+ gtk_drag_finish(m_pContext, bSuccess, false, m_nTime);
+ if (GtkDragSource::g_ActiveDragSource)
+ {
+ g_DropSuccessSet = true;
+ g_DropSuccess = bSuccess;
+ }
+ }
+};
+
+}
+
+class GtkDnDTransferable : public GtkTransferable
+{
+ GdkDragContext *m_pContext;
+ guint m_nTime;
+ GtkWidget *m_pWidget;
+ GtkDropTarget* m_pDropTarget;
+ GMainLoop *m_pLoop;
+ GtkSelectionData *m_pData;
+public:
+ GtkDnDTransferable(GdkDragContext *pContext, guint nTime, GtkWidget *pWidget, GtkDropTarget *pDropTarget)
+ : m_pContext(pContext)
+ , m_nTime(nTime)
+ , m_pWidget(pWidget)
+ , m_pDropTarget(pDropTarget)
+ , m_pLoop(nullptr)
+ , m_pData(nullptr)
+ {
+ }
+
+ 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_aMimeTypeToAtom.find(aFlavor.MimeType);
+ if (it == m_aMimeTypeToAtom.end())
+ return css::uno::Any();
+
+ /* like gtk_clipboard_wait_for_contents run a sub loop
+ * waiting for drag-data-received triggered from
+ * gtk_drag_get_data
+ */
+ {
+ m_pLoop = g_main_loop_new(nullptr, true);
+ m_pDropTarget->SetFormatConversionRequest(this);
+
+ gtk_drag_get_data(m_pWidget, m_pContext, it->second, m_nTime);
+
+ if (g_main_loop_is_running(m_pLoop))
+ {
+ gdk_threads_leave();
+ g_main_loop_run(m_pLoop);
+ gdk_threads_enter();
+ }
+
+ g_main_loop_unref(m_pLoop);
+ m_pLoop = nullptr;
+ m_pDropTarget->SetFormatConversionRequest(nullptr);
+ }
+
+ css::uno::Any aRet;
+
+ if (aFlavor.MimeType == "text/plain;charset=utf-8")
+ {
+ OUString aStr;
+ gchar *pText = reinterpret_cast<gchar*>(gtk_selection_data_get_text(m_pData));
+ if (pText)
+ aStr = OUString(pText, rtl_str_getLength(pText), RTL_TEXTENCODING_UTF8);
+ g_free(pText);
+ aRet <<= aStr.replaceAll("\r\n", "\n");
+ }
+ else
+ {
+ gint length(0);
+ const guchar *rawdata = gtk_selection_data_get_data_with_length(m_pData,
+ &length);
+ // 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);
+
+ return aRet;
+ }
+
+ virtual std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector() override
+ {
+ std::vector<GdkAtom> targets;
+ for (GList* l = gdk_drag_context_list_targets(m_pContext); l; l = l->next)
+ targets.push_back(static_cast<GdkAtom>(l->data));
+ return GtkTransferable::getTransferDataFlavorsAsVector(targets.data(), targets.size());
+ }
+
+ void LoopEnd(GtkSelectionData *pData)
+ {
+ m_pData = pData;
+ g_main_loop_quit(m_pLoop);
+ }
+};
+
+// For LibreOffice internal D&D we provide the Transferable without Gtk
+// intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
+GtkDragSource* GtkDragSource::g_ActiveDragSource;
+
+gboolean GtkSalFrame::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return false;
+ return pThis->m_pDropTarget->signalDragDrop(pWidget, context, x, y, time);
+}
+
+gboolean GtkDropTarget::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time)
+{
+ // remove the deferred dragExit, as we'll do a drop
+#ifndef NDEBUG
+ bool res =
+#endif
+ g_idle_remove_by_data(this);
+ assert(res);
+
+ css::datatransfer::dnd::DropTargetDropEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(this);
+ aEvent.Context = new GtkDropTargetDropContext(context, time);
+ aEvent.LocationX = x;
+ aEvent.LocationY = y;
+ aEvent.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
+ // 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
+ GdkModifierType mask;
+ gdk_window_get_pointer(gtk_widget_get_window(pWidget), nullptr, nullptr, &mask);
+ if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
+ aEvent.DropAction |= css::datatransfer::dnd::DNDConstants::ACTION_DEFAULT;
+ aEvent.SourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
+ css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
+ // For LibreOffice internal D&D we provide the Transferable without Gtk
+ // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
+ if (GtkDragSource::g_ActiveDragSource)
+ xTransferable = GtkDragSource::g_ActiveDragSource->GetTransferrable();
+ else
+ xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
+ aEvent.Transferable = xTransferable;
+
+ fire_drop(aEvent);
+
+ return true;
+}
+
+namespace {
+
+class GtkDropTargetDragContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDragContext>
+{
+ GdkDragContext *m_pContext;
+ guint m_nTime;
+public:
+ GtkDropTargetDragContext(GdkDragContext *pContext, guint nTime)
+ : m_pContext(pContext)
+ , m_nTime(nTime)
+ {
+ }
+
+ virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) override
+ {
+ gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
+ }
+
+ virtual void SAL_CALL rejectDrag() override
+ {
+ gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
+ }
+};
+
+}
+
+void GtkSalFrame::signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return;
+ pThis->m_pDropTarget->signalDragDropReceived(pWidget, context, x, y, data, ttype, time);
+}
+
+void GtkDropTarget::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));
+}
+
+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);
+}
+
+
+gboolean GtkDropTarget::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time)
+{
+ if (!m_bInDrag)
+ gtk_drag_highlight(pWidget);
+
+ css::datatransfer::dnd::DropTargetDragEnterEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(this);
+ GtkDropTargetDragContext* pContext = new GtkDropTargetDragContext(context, time);
+ //preliminary accept the Drag and select the preferred action, the fire_* will
+ //inform the original caller of our choice and the callsite can decide
+ //to overrule this choice. i.e. typically here we default to ACTION_MOVE
+ sal_Int8 nSourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
+ GdkModifierType mask;
+ gdk_window_get_pointer(gtk_widget_get_window(pWidget), nullptr, nullptr, &mask);
+
+ // 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 = GtkDragSource::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);
+
+ gdk_drag_status(context, eAction, time);
+ 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 (GtkDragSource::g_ActiveDragSource)
+ xTransferable = GtkDragSource::g_ActiveDragSource->GetTransferrable();
+ else
+ xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
+ css::uno::Sequence<css::datatransfer::DataFlavor> aFormats = xTransferable->getTransferDataFlavors();
+ aEvent.SupportedDataFlavors = aFormats;
+ fire_dragEnter(aEvent);
+ m_bInDrag = true;
+ }
+ else
+ {
+ fire_dragOver(aEvent);
+ }
+
+ return true;
+}
+
+void GtkSalFrame::signalDragLeave(GtkWidget *pWidget, GdkDragContext *context, guint time, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDropTarget)
+ return;
+ pThis->m_pDropTarget->signalDragLeave(pWidget, context, time);
+}
+
+static gboolean lcl_deferred_dragExit(gpointer user_data)
+{
+ GtkDropTarget* pThis = static_cast<GtkDropTarget*>(user_data);
+ css::datatransfer::dnd::DropTargetEvent aEvent;
+ aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(pThis);
+ pThis->fire_dragExit(aEvent);
+ return FALSE;
+}
+
+void GtkDropTarget::signalDragLeave(GtkWidget* pWidget, GdkDragContext* /*context*/, guint /*time*/)
+{
+ m_bInDrag = false;
+ gtk_drag_unhighlight(pWidget);
+ // 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 )
+ {
+ 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_pEventBox = nullptr;
+ 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();
+ gtk_im_context_set_client_window(m_pIMContext, gtk_widget_get_window(m_pFrame->getMouseEventWidget()));
+ gtk_im_context_focus_in( m_pIMContext );
+ GetGenericUnixSalData()->ErrorTrapPop();
+ m_bFocused = true;
+
+}
+
+void GtkSalFrame::IMHandler::deleteIMContext()
+{
+ if( m_pIMContext )
+ {
+ // first give IC a chance to deinitialize
+ GetGenericUnixSalData()->ErrorTrapPush();
+ gtk_im_context_set_client_window( 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 )
+ {
+ 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 );
+ }
+}
+
+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();
+ }
+ }
+}
+
+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:
+ sal_attr |= ExtTextInputAttr::Underline;
+ break;
+ case PANGO_ATTR_STRIKETHROUGH:
+ sal_attr |= ExtTextInputAttr::RedText;
+ break;
+ default:
+ break;
+ }
+ pango_attribute_destroy (pango_attr);
+ tmp_list = tmp_list->next;
+ }
+ if (sal_attr == ExtTextInputAttr::NONE)
+ sal_attr |= ExtTextInputAttr::Underline;
+ g_slist_free (attr_list);
+
+ // Set the sal attributes on our text
+ // 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();
+}
+
+static uno::Reference<accessibility::XAccessibleEditableText> lcl_GetxText(vcl::Window *pFocusWin)
+{
+ uno::Reference<accessibility::XAccessibleEditableText> xText;
+ try
+ {
+ uno::Reference< accessibility::XAccessible > xAccessible( pFocusWin->GetAccessible() );
+ if (xAccessible.is())
+ xText = FindFocusedEditableText(xAccessible->getAccessibleContext());
+ }
+ catch(const uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "vcl.gtk3", "Exception in getting input method surrounding text");
+ }
+ return xText;
+}
+
+gboolean GtkSalFrame::IMHandler::signalIMRetrieveSurrounding( GtkIMContext* pContext, gpointer /*im_handler*/ )
+{
+ vcl::Window *pFocusWin = Application::GetFocusWindow();
+ if (!pFocusWin)
+ return true;
+
+ uno::Reference<accessibility::XAccessibleEditableText> xText = lcl_GetxText(pFocusWin);
+ if (xText.is())
+ {
+ sal_Int32 nPosition = xText->getCaretPosition();
+ if (nPosition != -1)
+ {
+ OUString sAllText = xText->getText();
+ OString sUTF = OUStringToOString(sAllText, RTL_TEXTENCODING_UTF8);
+ OUString sCursorText(sAllText.copy(0, nPosition));
+ gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
+ OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
+ return true;
+ }
+ }
+
+ return false;
+}
+
+gboolean GtkSalFrame::IMHandler::signalIMDeleteSurrounding( GtkIMContext*, gint offset, gint nchars,
+ gpointer /*im_handler*/ )
+{
+ vcl::Window *pFocusWin = Application::GetFocusWindow();
+ if (!pFocusWin)
+ return true;
+
+ uno::Reference<accessibility::XAccessibleEditableText> xText = lcl_GetxText(pFocusWin);
+ if (xText.is())
+ {
+ sal_Int32 nPosition = xText->getCaretPosition();
+ // #i111768# range checking
+ sal_Int32 nDeletePos = nPosition + offset;
+ sal_Int32 nDeleteEnd = nDeletePos + nchars;
+ if (nDeletePos < 0)
+ nDeletePos = 0;
+ if (nDeleteEnd < 0)
+ nDeleteEnd = 0;
+ if (nDeleteEnd > xText->getCharacterCount())
+ nDeleteEnd = xText->getCharacterCount();
+
+ xText->deleteText(nDeletePos, nDeleteEnd);
+ //tdf91641 adjust cursor if deleted chars shift it forward (normal case)
+ if (nDeletePos < nPosition)
+ {
+ if (nDeleteEnd <= nPosition)
+ nPosition = nPosition - (nDeleteEnd - nDeletePos);
+ else
+ nPosition = nDeletePos;
+
+ if (xText->getCharacterCount() >= nPosition)
+ xText->setCaretPosition( nPosition );
+ }
+ return true;
+ }
+
+ return false;
+}
+
+Size GtkSalDisplay::GetScreenSize( int nDisplayScreen )
+{
+ tools::Rectangle aRect = m_pSys->GetDisplayScreenPosSizePixel( nDisplayScreen );
+ return Size( aRect.GetWidth(), aRect.GetHeight() );
+}
+
+sal_uIntPtr GtkSalFrame::GetNativeWindowHandle(GtkWidget *pWidget)
+{
+ (void) this; // Silence loplugin:staticmethods
+ GdkDisplay *pDisplay = getGdkDisplay();
+ GdkWindow *pWindow = gtk_widget_get_window(pWidget);
+
+#if defined(GDK_WINDOWING_X11)
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ {
+ return GDK_WINDOW_XID(pWindow);
+ }
+#endif
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
+ {
+ return reinterpret_cast<sal_uIntPtr>(gdk_wayland_window_get_wl_surface(pWindow));
+ }
+#endif
+ return 0;
+}
+
+sal_uIntPtr GtkSalFrame::GetNativeWindowHandle()
+{
+ return GetNativeWindowHandle(m_pWindow);
+}
+
+void GtkDragSource::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 GtkDragSource::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;
+}
+
+std::vector<GtkTargetEntry> GtkDragSource::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
+{
+ return m_aConversionHelper.FormatsToGtk(rFormats);
+}
+
+void GtkDragSource::startDrag(const datatransfer::dnd::DragGestureEvent& rEvent,
+ sal_Int8 sourceActions, sal_Int32 /*cursor*/, sal_Int32 /*image*/,
+ const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
+ const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener)
+{
+ set_datatransfer(rTrans, rListener);
+
+ if (m_pFrame)
+ {
+ auto aFormats = m_xTrans->getTransferDataFlavors();
+ std::vector<GtkTargetEntry> aGtkTargets(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;
+ }
+
+ setActiveDragSource();
+
+ m_pFrame->startDrag(nDragButton, rEvent.DragOriginX, rEvent.DragOriginY,
+ VclToGdk(sourceActions), pTargetList);
+
+ gtk_target_list_unref(pTargetList);
+ for (auto &a : aGtkTargets)
+ g_free(a.target);
+ }
+ else
+ dragFailed();
+}
+
+void GtkSalFrame::startDrag(gint nButton, gint nDragOriginX, gint nDragOriginY,
+ GdkDragAction sourceActions, GtkTargetList* pTargetList)
+{
+ SolarMutexGuard aGuard;
+
+ assert(m_pDragSource);
+
+ GdkEvent aFakeEvent;
+ memset(&aFakeEvent, 0, sizeof(GdkEvent));
+ aFakeEvent.type = GDK_BUTTON_PRESS;
+ aFakeEvent.button.window = gtk_widget_get_window(getMouseEventWidget());
+ aFakeEvent.button.time = GDK_CURRENT_TIME;
+ GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(getGdkDisplay());
+ aFakeEvent.button.device = gdk_device_manager_get_client_pointer(pDeviceManager);
+
+ GdkDragContext *pContext = gtk_drag_begin_with_coordinates(getMouseEventWidget(),
+ pTargetList,
+ sourceActions,
+ nButton,
+ &aFakeEvent,
+ nDragOriginX,
+ nDragOriginY);
+
+ if (!pContext)
+ m_pDragSource->dragFailed();
+}
+
+void GtkDragSource::dragFailed()
+{
+ if (m_xListener.is())
+ {
+ datatransfer::dnd::DragSourceDropEvent aEv;
+ aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_NONE;
+ aEv.DropSuccess = false;
+ auto xListener = m_xListener;
+ m_xListener.clear();
+ xListener->dragDropEnd(aEv);
+ }
+}
+
+gboolean GtkSalFrame::signalDragFailed(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkDragResult /*result*/, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDragSource)
+ return false;
+ pThis->m_pDragSource->dragFailed();
+ return false;
+}
+
+void GtkDragSource::dragDelete()
+{
+ if (m_xListener.is())
+ {
+ datatransfer::dnd::DragSourceDropEvent aEv;
+ aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ aEv.DropSuccess = true;
+ auto xListener = m_xListener;
+ m_xListener.clear();
+ xListener->dragDropEnd(aEv);
+ }
+}
+
+void GtkSalFrame::signalDragDelete(GtkWidget* /*widget*/, GdkDragContext* /*context*/, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDragSource)
+ return;
+ pThis->m_pDragSource->dragDelete();
+}
+
+void GtkDragSource::dragEnd(GdkDragContext* context)
+{
+ if (m_xListener.is())
+ {
+ datatransfer::dnd::DragSourceDropEvent aEv;
+ aEv.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
+ // 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;
+}
+
+void GtkSalFrame::signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDragSource)
+ return;
+ pThis->m_pDragSource->dragEnd(context);
+}
+
+void GtkDragSource::dragDataGet(GtkSelectionData *data, guint info)
+{
+ m_aConversionHelper.setSelectionData(m_xTrans, data, info);
+}
+
+void GtkSalFrame::signalDragDataGet(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkSelectionData *data, guint info,
+ guint /*time*/, gpointer frame)
+{
+ GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
+ if (!pThis->m_pDragSource)
+ return;
+ pThis->m_pDragSource->dragDataGet(data, info);
+}
+
+bool GtkSalFrame::CallCallbackExc(SalEvent nEvent, const void* pEvent) const
+{
+ bool nRet = false;
+ try
+ {
+ nRet = CallCallback(nEvent, pEvent);
+ }
+ catch (...)
+ {
+ GtkSalData *pSalData = static_cast<GtkSalData*>(GetSalData());
+ pSalData->setException(std::current_exception());
+ }
+ return nRet;
+}
+
+void GtkSalFrame::nopaint_container_resize_children(GtkContainer *pContainer)
+{
+ bool bOrigSalObjectSetPosSize = m_bSalObjectSetPosSize;
+ m_bSalObjectSetPosSize = true;
+ gtk_container_resize_children(pContainer);
+ m_bSalObjectSetPosSize = bOrigSalObjectSetPosSize;
+}
+
+GdkEvent* GtkSalFrame::makeFakeKeyPress(GtkWidget* pWidget)
+{
+ GdkEvent *event = gdk_event_new(GDK_KEY_PRESS);
+ event->key.window = GDK_WINDOW(g_object_ref(gtk_widget_get_window(pWidget)));
+
+#if GTK_CHECK_VERSION(3, 20, 0)
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ GdkSeat *seat = gdk_display_get_default_seat(gtk_widget_get_display(pWidget));
+ gdk_event_set_device(event, gdk_seat_get_keyboard(seat));
+ }
+#endif
+
+ 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;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3gtkinst.cxx b/vcl/unx/gtk3/gtk3gtkinst.cxx
new file mode 100644
index 000000000..0290359be
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3gtkinst.cxx
@@ -0,0 +1,16272 @@
+/* -*- 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 <osl/process.h>
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#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/gtkprn.hxx>
+#include <unx/gtk/gtksalmenu.hxx>
+#include <headless/svpvd.hxx>
+#include <headless/svpbmp.hxx>
+#include <vcl/inputtypes.hxx>
+#include <vcl/transfer.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>
+
+#include <unx/gtk/gtkprintwrapper.hxx>
+
+#include "a11y/atkwrapper.hxx"
+#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/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 <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <comphelper/string.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <rtl/bootstrap.hxx>
+#include <o3tl/unreachable.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 <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 <strings.hrc>
+#include <window.h>
+#include <numeric>
+
+#include <boost/property_tree/ptree.hpp>
+
+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*>(GetSalData()->m_pInstance->GetYieldMutex())
+ static void GdkThreadsEnter()
+ {
+ GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
+ pYieldMutex->ThreadsEnter();
+ }
+ static void GdkThreadsLeave()
+ {
+ GtkYieldMutex *pYieldMutex = GET_YIELD_MUTEX();
+ pYieldMutex->ThreadsLeave();
+ }
+
+ VCLPLUG_GTK_PUBLIC SalInstance* create_SalInstance()
+ {
+ SAL_INFO(
+ "vcl.gtk",
+ "create vcl plugin instance with gtk version " << gtk_major_version
+ << " " << gtk_minor_version << " " << gtk_micro_version);
+
+ if (gtk_major_version == 3 && gtk_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
+
+ // 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");
+
+ auto pYieldMutex = std::make_unique<GtkYieldMutex>();
+
+ gdk_threads_init();
+
+ GtkInstance* pInstance = new GtkInstance( std::move(pYieldMutex) );
+ SAL_INFO("vcl.gtk", "creating GtkInstance " << pInstance);
+
+ // Create SalData, this does not leak
+ new GtkSalData( pInstance );
+
+ return pInstance;
+ }
+}
+
+static VclInputFlags categorizeEvent(const GdkEvent *pEvent)
+{
+ VclInputFlags nType = VclInputFlags::NONE;
+ switch( pEvent->type )
+ {
+ case GDK_MOTION_NOTIFY:
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ 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;
+ case GDK_EXPOSE:
+ nType = VclInputFlags::PAINT;
+ break;
+ default:
+ nType = VclInputFlags::OTHER;
+ break;
+ }
+ return nType;
+}
+
+GtkInstance::GtkInstance( std::unique_ptr<SalYieldMutex> pMutex )
+ : SvpSalInstance( std::move(pMutex) )
+ , m_pTimer(nullptr)
+ , bNeedsInit(true)
+ , m_pLastCairoFontOptions(nullptr)
+{
+}
+
+//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();
+
+ InitAtkBridge();
+
+ 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 );
+ DeInitAtkBridge();
+ 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 GtkSalInfoPrinter;
+ configurePspInfoPrinter(pPrinter, pQueueInfo, pSetupData);
+ return pPrinter;
+}
+
+std::unique_ptr<SalPrinter> GtkInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
+{
+ EnsureInit();
+ mbPrinterInit = true;
+ return std::unique_ptr<SalPrinter>(new GtkSalPrinter( 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()) {
+ auto n = yieldCounts.top();
+ yieldCounts.pop();
+ assert(n > 0);
+ n--;
+ if (n > 0)
+ acquire(n);
+ }
+}
+
+void GtkYieldMutex::ThreadsLeave()
+{
+ assert(m_nCount != 0);
+ yieldCounts.push(m_nCount);
+ release(true);
+}
+
+std::unique_ptr<SalVirtualDevice> GtkInstance::CreateVirtualDevice( SalGraphics *pG,
+ long &nDX, long &nDY,
+ DeviceFormat eFormat,
+ const SystemGraphicsData* pGd )
+{
+ EnsureInit();
+ SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(pG);
+ 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(eFormat, 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());
+}
+
+bool GtkInstance::AnyInput( VclInputFlags nType )
+{
+ EnsureInit();
+ if( (nType & VclInputFlags::TIMER) && IsTimerExpired() )
+ return true;
+ if (!gdk_events_pending())
+ return false;
+
+ if (nType == VCL_INPUT_ANY)
+ return true;
+
+ bool bRet = false;
+ std::deque<GdkEvent*> aEvents;
+ GdkEvent *pEvent = nullptr;
+ while ((pEvent = gdk_event_get()))
+ {
+ 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_event_put(pEvent);
+ gdk_event_free(pEvent);
+ aEvents.pop_front();
+ }
+ return bRet;
+}
+
+std::unique_ptr<GenPspGraphics> GtkInstance::CreatePrintGraphics()
+{
+ EnsureInit();
+ return std::make_unique<GenPspGraphics>();
+}
+
+std::shared_ptr<vcl::unx::GtkPrintWrapper> const &
+GtkInstance::getPrintWrapper() const
+{
+ if (!m_xPrintWrapper)
+ m_xPrintWrapper = std::make_shared<vcl::unx::GtkPrintWrapper>();
+ return m_xPrintWrapper;
+}
+
+const cairo_font_options_t* GtkInstance::GetCairoFontOptions()
+{
+ const cairo_font_options_t* pCairoFontOptions = gdk_screen_get_font_options(gdk_screen_get_default());
+ 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
+ };
+
+ static 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;
+ }
+ };
+}
+
+std::vector<css::datatransfer::DataFlavor> GtkTransferable::getTransferDataFlavorsAsVector(GdkAtom *targets, gint n_targets)
+{
+ std::vector<css::datatransfer::DataFlavor> aVector;
+
+ bool bHaveText = false, bHaveUTF16 = false;
+
+ for (gint i = 0; i < n_targets; ++i)
+ {
+ gchar* pName = gdk_atom_name(targets[i]);
+ 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)
+ {
+ g_free(pName);
+ 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)
+ {
+ g_free(pName);
+ continue;
+ }
+
+ aFlavor.MimeType = OUString(pFinalName,
+ strlen(pFinalName),
+ RTL_TEXTENCODING_UTF8);
+
+ m_aMimeTypeToAtom[aFlavor.MimeType] = targets[i];
+
+ aFlavor.DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ sal_Int32 nIndex(0);
+ if (aFlavor.MimeType.getToken(0, ';', nIndex) == "text/plain")
+ {
+ bHaveText = true;
+ OUString aToken(aFlavor.MimeType.getToken(0, ';', nIndex));
+ if (aToken == "charset=utf-16")
+ {
+ bHaveUTF16 = true;
+ aFlavor.DataType = cppu::UnoType<OUString>::get();
+ }
+ }
+ aVector.push_back(aFlavor);
+ g_free(pName);
+ }
+
+ //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));
+}
+
+namespace {
+
+class GtkClipboardTransferable : public GtkTransferable
+{
+private:
+ GdkAtom m_nSelection;
+public:
+
+ explicit GtkClipboardTransferable(GdkAtom nSelection)
+ : m_nSelection(nSelection)
+ {
+ }
+
+ /*
+ * XTransferable
+ */
+
+ virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override
+ {
+ GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
+ if (rFlavor.MimeType == "text/plain;charset=utf-16")
+ {
+ OUString aStr;
+ gchar *pText = gtk_clipboard_wait_for_text(clipboard);
+ if (pText)
+ aStr = OUString(pText, strlen(pText), RTL_TEXTENCODING_UTF8);
+ g_free(pText);
+ css::uno::Any aRet;
+ aRet <<= aStr.replaceAll("\r\n", "\n");
+ return aRet;
+ }
+
+ auto it = m_aMimeTypeToAtom.find(rFlavor.MimeType);
+ if (it == m_aMimeTypeToAtom.end())
+ return css::uno::Any();
+
+ css::uno::Any aRet;
+ 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;
+ return aRet;
+ }
+
+ std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector()
+ override
+ {
+ std::vector<css::datatransfer::DataFlavor> aVector;
+
+ GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
+
+ GdkAtom *targets;
+ gint n_targets;
+ if (gtk_clipboard_wait_for_targets(clipboard, &targets, &n_targets))
+ {
+ aVector = GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
+ g_free(targets);
+ }
+
+ return aVector;
+ }
+};
+
+class VclGtkClipboard :
+ public cppu::WeakComponentImplHelper<
+ datatransfer::clipboard::XSystemClipboard,
+ datatransfer::clipboard::XFlushableClipboard,
+ XServiceInfo>
+{
+ GdkAtom m_nSelection;
+ 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;
+ std::vector<GtkTargetEntry> m_aGtkTargets;
+ VclToGtkHelper m_aConversionHelper;
+
+ DECL_LINK(AsyncSetGtkClipboard, void*, void);
+public:
+
+ explicit VclGtkClipboard(GdkAtom nSelection);
+ 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;
+
+ void ClipboardGet(GtkSelectionData *selection_data, guint info);
+ void ClipboardClear();
+ void OwnerPossiblyChanged(GtkClipboard *clipboard);
+ 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_nSelection);
+ }
+
+ return m_aContents;
+}
+
+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;
+ }
+}
+
+namespace
+{
+ void ClipboardGetFunc(GtkClipboard* /*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(GtkClipboard* /*clipboard*/, gpointer user_data_or_owner)
+ {
+ VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data_or_owner);
+ pThis->ClipboardClear();
+ }
+
+ void handle_owner_change(GtkClipboard *clipboard, GdkEvent* /*event*/, gpointer user_data)
+ {
+ VclGtkClipboard* pThis = static_cast<VclGtkClipboard*>(user_data);
+ pThis->OwnerPossiblyChanged(clipboard);
+ }
+}
+
+void VclGtkClipboard::OwnerPossiblyChanged(GtkClipboard* clipboard)
+{
+ SyncGtkClipboard(); // tdf#138183 do any pending SetGtkClipboard calls
+ if (!m_aContents.is())
+ return;
+
+ //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);
+
+ 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;
+ }
+ for (auto &a : m_aGtkTargets)
+ g_free(a.target);
+ m_aGtkTargets.clear();
+}
+
+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;
+}
+
+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());
+}
+
+VclGtkClipboard::VclGtkClipboard(GdkAtom nSelection)
+ : cppu::WeakComponentImplHelper<datatransfer::clipboard::XSystemClipboard,
+ datatransfer::clipboard::XFlushableClipboard, XServiceInfo>
+ (m_aMutex)
+ , m_nSelection(nSelection)
+ , m_pSetClipboardEvent(nullptr)
+{
+ GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
+ m_nOwnerChangedSignalId = g_signal_connect(clipboard, "owner-change",
+ G_CALLBACK(handle_owner_change), this);
+}
+
+void VclGtkClipboard::flushClipboard()
+{
+ SolarMutexGuard aGuard;
+
+ if (GDK_SELECTION_CLIPBOARD != m_nSelection)
+ return;
+
+ GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
+ gtk_clipboard_store(clipboard);
+}
+
+VclGtkClipboard::~VclGtkClipboard()
+{
+ GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
+ g_signal_handler_disconnect(clipboard, m_nOwnerChangedSignalId);
+ if (!m_aGtkTargets.empty())
+ {
+ gtk_clipboard_clear(clipboard);
+ ClipboardClear();
+ }
+ assert(!m_pSetClipboardEvent);
+ assert(m_aGtkTargets.empty());
+}
+
+std::vector<GtkTargetEntry> VclToGtkHelper::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
+{
+ std::vector<GtkTargetEntry> aGtkTargets;
+
+ bool bHaveText(false), bHaveUTF8(false);
+ for (const css::datatransfer::DataFlavor& rFlavor : rFormats)
+ {
+ sal_Int32 nIndex(0);
+ if (rFlavor.MimeType.getToken(0, ';', nIndex) == "text/plain")
+ {
+ bHaveText = true;
+ OUString aToken(rFlavor.MimeType.getToken(0, ';', nIndex));
+ if (aToken == "charset=utf-8")
+ {
+ bHaveUTF8 = true;
+ }
+ }
+ GtkTargetEntry aEntry(makeGtkTargetEntry(rFlavor));
+ aGtkTargets.push_back(aEntry);
+ }
+
+ 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()
+{
+ GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
+ 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());
+}
+
+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;
+ m_aOwner = xClipboardOwner;
+
+ std::vector< Reference< datatransfer::clipboard::XClipboardListener > > aListeners( m_aListeners );
+ datatransfer::clipboard::ClipboardEvent aEv;
+
+ GtkClipboard* clipboard = gtk_clipboard_get(m_nSelection);
+ if (!m_aGtkTargets.empty())
+ {
+ gtk_clipboard_clear(clipboard);
+ ClipboardClear();
+ }
+ assert(m_aGtkTargets.empty());
+ if (m_aContents.is())
+ {
+ std::vector<GtkTargetEntry> aGtkTargets(m_aConversionHelper.FormatsToGtk(aFormats));
+ if (!aGtkTargets.empty())
+ {
+ 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);
+
+ 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_nSelection == GDK_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)
+{
+ 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);
+ }
+
+ GdkAtom nSelection = (sel == "CLIPBOARD") ? GDK_SELECTION_CLIPBOARD : GDK_SELECTION_PRIMARY;
+
+ auto it = m_aClipboards.find(nSelection);
+ if (it != m_aClipboards.end())
+ return it->second;
+
+ Reference<XInterface> xClipboard(static_cast<cppu::OWeakObject *>(new VclGtkClipboard(nSelection)));
+ m_aClipboards[nSelection] = xClipboard;
+
+ return xClipboard;
+}
+
+GtkDropTarget::GtkDropTarget()
+ : WeakComponentImplHelper(m_aMutex)
+ , m_pFrame(nullptr)
+ , m_pFormatConversionRequest(nullptr)
+ , m_bActive(false)
+ , m_bInDrag(false)
+ , m_nDefaultActions(0)
+{
+}
+
+OUString SAL_CALL GtkDropTarget::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.VclGtkDropTarget";
+}
+
+sal_Bool SAL_CALL GtkDropTarget::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL GtkDropTarget::getSupportedServiceNames()
+{
+ Sequence<OUString> aRet { "com.sun.star.datatransfer.dnd.GtkDropTarget" };
+ return aRet;
+}
+
+GtkDropTarget::~GtkDropTarget()
+{
+ if (m_pFrame)
+ m_pFrame->deregisterDropTarget(this);
+}
+
+void GtkDropTarget::deinitialize()
+{
+ m_pFrame = nullptr;
+ m_bActive = false;
+}
+
+void GtkDropTarget::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 GtkDropTarget::addDropTargetListener( const Reference< css::datatransfer::dnd::XDropTargetListener >& xListener)
+{
+ ::osl::Guard< ::osl::Mutex > aGuard( m_aMutex );
+
+ m_aListeners.push_back( xListener );
+}
+
+void GtkDropTarget::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 GtkDropTarget::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 GtkDropTarget::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 GtkDropTarget::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 GtkDropTarget::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 GtkDropTarget::isActive()
+{
+ return m_bActive;
+}
+
+void GtkDropTarget::setActive(sal_Bool bActive)
+{
+ m_bActive = bActive;
+}
+
+sal_Int8 GtkDropTarget::getDefaultActions()
+{
+ return m_nDefaultActions;
+}
+
+void GtkDropTarget::setDefaultActions(sal_Int8 nDefaultActions)
+{
+ m_nDefaultActions = nDefaultActions;
+}
+
+Reference< XInterface > GtkInstance::CreateDropTarget()
+{
+ return Reference<XInterface>(static_cast<cppu::OWeakObject*>(new GtkDropTarget));
+}
+
+GtkDragSource::~GtkDragSource()
+{
+ if (m_pFrame)
+ m_pFrame->deregisterDragSource(this);
+
+ if (GtkDragSource::g_ActiveDragSource == this)
+ {
+ SAL_WARN( "vcl.gtk", "dragEnd should have been called on GtkDragSource before dtor");
+ GtkDragSource::g_ActiveDragSource = nullptr;
+ }
+}
+
+void GtkDragSource::deinitialize()
+{
+ m_pFrame = nullptr;
+}
+
+sal_Bool GtkDragSource::isDragImageSupported()
+{
+ return true;
+}
+
+sal_Int32 GtkDragSource::getDefaultCursor( sal_Int8 )
+{
+ return 0;
+}
+
+void GtkDragSource::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 GtkDragSource::getImplementationName()
+{
+ return "com.sun.star.datatransfer.dnd.VclGtkDragSource";
+}
+
+sal_Bool SAL_CALL GtkDragSource::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL GtkDragSource::getSupportedServiceNames()
+{
+ Sequence<OUString> aRet { "com.sun.star.datatransfer.dnd.GtkDragSource" };
+ return aRet;
+}
+
+Reference< XInterface > GtkInstance::CreateDragSource()
+{
+ return Reference< XInterface >( static_cast<cppu::OWeakObject *>(new GtkDragSource()) );
+}
+
+namespace {
+
+class GtkOpenGLContext : public OpenGLContext
+{
+ GLWindow m_aGLWin;
+ GtkWidget *m_pGLArea;
+ GdkGLContext *m_pContext;
+ guint m_nAreaFrameBuffer;
+ guint m_nFrameBuffer;
+ guint m_nRenderBuffer;
+ guint m_nDepthBuffer;
+ guint m_nFrameScratchBuffer;
+ guint m_nRenderScratchBuffer;
+ guint m_nDepthScratchBuffer;
+
+public:
+ GtkOpenGLContext()
+ : OpenGLContext()
+ , m_pGLArea(nullptr)
+ , m_pContext(nullptr)
+ , 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;
+ }
+
+ 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);
+ }
+
+ virtual bool ImplInit() override
+ {
+ const SystemEnvData* pEnvData = m_pChildWindow->GetSystemData();
+ GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
+ m_pGLArea = gtk_gl_area_new();
+ g_signal_connect(G_OBJECT(m_pGLArea), "destroy", G_CALLBACK(signalDestroy), this);
+ 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);
+ gtk_container_add(GTK_CONTAINER(pParent), m_pGLArea);
+ gtk_widget_show_all(pParent);
+
+ 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);
+
+ GdkWindow *pWindow = gtk_widget_get_window(pParent);
+ m_pContext = gdk_window_create_gl_context(pWindow, nullptr);
+ 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_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;
+ return G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
+}
+
+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;
+ return G_TYPE_CHECK_INSTANCE_TYPE(pDisplay, get_type());
+}
+
+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 GtkToVcl(const GdkEventKey& rEvent)
+ {
+ sal_uInt16 nKeyCode = GtkSalFrame::GetKeyCode(rEvent.keyval);
+ if (nKeyCode == 0)
+ {
+ guint updated_keyval = GtkSalFrame::GetKeyValFor(gdk_keymap_get_default(), rEvent.hardware_keycode, rEvent.group);
+ nKeyCode = GtkSalFrame::GetKeyCode(updated_keyval);
+ }
+ nKeyCode |= GtkSalFrame::GetKeyModCode(rEvent.state);
+ return KeyEvent(gdk_keyval_to_unicode(rEvent.keyval), nKeyCode, 0);
+ }
+}
+
+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();
+ }
+
+ 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))
+ {
+ gtk_container_child_get(GTK_CONTAINER(pParent), pWidget,
+ "left-attach", &nTopAttach,
+ "top-attach", &nLeftAttach,
+ "width", &nWidth,
+ "height", &nHeight,
+ nullptr);
+ }
+
+ 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);
+ }
+
+ gtk_container_remove(GTK_CONTAINER(pParent), pWidget);
+
+ gtk_widget_set_visible(pReplacement, gtk_widget_get_visible(pWidget));
+ gtk_widget_set_no_show_all(pReplacement, gtk_widget_get_no_show_all(pWidget));
+
+ 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);
+
+ gtk_container_add(GTK_CONTAINER(pParent), pReplacement);
+
+ if (GTK_IS_GRID(pParent))
+ {
+ gtk_container_child_set(GTK_CONTAINER(pParent), pReplacement,
+ "left-attach", nTopAttach,
+ "top-attach", nLeftAttach,
+ "width", nWidth,
+ "height", nHeight,
+ nullptr);
+ }
+
+ if (GTK_IS_BOX(pParent))
+ {
+ gtk_container_child_set(GTK_CONTAINER(pParent), pReplacement,
+ "expand", bExpand,
+ "fill", bFill,
+ "pack-type", ePackType,
+ "padding", nPadding,
+ "position", nPosition,
+ nullptr);
+ }
+
+ 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));
+
+ g_object_unref(pWidget);
+ }
+
+ void insertAsParent(GtkWidget* pWidget, GtkWidget* pReplacement)
+ {
+ g_object_ref(pWidget);
+
+ replaceWidget(pWidget, pReplacement);
+
+ gtk_container_add(GTK_CONTAINER(pReplacement), pWidget);
+
+ g_object_unref(pWidget);
+ }
+
+ GtkWidget* ensureEventWidget(GtkWidget* pWidget)
+ {
+ 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;
+ }
+}
+
+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;
+}
+
+class GtkInstanceWidget : public virtual weld::Widget
+{
+protected:
+ GtkWidget* m_pWidget;
+ GtkWidget* m_pMouseEventBox;
+ GtkInstanceBuilder* m_pBuilder;
+
+ DECL_LINK(async_signal_focus_in, void*, void);
+ DECL_LINK(async_signal_focus_out, void*, void);
+ DECL_LINK(async_drag_cancel, void*, void);
+
+ void launch_signal_focus_in()
+ {
+ // in e.g. function wizard RefEdits we want to select all when we get focus
+ // but there are pending gtk handlers which change selection after our handler
+ // post our focus in event to happen after those finish
+ if (m_pFocusInEvent)
+ Application::RemoveUserEvent(m_pFocusInEvent);
+ m_pFocusInEvent = Application::PostUserEvent(LINK(this, GtkInstanceWidget, async_signal_focus_in));
+ }
+
+ static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ pThis->launch_signal_focus_in();
+ return false;
+ }
+
+ void signal_focus_in()
+ {
+ 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);
+ }
+
+ void launch_signal_focus_out()
+ {
+ // tdf#127262 because focus in is async, focus out must not appear out
+ // of sequence to focus in
+ if (m_pFocusOutEvent)
+ Application::RemoveUserEvent(m_pFocusOutEvent);
+ m_pFocusOutEvent = Application::PostUserEvent(LINK(this, GtkInstanceWidget, async_signal_focus_out));
+ }
+
+ static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->launch_signal_focus_out();
+ return false;
+ }
+
+ 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);
+ }
+
+ void signal_focus_out()
+ {
+ m_aFocusOutHdl.Call(*this);
+ }
+
+ void ensureEventWidget()
+ {
+ if (!m_pMouseEventBox)
+ m_pMouseEventBox = ::ensureEventWidget(m_pWidget);
+ }
+
+ void ensureButtonPressSignal()
+ {
+ if (!m_nButtonPressSignalId)
+ {
+ ensureEventWidget();
+ m_nButtonPressSignalId = g_signal_connect(m_pMouseEventBox, "button-press-event", G_CALLBACK(signalButton), this);
+ }
+ }
+
+ 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);
+ }
+
+ bool SwapForRTL() const
+ {
+ return ::SwapForRTL(m_pWidget);
+ }
+
+ void do_enable_drag_source(rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants)
+ {
+ css::uno::Reference<css::datatransfer::XTransferable> xTrans(rHelper.get());
+ css::uno::Reference<css::datatransfer::dnd::XDragSourceListener> xListener(rHelper.get());
+
+ ensure_drag_source();
+
+ auto aFormats = xTrans->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(xTrans, xListener);
+ }
+
+ 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())
+ m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
+ }
+
+ void ensure_drag_begin_end()
+ {
+ if (!m_nDragBeginSignalId)
+ {
+ // using "after" due to https://gitlab.gnome.org/GNOME/pygobject/issues/251
+ m_nDragBeginSignalId = g_signal_connect_after(m_pWidget, "drag-begin", G_CALLBACK(signalDragBegin), this);
+ }
+ if (!m_nDragEndSignalId)
+ m_nDragEndSignalId = g_signal_connect(m_pWidget, "drag-end", G_CALLBACK(signalDragEnd), this);
+ }
+
+private:
+ bool m_bTakeOwnership;
+ bool m_bDraggedOver;
+ sal_uInt16 m_nLastMouseButton;
+ sal_uInt16 m_nLastMouseClicks;
+ int m_nPressedButton;
+ int m_nPressStartX;
+ int m_nPressStartY;
+ ImplSVEvent* m_pFocusInEvent;
+ ImplSVEvent* m_pFocusOutEvent;
+ ImplSVEvent* m_pDragCancelEvent;
+ GtkCssProvider* m_pBgCssProvider;
+ GdkDragAction m_eDragAction;
+ gulong m_nFocusInSignalId;
+ gulong m_nMnemonicActivateSignalId;
+ gulong m_nFocusOutSignalId;
+ gulong m_nKeyPressSignalId;
+ gulong m_nKeyReleaseSignalId;
+ gulong m_nSizeAllocateSignalId;
+ 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;
+
+ rtl::Reference<GtkDropTarget> m_xDropTarget;
+ rtl::Reference<GtkDragSource> 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);
+ }
+
+ static gboolean signalKey(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ // #i1820# use locale specific decimal separator
+ if (pEvent->keyval == GDK_KEY_KP_Decimal && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep())
+ {
+ OUString aSep(Application::GetSettings().GetLocaleDataWrapper().getNumDecimalSep());
+ pEvent->keyval = aSep[0];
+ }
+
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ return pThis->signal_key(pEvent);
+ }
+
+ virtual bool signal_popup_menu(const CommandEvent&)
+ {
+ return false;
+ }
+
+ static gboolean signalButton(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);
+ }
+
+ 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)
+ {
+ GtkTargetList* pDragData = (m_eDragAction != 0 && m_nPressedButton != -1 && m_xDragSource.is()) ? gtk_drag_source_get_target_list(m_pWidget) : nullptr;
+ bool bUnsetDragIcon(false);
+ if (pDragData && 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,
+ pDragData,
+ 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;
+ }
+
+ if (!m_aMouseMotionHdl.IsSet())
+ return false;
+
+ Point aPos(pEvent->x, pEvent->y);
+ if (SwapForRTL())
+ aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
+ sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
+ MouseEvent aMEvt(aPos, 0, ImplGetMouseMoveMode(nModCode), nModCode, nModCode);
+
+ m_aMouseMotionHdl.Call(aMEvt);
+ return true;
+ }
+
+ static gboolean signalCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_crossing(pEvent);
+ }
+
+ bool signal_crossing(const GdkEventCrossing* pEvent)
+ {
+ if (!m_aMouseMotionHdl.IsSet())
+ return false;
+
+ Point aPos(pEvent->x, pEvent->y);
+ if (SwapForRTL())
+ aPos.setX(gtk_widget_get_allocated_width(m_pWidget) - 1 - aPos.X());
+ sal_uInt32 nModCode = GtkSalFrame::GetMouseModCode(pEvent->state);
+ MouseEventModifiers eModifiers = ImplGetMouseMoveMode(nModCode);
+ eModifiers = eModifiers | (pEvent->type == GDK_ENTER_NOTIFY ? MouseEventModifiers::ENTERWINDOW : MouseEventModifiers::LEAVEWINDOW);
+ MouseEvent aMEvt(aPos, 0, eModifiers, nModCode, nModCode);
+
+ m_aMouseMotionHdl.Call(aMEvt);
+ return true;
+ }
+
+ virtual void drag_started()
+ {
+ }
+
+ 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);
+ }
+
+ virtual void drag_ended()
+ {
+ }
+
+ static void signalDragLeave(GtkWidget *pWidget, GdkDragContext *context, guint time, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ pThis->m_xDropTarget->signalDragLeave(pWidget, context, time);
+ if (pThis->m_bDraggedOver)
+ {
+ pThis->m_bDraggedOver = false;
+ pThis->drag_ended();
+ }
+ }
+
+ static void signalDragBegin(GtkWidget*, GdkDragContext* context, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ pThis->signal_drag_begin(context);
+ }
+
+ void ensure_drag_source()
+ {
+ if (!m_xDragSource)
+ {
+ m_xDragSource.set(new GtkDragSource);
+
+ 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);
+
+ ensure_drag_begin_end();
+ }
+ }
+
+ virtual bool do_signal_drag_begin(bool& rUnsetDragIcon)
+ {
+ rUnsetDragIcon = false;
+ return false;
+ }
+
+ void signal_drag_begin(GdkDragContext* context)
+ {
+ bool bUnsetDragIcon(false);
+ if (do_signal_drag_begin(bUnsetDragIcon))
+ {
+ launch_drag_cancel(context);
+ return;
+ }
+ if (bUnsetDragIcon)
+ {
+ cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
+ gtk_drag_set_icon_surface(context, surface);
+ }
+ if (!m_xDragSource)
+ return;
+ m_xDragSource->setActiveDragSource();
+ }
+
+ virtual void do_signal_drag_end()
+ {
+ }
+
+ static void signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer widget)
+ {
+ GtkInstanceWidget* pThis = static_cast<GtkInstanceWidget*>(widget);
+ pThis->do_signal_drag_end();
+ if (pThis->m_xDragSource.is())
+ pThis->m_xDragSource->dragEnd(context);
+ }
+
+ 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);
+ }
+
+ virtual void drag_source_set(const std::vector<GtkTargetEntry>& rGtkTargets, GdkDragAction eDragAction)
+ {
+ gtk_drag_source_set(m_pWidget, GDK_BUTTON1_MASK, rGtkTargets.data(), rGtkTargets.size(), eDragAction);
+ }
+
+ void set_background(const OUString* pColor)
+ {
+ if (!pColor && !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 (!pColor)
+ return;
+ m_pBgCssProvider = gtk_css_provider_new();
+ OUString aBuffer = "* { background-color: #" + *pColor + "; }";
+ OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
+ gtk_css_provider_load_from_data(m_pBgCssProvider, aResult.getStr(), aResult.getLength(), nullptr);
+ gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(m_pBgCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+public:
+ GtkInstanceWidget(GtkWidget* pWidget, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : m_pWidget(pWidget)
+ , m_pMouseEventBox(nullptr)
+ , m_pBuilder(pBuilder)
+ , m_bTakeOwnership(bTakeOwnership)
+ , m_bDraggedOver(false)
+ , m_nLastMouseButton(0)
+ , m_nLastMouseClicks(0)
+ , m_nPressedButton(-1)
+ , m_nPressStartX(-1)
+ , m_nPressStartY(-1)
+ , m_pFocusInEvent(nullptr)
+ , m_pFocusOutEvent(nullptr)
+ , m_pDragCancelEvent(nullptr)
+ , m_pBgCssProvider(nullptr)
+ , m_eDragAction(GdkDragAction(0))
+ , 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 (!bTakeOwnership)
+ g_object_ref(m_pWidget);
+
+ localizeDecimalSeparator();
+ }
+
+ virtual void connect_key_press(const Link<const KeyEvent&, bool>& rLink) override
+ {
+ if (!m_nKeyPressSignalId)
+ m_nKeyPressSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKey), this);
+ weld::Widget::connect_key_press(rLink);
+ }
+
+ virtual void connect_key_release(const Link<const KeyEvent&, bool>& rLink) override
+ {
+ if (!m_nKeyReleaseSignalId)
+ m_nKeyReleaseSignalId = g_signal_connect(m_pWidget, "key-release-event", G_CALLBACK(signalKey), this);
+ 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
+ {
+ ensureEventWidget();
+ 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);
+ weld::Widget::connect_mouse_move(rLink);
+ }
+
+ virtual void connect_mouse_release(const Link<const MouseEvent&, bool>& rLink) override
+ {
+ ensureEventWidget();
+ if (!m_nButtonReleaseSignalId)
+ m_nButtonReleaseSignalId = g_signal_connect(m_pMouseEventBox, "button-release-event", G_CALLBACK(signalButton), this);
+ 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
+ {
+ disable_notify_events();
+ gtk_widget_grab_focus(m_pWidget);
+ enable_notify_events();
+ }
+
+ virtual bool has_focus() const override
+ {
+ return gtk_widget_has_focus(m_pWidget);
+ }
+
+ virtual bool is_active() const override
+ {
+ GtkWindow* pTopLevel = GTK_WINDOW(gtk_widget_get_toplevel(m_pWidget));
+ return pTopLevel && gtk_window_is_active(pTopLevel) && has_focus();
+ }
+
+ virtual void set_has_default(bool has_default) override
+ {
+ g_object_set(G_OBJECT(m_pWidget), "has-default", has_default, nullptr);
+ }
+
+ virtual bool get_has_default() const override
+ {
+ gboolean has_default(false);
+ g_object_get(G_OBJECT(m_pWidget), "has-default", &has_default, nullptr);
+ return has_default;
+ }
+
+ 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
+ {
+ PangoContext* pContext = gtk_widget_get_pango_context(m_pWidget);
+ return pango_to_vcl(pango_context_get_font_description(pContext),
+ Application::GetSettings().GetUILanguageTag().getLocale());
+ }
+
+ virtual void set_grid_left_attach(int nAttach) override
+ {
+ GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
+ gtk_container_child_set(pParent, m_pWidget, "left-attach", nAttach, nullptr);
+ }
+
+ virtual int get_grid_left_attach() const override
+ {
+ GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
+ gint nAttach(0);
+ gtk_container_child_get(pParent, m_pWidget, "left-attach", &nAttach, nullptr);
+ return nAttach;
+ }
+
+ virtual void set_grid_width(int nCols) override
+ {
+ GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
+ gtk_container_child_set(pParent, m_pWidget, "width", nCols, nullptr);
+ }
+
+ virtual void set_grid_top_attach(int nAttach) override
+ {
+ GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
+ gtk_container_child_set(pParent, m_pWidget, "top-attach", nAttach, nullptr);
+ }
+
+ virtual int get_grid_top_attach() const override
+ {
+ GtkContainer* pParent = GTK_CONTAINER(gtk_widget_get_parent(m_pWidget));
+ gint nAttach(0);
+ gtk_container_child_get(pParent, m_pWidget, "top-attach", &nAttach, nullptr);
+ 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_secondary(bool bSecondary) override
+ {
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (pParent && GTK_IS_BUTTON_BOX(pParent))
+ gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(pParent), m_pWidget, bSecondary);
+ }
+
+ 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_left(int nMargin) override
+ {
+ gtk_widget_set_margin_left(m_pWidget, nMargin);
+ }
+
+ virtual void set_margin_right(int nMargin) override
+ {
+ gtk_widget_set_margin_right(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_left() const override
+ {
+ return gtk_widget_get_margin_left(m_pWidget);
+ }
+
+ virtual int get_margin_right() const override
+ {
+ return gtk_widget_get_margin_right(m_pWidget);
+ }
+
+ virtual void set_accessible_name(const OUString& rName) override
+ {
+ AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
+ if (!pAtkObject)
+ return;
+ atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ virtual OUString get_accessible_name() const override
+ {
+ 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);
+ }
+
+ virtual OUString get_accessible_description() const override
+ {
+ 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);
+ }
+
+ virtual void set_accessible_relation_labeled_by(weld::Widget* pLabel) override
+ {
+ AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
+ if (!pAtkObject)
+ return;
+ AtkObject *pAtkLabel = pLabel ? gtk_widget_get_accessible(dynamic_cast<GtkInstanceWidget&>(*pLabel).getWidget()) : 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)
+ atk_relation_set_remove(pRelationSet, pRelation);
+ if (pAtkLabel)
+ {
+ AtkObject *obj_array[1];
+ obj_array[0] = pAtkLabel;
+ pRelation = atk_relation_new(obj_array, 1, ATK_RELATION_LABELLED_BY);
+ atk_relation_set_add(pRelationSet, pRelation);
+ }
+ g_object_unref(pRelationSet);
+ }
+
+ virtual void set_accessible_relation_label_for(weld::Widget* pLabeled) override
+ {
+ AtkObject* pAtkObject = gtk_widget_get_accessible(m_pWidget);
+ if (!pAtkObject)
+ return;
+ AtkObject *pAtkLabeled = pLabeled ? gtk_widget_get_accessible(dynamic_cast<GtkInstanceWidget&>(*pLabeled).getWidget()) : nullptr;
+ AtkRelationSet *pRelationSet = atk_object_ref_relation_set(pAtkObject);
+ AtkRelation *pRelation = atk_relation_set_get_relation_by_type(pRelationSet, ATK_RELATION_LABEL_FOR);
+ if (pRelation)
+ atk_relation_set_remove(pRelationSet, pRelation);
+ if (pAtkLabeled)
+ {
+ AtkObject *obj_array[1];
+ obj_array[0] = pAtkLabeled;
+ pRelation = atk_relation_new(obj_array, 1, ATK_RELATION_LABEL_FOR);
+ atk_relation_set_add(pRelationSet, pRelation);
+ }
+ g_object_unref(pRelationSet);
+ }
+
+ virtual bool get_extents_relative_to(weld::Widget& rRelative, int& x, int &y, int& width, int &height) 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
+ bool ret = gtk_widget_translate_coordinates(m_pWidget,
+ dynamic_cast<GtkInstanceWidget&>(rRelative).getWidget(),
+ 0, 0, &x, &y);
+ 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
+ {
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(m_pWidget));
+ return OString(pStr, pStr ? strlen(pStr) : 0);
+ }
+
+ 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()
+ {
+ return m_pWidget;
+ }
+
+ GtkWindow* getWindow()
+ {
+ return GTK_WINDOW(gtk_widget_get_toplevel(m_pWidget));
+ }
+
+ virtual void connect_focus_in(const Link<Widget&, void>& rLink) override
+ {
+ if (!m_nFocusInSignalId)
+ m_nFocusInSignalId = g_signal_connect(m_pWidget, "focus-in-event", G_CALLBACK(signalFocusIn), this);
+ 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)
+ m_nFocusOutSignalId = g_signal_connect(m_pWidget, "focus-out-event", G_CALLBACK(signalFocusOut), this);
+ 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));
+ }
+
+ bool signal_key(const GdkEventKey* pEvent)
+ {
+ if (pEvent->type == GDK_KEY_PRESS && m_aKeyPressHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ return m_aKeyPressHdl.Call(GtkToVcl(*pEvent));
+ }
+ if (pEvent->type == GDK_KEY_RELEASE && m_aKeyReleaseHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ return m_aKeyReleaseHdl.Call(GtkToVcl(*pEvent));
+ }
+ return false;
+ }
+
+ virtual void grab_add() override
+ {
+ gtk_grab_add(m_pWidget);
+ }
+
+ virtual bool has_grab() const override
+ {
+ return gtk_widget_has_grab(m_pWidget);
+ }
+
+ virtual void grab_remove() override
+ {
+ gtk_grab_remove(m_pWidget);
+ }
+
+ 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
+ {
+ gtk_widget_freeze_child_notify(m_pWidget);
+ }
+
+ virtual void thaw() override
+ {
+ gtk_widget_thaw_child_notify(m_pWidget);
+ }
+
+ virtual css::uno::Reference<css::datatransfer::dnd::XDropTarget> get_drop_target() override
+ {
+ if (!m_xDropTarget)
+ {
+ m_xDropTarget.set(new GtkDropTarget);
+ 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);
+ }
+ return m_xDropTarget.get();
+ }
+
+ virtual void connect_get_property_tree(const Link<boost::property_tree::ptree&, void>& /*rLink*/) override
+ {
+ //not implemented for the gtk variant
+ }
+
+ virtual void set_stack_background() override
+ {
+ OUString sColor = Application::GetSettings().GetStyleSettings().GetWindowColor().AsRGBHexString();
+ set_background(&sColor);
+ }
+
+ virtual void set_highlight_background() override
+ {
+ OUString sColor = Application::GetSettings().GetStyleSettings().GetHighlightColor().AsRGBHexString();
+ set_background(&sColor);
+ }
+
+ virtual void set_toolbar_background() override
+ {
+ // no-op
+ }
+
+ virtual ~GtkInstanceWidget() override
+ {
+ if (m_pFocusInEvent)
+ Application::RemoveUserEvent(m_pFocusInEvent);
+ if (m_pFocusOutEvent)
+ Application::RemoveUserEvent(m_pFocusOutEvent);
+ 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)
+ g_signal_handler_disconnect(m_pWidget, m_nDragEndSignalId);
+ if (m_nDragBeginSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nDragBeginSignalId);
+ 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)
+ g_signal_handler_disconnect(m_pWidget, m_nKeyPressSignalId);
+ if (m_nKeyReleaseSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nKeyReleaseSignalId);
+ if (m_nButtonPressSignalId)
+ g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonPressSignalId);
+ if (m_nMotionSignalId)
+ g_signal_handler_disconnect(m_pMouseEventBox, m_nMotionSignalId);
+ if (m_nLeaveSignalId)
+ g_signal_handler_disconnect(m_pMouseEventBox, m_nLeaveSignalId);
+ if (m_nEnterSignalId)
+ g_signal_handler_disconnect(m_pMouseEventBox, m_nEnterSignalId);
+ if (m_nButtonReleaseSignalId)
+ g_signal_handler_disconnect(m_pMouseEventBox, m_nButtonReleaseSignalId);
+ if (m_nFocusInSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nFocusInSignalId);
+ if (m_nMnemonicActivateSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nMnemonicActivateSignalId);
+ if (m_nFocusOutSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nFocusOutSignalId);
+ if (m_nSizeAllocateSignalId)
+ g_signal_handler_disconnect(m_pWidget, m_nSizeAllocateSignalId);
+
+ set_background(nullptr);
+
+ if (m_pMouseEventBox && m_pMouseEventBox != m_pWidget)
+ {
+ // 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);
+ g_object_unref(m_pWidget);
+ }
+
+ if (m_bTakeOwnership)
+ gtk_widget_destroy(m_pWidget);
+ else
+ g_object_unref(m_pWidget);
+ }
+
+ virtual void disable_notify_events()
+ {
+ if (m_nFocusInSignalId)
+ g_signal_handler_block(m_pWidget, m_nFocusInSignalId);
+ if (m_nMnemonicActivateSignalId)
+ g_signal_handler_block(m_pWidget, m_nMnemonicActivateSignalId);
+ if (m_nFocusOutSignalId)
+ g_signal_handler_block(m_pWidget, m_nFocusOutSignalId);
+ 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)
+ g_signal_handler_unblock(m_pWidget, m_nFocusOutSignalId);
+ if (m_nMnemonicActivateSignalId)
+ g_signal_handler_unblock(m_pWidget, m_nMnemonicActivateSignalId);
+ if (m_nFocusInSignalId)
+ g_signal_handler_unblock(m_pWidget, m_nFocusInSignalId);
+ }
+
+ 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(VirtualDevice& rOutput) 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 (!bAlreadyVisible)
+ gtk_widget_show(m_pWidget);
+
+ GtkAllocation allocation;
+
+ if (!bAlreadyRealized)
+ gtk_widget_realize(m_pWidget);
+
+ if (!bAlreadyMapped)
+ gtk_widget_map(m_pWidget);
+
+ if (GTK_IS_CONTAINER(m_pWidget))
+ gtk_container_resize_children(GTK_CONTAINER(m_pWidget));
+
+ gtk_widget_get_allocation(m_pWidget, &allocation);
+
+ rOutput.SetOutputSizePixel(Size(allocation.width, allocation.height));
+ cairo_surface_t* pSurface = get_underlying_cairo_surface(rOutput);
+ cairo_t* cr = cairo_create(pSurface);
+
+ gtk_widget_draw(m_pWidget, cr);
+
+ cairo_destroy(cr);
+
+ if (!bAlreadyVisible)
+ gtk_widget_hide(m_pWidget);
+ if (!bAlreadyMapped)
+ gtk_widget_unmap(m_pWidget);
+ if (!bAlreadyRealized)
+ gtk_widget_unrealize(m_pWidget);
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(GtkInstanceWidget, async_signal_focus_in, void*, void)
+{
+ m_pFocusInEvent = nullptr;
+ signal_focus_in();
+}
+
+IMPL_LINK_NOARG(GtkInstanceWidget, async_signal_focus_out, void*, void)
+{
+ m_pFocusOutEvent = nullptr;
+ signal_focus_out();
+}
+
+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);
+}
+
+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());
+ }
+
+ OUString get_label(GtkButton* pButton)
+ {
+ const gchar* pStr = gtk_button_get_label(pButton);
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ void set_label(GtkButton* pButton, const OUString& rText)
+ {
+ gtk_button_set_label(pButton, MapToGtkAccelerator(rText).getStr());
+ }
+
+ 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, const OUString& 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, const OUString& rText)
+ {
+ g_object_set(G_OBJECT(pMessageDialog), "text",
+ OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr(),
+ nullptr);
+ }
+
+ void set_secondary_text(GtkMessageDialog* pMessageDialog, const OUString& 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;
+ }
+
+ GdkPixbuf* load_icon_by_name_theme_lang(const OUString& rIconName, const OUString& rIconTheme, const OUString& rUILang)
+ {
+ auto xMemStm = ImageTree::get().getImageStream(rIconName, rIconTheme, rUILang);
+ if (!xMemStm)
+ return nullptr;
+ return load_icon_from_stream(*xMemStm);
+ }
+}
+
+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);
+
+ std::unique_ptr<SvMemoryStream> xMemStm(new SvMemoryStream);
+
+ css::uno::Sequence<css::beans::PropertyValue> aFilterData(1);
+ aFilterData[0].Name = "Compression";
+ // We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed.
+ aFilterData[0].Value <<= sal_Int32(1);
+
+ vcl::PNGWriter aWriter(aImage.GetBitmapEx(), &aFilterData);
+ aWriter.Write(*xMemStm);
+
+ return load_icon_from_stream(*xMemStm);
+ }
+
+ 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;
+ }
+
+ 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");
+
+ GError *error = nullptr;
+ GtkIconTheme *icon_theme = gtk_icon_theme_get_default();
+ pixbuf = gtk_icon_theme_load_icon(icon_theme, OUStringToOString(rIconName, RTL_TEXTENCODING_UTF8).getStr(),
+ 16, GTK_ICON_LOOKUP_USE_BUILTIN, &error);
+ }
+ else
+ {
+ const AllSettings& rSettings = Application::GetSettings();
+ pixbuf = load_icon_by_name_theme_lang(rIconName,
+ rSettings.GetStyleSettings().DetermineIconTheme(),
+ rSettings.GetUILanguageTag().getBcp47());
+ }
+
+ return pixbuf;
+ }
+
+ GtkWidget* image_new_from_virtual_device(const VirtualDevice& rImageSurface)
+ {
+ GtkWidget* pImage = nullptr;
+ if (gtk_check_version(3, 20, 0) == 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);
+ }
+ else
+ {
+ GdkPixbuf* pixbuf = getPixbuf(rImageSurface);
+ pImage = gtk_image_new_from_pixbuf(pixbuf);
+ g_object_unref(pixbuf);
+ }
+ return pImage;
+ }
+
+class MenuHelper
+{
+protected:
+ GtkMenu* m_pMenu;
+ bool m_bTakeOwnership;
+ std::map<OString, GtkMenuItem*> m_aMap;
+private:
+
+ 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_activate(pItem);
+ }
+
+ virtual void signal_activate(GtkMenuItem* pItem) = 0;
+
+public:
+ MenuHelper(GtkMenu* pMenu, bool bTakeOwnership)
+ : m_pMenu(pMenu)
+ , m_bTakeOwnership(bTakeOwnership)
+ {
+ if (!m_pMenu)
+ return;
+ gtk_container_foreach(GTK_CONTAINER(m_pMenu), collect, this);
+ }
+
+ void add_to_map(GtkMenuItem* pMenuItem)
+ {
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pMenuItem));
+ OString id(pStr, pStr ? strlen(pStr) : 0);
+ m_aMap[id] = pMenuItem;
+ g_signal_connect(pMenuItem, "activate", G_CALLBACK(signalActivate), this);
+ }
+
+ void remove_from_map(GtkMenuItem* pMenuItem)
+ {
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pMenuItem));
+ OString id(pStr, pStr ? strlen(pStr) : 0);
+ 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);
+ }
+
+ void insert_item(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, const VirtualDevice* pImageSurface,
+ TriState eCheckRadioFalse)
+ {
+ GtkWidget* pImage = nullptr;
+ if (pIconName && !pIconName->isEmpty())
+ {
+ GdkPixbuf* pixbuf = load_icon_by_name(*pIconName);
+ if (!pixbuf)
+ {
+ pImage = gtk_image_new_from_pixbuf(pixbuf);
+ g_object_unref(pixbuf);
+ }
+ }
+ else if (pImageSurface)
+ pImage = image_new_from_virtual_device(*pImageSurface);
+
+ GtkWidget *pItem;
+ if (pImage)
+ {
+ GtkWidget *pBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+ GtkWidget *pLabel = gtk_label_new(MapToGtkAccelerator(rStr).getStr());
+ pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new() : gtk_menu_item_new();
+ gtk_container_add(GTK_CONTAINER(pBox), pImage);
+ gtk_container_add(GTK_CONTAINER(pBox), pLabel);
+ gtk_container_add(GTK_CONTAINER(pItem), 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);
+
+ gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
+ 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);
+ }
+
+ void insert_separator(int pos, const OUString& rId)
+ {
+ GtkWidget* pItem = gtk_separator_menu_item_new();
+ gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
+ 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);
+ }
+
+ void remove_item(const OString& rIdent)
+ {
+ GtkMenuItem* pMenuItem = m_aMap[rIdent];
+ remove_from_map(pMenuItem);
+ gtk_widget_destroy(GTK_WIDGET(pMenuItem));
+ }
+
+ void set_item_sensitive(const OString& rIdent, bool bSensitive)
+ {
+ gtk_widget_set_sensitive(GTK_WIDGET(m_aMap[rIdent]), bSensitive);
+ }
+
+ void set_item_active(const OString& rIdent, bool bActive)
+ {
+ disable_item_notify_events();
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(m_aMap[rIdent]), bActive);
+ enable_item_notify_events();
+ }
+
+ bool get_item_active(const OString& rIdent) const
+ {
+ return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(m_aMap.find(rIdent)->second));
+ }
+
+ void set_item_label(const OString& rIdent, const OUString& rText)
+ {
+ gtk_menu_item_set_label(m_aMap[rIdent], MapToGtkAccelerator(rText).getStr());
+ }
+
+ OUString get_item_label(const OString& rIdent) const
+ {
+ const gchar* pText = gtk_menu_item_get_label(m_aMap.find(rIdent)->second);
+ return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ void set_item_help_id(const OString& rIdent, const OString& rHelpId)
+ {
+ set_help_id(GTK_WIDGET(m_aMap[rIdent]), rHelpId);
+ }
+
+ OString get_item_help_id(const OString& rIdent) const
+ {
+ return get_help_id(GTK_WIDGET(m_aMap.find(rIdent)->second));
+ }
+
+ void set_item_visible(const OString& rIdent, bool bShow)
+ {
+ GtkWidget* pWidget = GTK_WIDGET(m_aMap[rIdent]);
+ if (bShow)
+ gtk_widget_show(pWidget);
+ else
+ gtk_widget_hide(pWidget);
+ }
+
+ void clear_items()
+ {
+ 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();
+ }
+
+ GtkMenu* getMenu() const
+ {
+ return m_pMenu;
+ }
+
+ virtual ~MenuHelper()
+ {
+ for (auto& a : m_aMap)
+ g_signal_handlers_disconnect_by_data(a.second, this);
+ if (m_bTakeOwnership)
+ gtk_widget_destroy(GTK_WIDGET(m_pMenu));
+ }
+};
+
+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.SetPriority(TaskPriority::RESIZE);
+ maLayoutIdle.SetInvokeHandler( LINK( this, ChildFrame, ImplHandleLayoutTimerHdl ) );
+ maLayoutIdle.SetDebugName( "ChildFrame maLayoutIdle" );
+ }
+
+ 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();
+ }
+
+ virtual void Resize() override
+ {
+ WorkWindow::Resize();
+ queue_resize();
+ }
+};
+
+IMPL_LINK_NOARG(ChildFrame, ImplHandleLayoutTimerHdl, Timer*, void)
+{
+ if (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild))
+ pChild->SetPosSizePixel(Point(0, 0), GetSizePixel());
+}
+
+class GtkInstanceContainer : public GtkInstanceWidget, public virtual weld::Container
+{
+private:
+ GtkContainer* m_pContainer;
+
+ 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);
+ }
+
+public:
+ GtkInstanceContainer(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pContainer), pBuilder, bTakeOwnership)
+ , m_pContainer(pContainer)
+ {
+ }
+
+ GtkContainer* getContainer() { return m_pContainer; }
+
+ 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);
+ gtk_container_remove(getContainer(), pChild);
+
+ GtkInstanceContainer* pNewGtkParent = dynamic_cast<GtkInstanceContainer*>(pNewParent);
+ assert(!pNewParent || pNewGtkParent);
+ if (pNewGtkParent)
+ gtk_container_add(pNewGtkParent->getContainer(), pChild);
+ g_object_unref(pChild);
+ }
+
+ virtual void recursively_unset_default_buttons() override
+ {
+ implResetDefault(GTK_WIDGET(m_pContainer), nullptr);
+ }
+
+ 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);
+ gtk_container_remove(GTK_CONTAINER(pParent), pWindow);
+ gtk_container_add(m_pContainer, pWindow);
+ gtk_container_child_set(m_pContainer, pWindow, "expand", true, "fill", true, nullptr);
+ 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;
+ }
+};
+
+}
+
+std::unique_ptr<weld::Container> GtkInstanceWidget::weld_parent() const
+{
+ GtkWidget* pParent = gtk_widget_get_parent(m_pWidget);
+ if (!pParent)
+ return nullptr;
+ return std::make_unique<GtkInstanceContainer>(GTK_CONTAINER(pParent), m_pBuilder, false);
+}
+
+namespace {
+
+class GtkInstanceBox : public GtkInstanceContainer, public virtual weld::Box
+{
+private:
+ GtkBox* m_pBox;
+
+public:
+ GtkInstanceBox(GtkBox* pBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceContainer(GTK_CONTAINER(pBox), pBuilder, bTakeOwnership)
+ , 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();
+ gtk_box_reorder_child(m_pBox, pChild, nNewPosition);
+ }
+};
+
+ void set_cursor(GtkWidget* pWidget, const char *pName)
+ {
+ if (!gtk_widget_get_realized(pWidget))
+ gtk_widget_realize(pWidget);
+ GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
+ GdkCursor *pCursor = pName ? gdk_cursor_new_from_name(pDisplay, pName) : nullptr;
+ gdk_window_set_cursor(gtk_widget_get_window(pWidget), pCursor);
+ gdk_display_flush(pDisplay);
+ if (pCursor)
+ g_object_unref(pCursor);
+ }
+}
+
+namespace
+{
+ struct ButtonOrder
+ {
+ const char * m_aType;
+ int m_nPriority;
+ };
+
+ int getButtonPriority(const OString &rType)
+ {
+ static const size_t N_TYPES = 7;
+ static const ButtonOrder aDiscardCancelSave[N_TYPES] =
+ {
+ { "/discard", 0 },
+ { "/cancel", 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 }
+ };
+
+ 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.endsWith(pOrder->m_aType))
+ return pOrder->m_nPriority;
+ }
+
+ return -1;
+ }
+
+ bool sortButtons(const GtkWidget* pA, const GtkWidget* pB)
+ {
+ //order within groups according to platform rules
+ return getButtonPriority(::get_help_id(pA)) < getButtonPriority(::get_help_id(pB));
+ }
+
+ void sort_native_button_order(GtkBox* pContainer)
+ {
+ std::vector<GtkWidget*> aChildren;
+ 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);
+
+ //sort child order within parent so that we match the platform button order
+ std::stable_sort(aChildren.begin(), aChildren.end(), sortButtons);
+
+ for (size_t pos = 0; pos < aChildren.size(); ++pos)
+ gtk_box_reorder_child(pContainer, aChildren[pos], pos);
+ }
+
+ Point get_csd_offset(GtkWidget* pTopLevel)
+ {
+ // try and omit drawing CSD under wayland
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pTopLevel));
+ GList* pChild = g_list_first(pChildren);
+
+ int x, y;
+ gtk_widget_translate_coordinates(GTK_WIDGET(pChild->data),
+ GTK_WIDGET(pTopLevel),
+ 0, 0, &x, &y);
+
+ int innerborder = gtk_container_get_border_width(GTK_CONTAINER(pChild->data));
+ g_list_free(pChildren);
+
+ int outerborder = gtk_container_get_border_width(GTK_CONTAINER(pTopLevel));
+ int totalborder = outerborder + innerborder;
+ x -= totalborder;
+ y -= totalborder;
+
+ return Point(x, y);
+ }
+
+ void do_collect_screenshot_data(GtkWidget* pItem, gpointer data)
+ {
+ GtkWidget* pTopLevel = gtk_widget_get_toplevel(pItem);
+
+ int 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_IS_CONTAINER(pItem))
+ gtk_container_forall(GTK_CONTAINER(pItem), do_collect_screenshot_data, data);
+ }
+
+ tools::Rectangle get_monitor_workarea(GtkWidget* pWindow)
+ {
+ GdkScreen* pScreen = gtk_widget_get_screen(pWindow);
+ gint nMonitor = gdk_screen_get_monitor_at_window(pScreen, gtk_widget_get_window(pWindow));
+ GdkRectangle aRect;
+ gdk_screen_get_monitor_workarea(pScreen, nMonitor, &aRect);
+ 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
+ gulong m_nToplevelFocusChangedSignalId;
+
+ static gboolean help_pressed(GtkAccelGroup*, GObject*, guint, GdkModifierType, gpointer widget)
+ {
+ GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
+ pThis->help();
+ return true;
+ }
+
+ static void signalToplevelFocusChanged(GtkWindow*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceWindow* pThis = static_cast<GtkInstanceWindow*>(widget);
+ pThis->signal_toplevel_focus_changed();
+ }
+
+protected:
+ void help();
+public:
+ GtkInstanceWindow(GtkWindow* pWindow, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceContainer(GTK_CONTAINER(pWindow), pBuilder, bTakeOwnership)
+ , m_pWindow(pWindow)
+ , m_nToplevelFocusChangedSignalId(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);
+ }
+ }
+
+ 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 css::uno::Reference<css::awt::XWindow>(m_xWindow.get());
+ }
+
+ virtual void set_busy_cursor(bool bBusy) override
+ {
+ set_cursor(m_pWidget, bBusy ? "progress" : nullptr);
+ }
+
+ 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
+ {
+ gtk_window_resize(m_pWindow, 1, 1);
+ }
+
+ virtual void window_move(int x, int y) override
+ {
+ gtk_window_move(m_pWindow, x, y);
+ }
+
+ virtual SystemEnvData get_system_data() const override
+ {
+ assert(false && "nothing should call this impl, yet anyway, if ever");
+ return SystemEnvData();
+ }
+
+ virtual Size get_size() const override
+ {
+ int current_width, current_height;
+ gtk_window_get_size(m_pWindow, &current_width, &current_height);
+ return Size(current_width, current_height);
+ }
+
+ virtual Point get_position() const override
+ {
+ int current_x, current_y;
+ gtk_window_get_position(m_pWindow, &current_x, &current_y);
+ return Point(current_x, current_y);
+ }
+
+ 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 (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);
+ }
+
+ virtual bool get_resizable() const override
+ {
+ return gtk_window_get_resizable(m_pWindow);
+ }
+
+ virtual bool has_toplevel_focus() const override
+ {
+ return gtk_window_has_toplevel_focus(m_pWindow);
+ }
+
+ virtual void present() override
+ {
+ gtk_window_present(m_pWindow);
+ }
+
+ 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);
+ }
+ }
+
+ virtual OString get_window_state(WindowStateMask nMask) const override
+ {
+ bool bPositioningAllowed = true;
+#if defined(GDK_WINDOWING_WAYLAND)
+ // drop x/y when under wayland
+ GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
+ bPositioningAllowed = !DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay);
+#endif
+
+ 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_toplevel_focus_changed(const Link<weld::Widget&, void>& rLink) override
+ {
+ assert(!m_nToplevelFocusChangedSignalId);
+ m_nToplevelFocusChangedSignalId = g_signal_connect(m_pWindow, "notify::has-toplevel-focus", G_CALLBACK(signalToplevelFocusChanged), this);
+ weld::Window::connect_toplevel_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 void draw(VirtualDevice& rOutput) 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 (!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));
+ }
+
+ if (!bAlreadyRealized)
+ {
+ GtkAllocation allocation;
+ gtk_widget_realize(GTK_WIDGET(m_pWindow));
+ gtk_widget_get_allocation(GTK_WIDGET(m_pWindow), &allocation);
+ gtk_widget_size_allocate(GTK_WIDGET(m_pWindow), &allocation);
+ }
+
+ rOutput.SetOutputSizePixel(get_size());
+ cairo_surface_t* pSurface = get_underlying_cairo_surface(rOutput);
+ cairo_t* cr = cairo_create(pSurface);
+
+ Point aOffset = get_csd_offset(GTK_WIDGET(m_pWindow));
+
+#if defined(GDK_WINDOWING_X11)
+ GdkDisplay *pDisplay = gtk_widget_get_display(GTK_WIDGET(m_pWindow));
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ assert(aOffset.X() == 0 && aOffset.Y() == 0 && "expected offset of 0 under X");
+#endif
+
+ cairo_translate(cr, -aOffset.X(), -aOffset.Y());
+
+ gtk_widget_draw(GTK_WIDGET(m_pWindow), cr);
+
+ cairo_destroy(cr);
+
+ if (!bAlreadyVisible)
+ gtk_widget_hide(GTK_WIDGET(m_pWindow));
+ if (!bAlreadyRealized)
+ gtk_widget_unrealize(GTK_WIDGET(m_pWindow));
+ }
+
+ virtual weld::ScreenShotCollection collect_screenshot_data() override
+ {
+ weld::ScreenShotCollection aRet;
+
+ gtk_container_foreach(GTK_CONTAINER(m_pWindow), do_collect_screenshot_data, &aRet);
+
+ 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);
+
+ 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 */
+ }
+
+ 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;
+ gulong nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signal_delete), this);
+ 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;
+
+ gdk_threads_leave();
+ g_main_loop_run(m_pLoop);
+ gdk_threads_enter();
+
+ 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);
+ g_signal_handler_disconnect(m_pDialog, nSignalDeleteId);
+ 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
+{
+ void hideUnless(GtkContainer *pTop, const winset& rVisibleWidgets,
+ std::vector<GtkWidget*> &rWasVisibleWidgets)
+ {
+ GList* pChildren = gtk_container_get_children(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(GTK_CONTAINER(pChild), rVisibleWidgets, rWasVisibleWidgets);
+ }
+ }
+ g_list_free(pChildren);
+ }
+
+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
+ int m_nOldBorderWidth; // border width for expanded dialog
+
+ 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);
+ }
+
+ 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 */
+ }
+
+ 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);
+
+ static void signalActivate(GtkMenuItem*, gpointer data)
+ {
+ bool* pActivate = static_cast<bool*>(data);
+ *pActivate = true;
+ }
+
+ bool signal_screenshot_popup_menu(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))
+ {
+ gdk_threads_leave();
+ g_main_loop_run(pLoop);
+ gdk_threads_enter();
+ }
+
+ 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;
+ }
+
+ static gboolean signalScreenshotPopupMenu(GtkWidget*, gpointer widget)
+ {
+ GtkInstanceDialog* pThis = static_cast<GtkInstanceDialog*>(widget);
+ return pThis->signal_screenshot_popup_menu(nullptr);
+ }
+
+ 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;
+ }
+
+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)
+ , m_nOldBorderWidth(0)
+ {
+ 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);
+ g_signal_connect(m_pDialog, "button-press-event", G_CALLBACK(signalScreenshotButton), this);
+ }
+ }
+
+ 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;
+ m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);
+
+ 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;
+ m_nSignalDeleteId = g_signal_connect(m_pDialog, "delete-event", G_CALLBACK(signalAsyncDelete), this);
+
+ 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_IS_DIALOG(m_pDialog))
+ sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
+ 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
+ {
+ return new GtkInstanceContainer(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(m_pDialog))), m_pBuilder, false);
+ }
+
+ 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);
+ }
+ //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(GTK_CONTAINER(pContentArea), aVisibleWidgets, m_aHiddenWidgets);
+
+ gtk_widget_set_size_request(pRefEdit, m_nOldEditWidth, -1);
+ 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);
+
+ // 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
+#if defined(GDK_WINDOWING_WAYLAND)
+ bool bWorkaroundSizeSpringingBack = DLSYM_GDK_IS_WAYLAND_DISPLAY(gtk_widget_get_display(m_pWidget));
+ if (bWorkaroundSizeSpringingBack)
+ gtk_widget_unmap(GTK_WIDGET(m_pDialog));
+#endif
+
+ resize_to_request();
+
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (bWorkaroundSizeSpringingBack)
+ gtk_widget_map(GTK_WIDGET(m_pDialog));
+#endif
+
+ 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;
+ 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);
+ 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
+ {
+ return new GtkInstanceContainer(GTK_CONTAINER(gtk_message_dialog_get_message_area(m_pMessageDialog)), m_pBuilder, false);
+ }
+};
+
+class GtkInstanceAssistant : public GtkInstanceDialog, public virtual weld::Assistant
+{
+private:
+ GtkAssistant* m_pAssistant;
+ GtkWidget* m_pSidebar;
+ GtkWidget* m_pSidebarEventBox;
+ GtkButtonBox* m_pButtonBox;
+ 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(const OString& rIdent) 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);
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pPage));
+ if (g_strcmp0(pStr, rIdent.getStr()) == 0)
+ return i;
+ }
+ return -1;
+ }
+
+ static void wrap_sidebar_label(GtkWidget *pWidget, gpointer /*user_data*/)
+ {
+ if (GTK_IS_LABEL(pWidget))
+ {
+ gtk_label_set_line_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)
+ {
+ if (g_strcmp0(gtk_buildable_get_name(GTK_BUILDABLE(pWidget)), "sidebar") == 0)
+ {
+ GtkWidget **ppSidebar = static_cast<GtkWidget**>(user_data);
+ *ppSidebar = pWidget;
+ }
+ if (GTK_IS_CONTAINER(pWidget))
+ gtk_container_forall(GTK_CONTAINER(pWidget), find_sidebar, user_data);
+ }
+
+ static void signalHelpClicked(GtkButton*, gpointer widget)
+ {
+ GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
+ pThis->signal_help_clicked();
+ }
+
+ void signal_help_clicked()
+ {
+ help();
+ }
+
+ static gboolean signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstanceAssistant* pThis = static_cast<GtkInstanceAssistant*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_button(pEvent);
+ }
+
+ bool signal_button(GdkEventButton* pEvent)
+ {
+ int nNewCurrentPage = -1;
+
+ GtkAllocation allocation;
+
+ int nPageIndex = 0;
+ 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);
+ if (!gtk_widget_get_visible(pWidget))
+ continue;
+
+ gtk_widget_get_allocation(pWidget, &allocation);
+
+ gint dest_x1, dest_y1;
+ gtk_widget_translate_coordinates(pWidget,
+ m_pSidebarEventBox,
+ 0,
+ 0,
+ &dest_x1,
+ &dest_y1);
+
+ gint dest_x2, dest_y2;
+ gtk_widget_translate_coordinates(pWidget,
+ m_pSidebarEventBox,
+ allocation.width,
+ allocation.height,
+ &dest_x2,
+ &dest_y2);
+
+
+ if (pEvent->x >= dest_x1 && pEvent->x <= dest_x2 && pEvent->y >= dest_y1 && pEvent->y <= dest_y2)
+ {
+ nNewCurrentPage = nPageIndex;
+ break;
+ }
+
+ ++nPageIndex;
+ }
+ g_list_free(pChildren);
+
+ 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)
+ {
+ 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);
+
+ m_pBack = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Back)).getStr()));
+ gtk_widget_set_can_default(GTK_WIDGET(m_pBack), true);
+ gtk_buildable_set_name(GTK_BUILDABLE(m_pBack), "previous");
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pBack), false, false, 0);
+
+ m_pNext = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Next)).getStr()));
+ gtk_widget_set_can_default(GTK_WIDGET(m_pNext), true);
+ gtk_buildable_set_name(GTK_BUILDABLE(m_pNext), "next");
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pNext), false, false, 0);
+
+ m_pCancel = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Cancel)).getStr()));
+ gtk_widget_set_can_default(GTK_WIDGET(m_pCancel), true);
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pCancel), false, false, 0);
+
+ m_pFinish = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Finish)).getStr()));
+ gtk_widget_set_can_default(GTK_WIDGET(m_pFinish), true);
+ gtk_buildable_set_name(GTK_BUILDABLE(m_pFinish), "finish");
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pFinish), false, false, 0);
+
+ m_pHelp = GTK_BUTTON(gtk_button_new_with_mnemonic(MapToGtkAccelerator(GetStandardText(StandardButtonType::Help)).getStr()));
+ gtk_widget_set_can_default(GTK_WIDGET(m_pHelp), true);
+ g_signal_connect(m_pHelp, "clicked", G_CALLBACK(signalHelpClicked), this);
+ gtk_box_pack_end(GTK_BOX(m_pButtonBox), GTK_WIDGET(m_pHelp), false, false, 0);
+
+ gtk_assistant_add_action_widget(pAssistant, GTK_WIDGET(m_pButtonBox));
+ gtk_button_box_set_child_secondary(m_pButtonBox, GTK_WIDGET(m_pHelp), true);
+ gtk_widget_set_hexpand(GTK_WIDGET(m_pButtonBox), true);
+
+ GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pButtonBox));
+ gtk_container_child_set(GTK_CONTAINER(pParent), GTK_WIDGET(m_pButtonBox), "expand", true, "fill", true, nullptr);
+ 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
+ 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);
+
+ gtk_widget_show_all(GTK_WIDGET(m_pButtonBox));
+
+ find_sidebar(GTK_WIDGET(m_pAssistant), &m_pSidebar);
+
+ m_pSidebarEventBox = ::ensureEventWidget(m_pSidebar);
+ m_nButtonPressSignalId = m_pSidebarEventBox ? g_signal_connect(m_pSidebarEventBox, "button-press-event", G_CALLBACK(signalButton), this) : 0;
+ }
+
+ 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);
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pWidget));
+ return OString(pStr, pStr ? strlen(pStr) : 0);
+ }
+
+ 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());
+ gtk_container_forall(GTK_CONTAINER(m_pSidebar), wrap_sidebar_label, nullptr);
+ }
+
+ 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());
+ gtk_container_forall(GTK_CONTAINER(m_pSidebar), wrap_sidebar_label, nullptr);
+ g_object_unref(pPage);
+ }
+
+ virtual weld::Container* append_page(const OString& rIdent) override
+ {
+ disable_notify_events();
+
+ GtkWidget *pChild = gtk_grid_new();
+ gtk_buildable_set_name(GTK_BUILDABLE(pChild), rIdent.getStr());
+ 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();
+
+ m_aPages.emplace_back(new GtkInstanceContainer(GTK_CONTAINER(pChild), m_pBuilder, false));
+
+ 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)
+ g_signal_handler_disconnect(m_pSidebarEventBox, m_nButtonPressSignalId);
+ }
+};
+
+class GtkInstanceFrame : public GtkInstanceContainer, public virtual weld::Frame
+{
+private:
+ GtkFrame* m_pFrame;
+public:
+ GtkInstanceFrame(GtkFrame* pFrame, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceContainer(GTK_CONTAINER(pFrame), pBuilder, bTakeOwnership)
+ , 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)
+ : GtkInstanceContainer(GTK_CONTAINER(pPaned), pBuilder, bTakeOwnership)
+ , 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 crippled_viewport_get_type();
+
+#define CRIPPLED_TYPE_VIEWPORT (crippled_viewport_get_type ())
+#define CRIPPLED_VIEWPORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CRIPPLED_TYPE_VIEWPORT, CrippledViewport))
+#ifndef NDEBUG
+# define CRIPPLED_IS_VIEWPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CRIPPLED_TYPE_VIEWPORT))
+#endif
+
+namespace {
+
+struct CrippledViewport
+{
+ GtkViewport viewport;
+
+ GtkAdjustment *hadjustment;
+ GtkAdjustment *vadjustment;
+};
+
+}
+
+enum
+{
+ PROP_0,
+ PROP_HADJUSTMENT,
+ PROP_VADJUSTMENT,
+ PROP_HSCROLL_POLICY,
+ PROP_VSCROLL_POLICY,
+ PROP_SHADOW_TYPE
+};
+
+static void viewport_set_adjustment(CrippledViewport *viewport,
+ GtkOrientation orientation,
+ GtkAdjustment *adjustment)
+{
+ if (!adjustment)
+ adjustment = gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (viewport->hadjustment)
+ g_object_unref(viewport->hadjustment);
+ viewport->hadjustment = adjustment;
+ }
+ else
+ {
+ if (viewport->vadjustment)
+ g_object_unref(viewport->vadjustment);
+ viewport->vadjustment = adjustment;
+ }
+
+ g_object_ref_sink(adjustment);
+}
+
+static void
+crippled_viewport_set_property(GObject* object,
+ guint prop_id,
+ const GValue* value,
+ GParamSpec* /*pspec*/)
+{
+ CrippledViewport *viewport = CRIPPLED_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
+crippled_viewport_get_property(GObject* object,
+ guint prop_id,
+ GValue* value,
+ GParamSpec* /*pspec*/)
+{
+ CrippledViewport *viewport = CRIPPLED_VIEWPORT(object);
+
+ switch (prop_id)
+ {
+ case PROP_HADJUSTMENT:
+ g_value_set_object(value, viewport->hadjustment);
+ break;
+ case PROP_VADJUSTMENT:
+ g_value_set_object(value, viewport->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 void crippled_viewport_class_init(GtkViewportClass *klass)
+{
+ GObjectClass* o_class = G_OBJECT_CLASS(klass);
+
+ /* GObject signals */
+ o_class->set_property = crippled_viewport_set_property;
+ o_class->get_property = crippled_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 crippled_viewport_get_type()
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ static const GTypeInfo tinfo =
+ {
+ sizeof (GtkViewportClass),
+ nullptr, /* base init */
+ nullptr, /* base finalize */
+ reinterpret_cast<GClassInitFunc>(crippled_viewport_class_init), /* class init */
+ nullptr, /* class finalize */
+ nullptr, /* class data */
+ sizeof (CrippledViewport), /* instance size */
+ 0, /* nb preallocs */
+ nullptr, /* instance init */
+ nullptr /* value table */
+ };
+
+ type = g_type_register_static( GTK_TYPE_VIEWPORT, "CrippledViewport",
+ &tinfo, GTypeFlags(0));
+ }
+
+ return type;
+}
+
+#define CUSTOM_TYPE_CELL_RENDERER_SURFACE (custom_cell_renderer_surface_get_type())
+#define CUSTOM_CELL_RENDERER_SURFACE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), CUSTOM_TYPE_CELL_RENDERER_SURFACE, CustomCellRendererSurface))
+#define CUSTOM_IS_CELL_RENDERER_SURFACE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CUSTOM_TYPE_CELL_RENDERER_SURFACE))
+
+namespace {
+
+ struct CustomCellRendererSurface
+ {
+ GtkCellRendererText parent;
+ VclPtr<VirtualDevice> device;
+ gchar *id;
+ gpointer instance;
+ };
+
+ struct CustomCellRendererSurfaceClass
+ {
+ GtkCellRendererTextClass parent_class;
+ };
+
+ enum
+ {
+ PROP_ID = 10000,
+ PROP_INSTANCE_TREE_VIEW = 10001
+ };
+}
+
+static gpointer custom_cell_renderer_surface_parent_class;
+
+static GType custom_cell_renderer_surface_get_type();
+static void custom_cell_renderer_surface_class_init(CustomCellRendererSurfaceClass *klass);
+
+GType custom_cell_renderer_surface_get_type()
+{
+ static GType type = 0;
+
+ if (!type)
+ {
+ static const GTypeInfo tinfo =
+ {
+ sizeof (CustomCellRendererSurfaceClass),
+ nullptr, /* base init */
+ nullptr, /* base finalize */
+ reinterpret_cast<GClassInitFunc>(custom_cell_renderer_surface_class_init), /* class init */
+ nullptr, /* class finalize */
+ nullptr, /* class data */
+ sizeof (CustomCellRendererSurface), /* instance size */
+ 0, /* nb preallocs */
+ nullptr, /* instance init */
+ nullptr /* value table */
+ };
+
+ // inherit from GtkCellRendererText so we can set the "text" property and get a11y support for that
+ type = g_type_register_static(GTK_TYPE_CELL_RENDERER_TEXT, "CustomCellRendererSurface",
+ &tinfo, GTypeFlags(0));
+ }
+
+ return type;
+}
+
+static void custom_cell_renderer_surface_get_property(GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CustomCellRendererSurface *cellsurface = CUSTOM_CELL_RENDERER_SURFACE(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_surface_parent_class)->get_property(object, param_id, value, pspec);
+ break;
+ }
+}
+
+static void custom_cell_renderer_surface_set_property(GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CustomCellRendererSurface *cellsurface = CUSTOM_CELL_RENDERER_SURFACE(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_surface_parent_class)->set_property(object, param_id, value, pspec);
+ break;
+ }
+}
+
+static bool custom_cell_renderer_surface_get_preferred_size(GtkCellRenderer *cell,
+ GtkOrientation orientation,
+ gint *minimum_size,
+ gint *natural_size);
+
+static void custom_cell_renderer_surface_render(GtkCellRenderer* cell,
+ cairo_t* cr,
+ GtkWidget* widget,
+ const GdkRectangle* background_area,
+ const GdkRectangle* cell_area,
+ GtkCellRendererState flags);
+
+static void custom_cell_renderer_surface_finalize(GObject *object)
+{
+ CustomCellRendererSurface *cellsurface = CUSTOM_CELL_RENDERER_SURFACE(object);
+
+ g_free(cellsurface->id);
+ cellsurface->device.disposeAndClear();
+
+ G_OBJECT_CLASS(custom_cell_renderer_surface_parent_class)->finalize(object);
+}
+
+static void custom_cell_renderer_surface_get_preferred_width(GtkCellRenderer *cell,
+ GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ if (!custom_cell_renderer_surface_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_surface_parent_class)->get_preferred_width(cell,
+ widget, minimum_size, natural_size);
+ }
+}
+
+static void custom_cell_renderer_surface_get_preferred_height(GtkCellRenderer *cell,
+ GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ if (!custom_cell_renderer_surface_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_surface_parent_class)->get_preferred_height(cell,
+ widget, minimum_size, natural_size);
+ }
+
+}
+
+static void custom_cell_renderer_surface_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_surface_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_surface_class_init(CustomCellRendererSurfaceClass *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_surface_get_property;
+ object_class->set_property = custom_cell_renderer_surface_set_property;
+
+ custom_cell_renderer_surface_parent_class = g_type_class_peek_parent(klass);
+ object_class->finalize = custom_cell_renderer_surface_finalize;
+
+ cell_class->get_preferred_width = custom_cell_renderer_surface_get_preferred_width;
+ cell_class->get_preferred_height = custom_cell_renderer_surface_get_preferred_height;
+ cell_class->get_preferred_width_for_height = custom_cell_renderer_surface_get_preferred_width_for_height;
+ cell_class->get_preferred_height_for_width = custom_cell_renderer_surface_get_preferred_height_for_width;
+
+ cell_class->render = custom_cell_renderer_surface_render;
+
+ 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));
+
+ gtk_cell_renderer_class_set_accessible_type(cell_class, GTK_TYPE_TEXT_CELL_ACCESSIBLE);
+}
+
+static GtkCellRenderer* custom_cell_renderer_surface_new()
+{
+ return GTK_CELL_RENDERER(g_object_new(CUSTOM_TYPE_CELL_RENDERER_SURFACE, nullptr));
+}
+
+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;
+ 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)
+ : GtkInstanceContainer(GTK_CONTAINER(pScrolledWindow), pBuilder, bTakeOwnership)
+ , m_pScrolledWindow(pScrolledWindow)
+ , m_pOrigViewport(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))
+ {
+ }
+
+ virtual void set_user_managed_scrolling() override
+ {
+ 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);
+ GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
+ assert(GTK_IS_VIEWPORT(pViewport));
+ GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
+ g_object_ref(pChild);
+ gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
+ g_object_ref(pViewport);
+ gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
+ GtkWidget* pNewViewport = GTK_WIDGET(g_object_new(crippled_viewport_get_type(), nullptr));
+ gtk_widget_show(pNewViewport);
+ gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), pNewViewport);
+ gtk_container_add(GTK_CONTAINER(pNewViewport), pChild);
+ 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, eGtkVPolicy, VclToGtk(eHPolicy));
+ }
+
+ virtual VclPolicyType get_hpolicy() const override
+ {
+ GtkPolicyType eGtkHPolicy;
+ gtk_scrolled_window_get_policy(m_pScrolledWindow, &eGtkHPolicy, nullptr);
+ return GtkToVcl(eGtkHPolicy);
+ }
+
+ virtual int get_hscroll_height() const override
+ {
+ if (gtk_scrolled_window_get_overlay_scrolling(m_pScrolledWindow))
+ return 0;
+ return gtk_widget_get_allocated_height(gtk_scrolled_window_get_hscrollbar(m_pScrolledWindow));
+ }
+
+ 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_vscroll_width() 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 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 ~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);
+
+ //put it back the way it was
+ if (m_pOrigViewport)
+ {
+ 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);
+
+ GtkWidget *pViewport = gtk_bin_get_child(GTK_BIN(m_pScrolledWindow));
+ assert(CRIPPLED_IS_VIEWPORT(pViewport));
+ GtkWidget *pChild = gtk_bin_get_child(GTK_BIN(pViewport));
+ g_object_ref(pChild);
+ gtk_container_remove(GTK_CONTAINER(pViewport), pChild);
+ g_object_ref(pViewport);
+ gtk_container_remove(GTK_CONTAINER(m_pScrolledWindow), pViewport);
+
+ gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), m_pOrigViewport);
+ g_object_unref(m_pOrigViewport);
+ gtk_container_add(GTK_CONTAINER(m_pOrigViewport), pChild);
+ g_object_unref(pChild);
+ gtk_widget_destroy(pViewport);
+ g_object_unref(pViewport);
+ m_pOrigViewport = nullptr;
+ GtkInstanceContainer::enable_notify_events();
+ }
+ }
+};
+
+class GtkInstanceNotebook : public GtkInstanceContainer, public virtual weld::Notebook
+{
+private:
+ GtkNotebook* m_pNotebook;
+ GtkBox* m_pOverFlowBox;
+ GtkNotebook* m_pOverFlowNotebook;
+ gulong m_nSwitchPageSignalId;
+ gulong m_nOverFlowSwitchPageSignalId;
+ gulong m_nSizeAllocateSignalId;
+ gulong m_nFocusSignalId;
+ 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();
+
+ 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));
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pTabWidget));
+ return OString(pStr, pStr ? strlen(pStr) : 0);
+ }
+
+ static gint get_page_number(GtkNotebook *pNotebook, const OString& rIdent)
+ {
+ 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));
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pTabWidget));
+ if (pStr && strcmp(pStr, rIdent.getStr()) == 0)
+ return i;
+ }
+ return -1;
+ }
+
+ int remove_page(GtkNotebook *pNotebook, const OString& rIdent)
+ {
+ disable_notify_events();
+ int nPageNumber = get_page_number(pNotebook, rIdent);
+ 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();
+ gtk_buildable_set_name(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(MapToGtkAccelerator(rLabel).getStr());
+ gtk_buildable_set_name(GTK_BUILDABLE(pTabWidget), rIdent.getStr());
+
+ gtk_notebook_insert_page(pNotebook, pChild, pTabWidget, nPos);
+ gtk_widget_show(pChild);
+ gtk_widget_show(pTabWidget);
+
+ enable_notify_events();
+ }
+
+ gint get_page_number(const OString& rIdent) const
+ {
+ 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;
+ }
+ }
+ }
+
+ 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));
+ gtk_container_add(GTK_CONTAINER(pParent), GTK_WIDGET(m_pOverFlowBox));
+ gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pOverFlowNotebook), false, false, 0);
+ g_object_ref(m_pNotebook);
+ gtk_container_remove(GTK_CONTAINER(pParent), GTK_WIDGET(m_pNotebook));
+ gtk_box_pack_start(m_pOverFlowBox, GTK_WIDGET(m_pNotebook), true, true, 0);
+ 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
+ gtk_notebook_set_scrollable(m_pNotebook, false);
+
+ gtk_widget_freeze_child_notify(GTK_WIDGET(m_pNotebook));
+ gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
+
+ 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");
+
+ gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
+ gtk_widget_thaw_child_notify(GTK_WIDGET(m_pNotebook));
+
+ 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_widget_get_child_visible(pTabWidget))
+ {
+ m_nLaunchSplitTimeoutId = g_timeout_add_full(G_PRIORITY_HIGH_IDLE, 0, reinterpret_cast<GSourceFunc>(launch_split_notebooks), this, nullptr);
+ break;
+ }
+ }
+ }
+ enable_notify_events();
+ }
+
+ static void signalSizeAllocate(GtkWidget*, GdkRectangle*, gpointer widget)
+ {
+ GtkInstanceNotebook* pThis = static_cast<GtkInstanceNotebook*>(widget);
+ pThis->signal_notebook_size_allocate();
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ // 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)
+ : GtkInstanceContainer(GTK_CONTAINER(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))
+ , m_nFocusSignalId(g_signal_connect(pNotebook, "focus", G_CALLBACK(signalFocus), this))
+ , 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)
+ {
+ gtk_widget_add_events(GTK_WIDGET(pNotebook), GDK_SCROLL_MASK);
+ if (get_n_pages() > 6)
+ m_nSizeAllocateSignalId = g_signal_connect_after(pNotebook, "size-allocate", G_CALLBACK(signalSizeAllocate), this);
+ else
+ m_nSizeAllocateSignalId = 0;
+ 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
+ GtkStyleContext *pNotebookContext = gtk_widget_get_style_context(GTK_WIDGET(m_pOverFlowNotebook));
+ GtkCssProvider *pProvider = gtk_css_provider_new();
+ static const gchar data[] = "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; }";
+ static const gchar olddata[] = "tab.top:active { 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; }";
+ gtk_css_provider_load_from_data(pProvider, gtk_check_version(3, 20, 0) == nullptr ? data : olddata, -1, nullptr);
+ gtk_style_context_add_provider(pNotebookContext, GTK_STYLE_PROVIDER(pProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+ 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 weld::Container* get_page(const OString& rIdent) const override
+ {
+ int nPage = get_page_number(rIdent);
+ if (nPage < 0)
+ return nullptr;
+
+ GtkContainer* pChild;
+ if (m_bOverFlowBoxIsStart)
+ {
+ auto nOverFlowLen = m_bOverFlowBoxActive ? gtk_notebook_get_n_pages(m_pOverFlowNotebook) - 1 : 0;
+ if (nPage < nOverFlowLen)
+ pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pOverFlowNotebook, nPage));
+ else
+ {
+ nPage -= nOverFlowLen;
+ pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pNotebook, nPage));
+ }
+ }
+ else
+ {
+ auto nMainLen = gtk_notebook_get_n_pages(m_pNotebook);
+ if (nPage < nMainLen)
+ pChild = GTK_CONTAINER(gtk_notebook_get_nth_page(m_pNotebook, nPage));
+ else
+ {
+ nPage -= nMainLen;
+ pChild = GTK_CONTAINER(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 (!m_aPages[nPageIndex])
+ m_aPages[nPageIndex].reset(new GtkInstanceContainer(pChild, m_pBuilder, false));
+ 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_number(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 disable_notify_events() override
+ {
+ g_signal_handler_block(m_pNotebook, m_nSwitchPageSignalId);
+ g_signal_handler_block(m_pNotebook, m_nFocusSignalId);
+ g_signal_handler_block(m_pNotebook, m_nChangeCurrentPageId);
+ g_signal_handler_block(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
+ gtk_widget_freeze_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::enable_notify_events();
+ gtk_widget_thaw_child_notify(GTK_WIDGET(m_pOverFlowNotebook));
+ g_signal_handler_unblock(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
+ g_signal_handler_unblock(m_pNotebook, m_nSwitchPageSignalId);
+ g_signal_handler_unblock(m_pNotebook, m_nFocusSignalId);
+ 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 (m_nSizeAllocateSignalId)
+ g_signal_handler_disconnect(m_pNotebook, m_nSizeAllocateSignalId);
+ g_signal_handler_disconnect(m_pNotebook, m_nSwitchPageSignalId);
+ g_signal_handler_disconnect(m_pNotebook, m_nFocusSignalId);
+ g_signal_handler_disconnect(m_pNotebook, m_nChangeCurrentPageId);
+ g_signal_handler_disconnect(m_pOverFlowNotebook, m_nOverFlowSwitchPageSignalId);
+ gtk_widget_destroy(GTK_WIDGET(m_pOverFlowNotebook));
+ if (m_pOverFlowBox)
+ {
+ // put it back to how we found it initially
+ GtkWidget* pParent = gtk_widget_get_parent(GTK_WIDGET(m_pOverFlowBox));
+ g_object_ref(m_pNotebook);
+ gtk_container_remove(GTK_CONTAINER(m_pOverFlowBox), GTK_WIDGET(m_pNotebook));
+ gtk_container_add(GTK_CONTAINER(pParent), GTK_WIDGET(m_pNotebook));
+ g_object_unref(m_pNotebook);
+
+ gtk_widget_destroy(GTK_WIDGET(m_pOverFlowBox));
+ }
+ }
+};
+
+class GtkInstanceButton : public GtkInstanceContainer, public virtual weld::Button
+{
+private:
+ GtkButton* m_pButton;
+ gulong m_nSignalId;
+
+ static void signalClicked(GtkButton*, gpointer widget)
+ {
+ GtkInstanceButton* pThis = static_cast<GtkInstanceButton*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_clicked();
+ }
+
+public:
+ GtkInstanceButton(GtkButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceContainer(GTK_CONTAINER(pButton), pBuilder, bTakeOwnership)
+ , m_pButton(pButton)
+ , m_nSignalId(g_signal_connect(pButton, "clicked", G_CALLBACK(signalClicked), this))
+ {
+ g_object_set_data(G_OBJECT(m_pButton), "g-lo-GtkInstanceButton", this);
+ }
+
+ virtual void set_label(const OUString& rText) override
+ {
+ ::set_label(m_pButton, rText);
+ }
+
+ virtual void set_image(VirtualDevice* pDevice) override
+ {
+ gtk_button_set_always_show_image(m_pButton, true);
+ gtk_button_set_image_position(m_pButton, GTK_POS_LEFT);
+ if (pDevice)
+ gtk_button_set_image(m_pButton, image_new_from_virtual_device(*pDevice));
+ else
+ gtk_button_set_image(m_pButton, nullptr);
+ }
+
+ virtual void set_from_icon_name(const OUString& rIconName) override
+ {
+ GdkPixbuf* pixbuf = load_icon_by_name(rIconName);
+ if (!pixbuf)
+ gtk_button_set_image(m_pButton, nullptr);
+ else
+ {
+ gtk_button_set_image(m_pButton, gtk_image_new_from_pixbuf(pixbuf));
+ g_object_unref(pixbuf);
+ }
+ }
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
+ {
+ GdkPixbuf* pixbuf = getPixbuf(rImage);
+ if (!pixbuf)
+ gtk_button_set_image(m_pButton, nullptr);
+ else
+ {
+ gtk_button_set_image(m_pButton, gtk_image_new_from_pixbuf(pixbuf));
+ g_object_unref(pixbuf);
+ }
+ }
+
+ virtual OUString get_label() const override
+ {
+ return ::get_label(m_pButton);
+ }
+
+ virtual void set_label_line_wrap(bool wrap) override
+ {
+ GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pButton));
+ gtk_label_set_line_wrap(GTK_LABEL(pChild), wrap);
+ }
+
+ // 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);
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::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)
+{
+ 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;
+
+ 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_IS_DIALOG(m_pDialog))
+ sort_native_button_order(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(m_pDialog))));
+ 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
+{
+private:
+ GtkToggleButton* m_pToggleButton;
+ gulong m_nSignalId;
+
+ 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_nSignalId(g_signal_connect(m_pToggleButton, "toggled", G_CALLBACK(signalToggled), this))
+ {
+ }
+
+ virtual void set_active(bool active) override
+ {
+ disable_notify_events();
+ gtk_toggle_button_set_inconsistent(m_pToggleButton, 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
+ {
+ gtk_toggle_button_set_inconsistent(m_pToggleButton, inconsistent);
+ }
+
+ virtual bool get_inconsistent() const override
+ {
+ return gtk_toggle_button_get_inconsistent(m_pToggleButton);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pToggleButton, m_nSignalId);
+ GtkInstanceButton::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceButton::enable_notify_events();
+ g_signal_handler_unblock(m_pToggleButton, m_nSignalId);
+ }
+
+ virtual ~GtkInstanceToggleButton() override
+ {
+ g_signal_handler_disconnect(m_pToggleButton, m_nSignalId);
+ }
+};
+
+void do_grab(GtkWidget* pWidget)
+{
+ GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
+#if GTK_CHECK_VERSION(3, 20, 0)
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
+ gdk_seat_grab(pSeat, gtk_widget_get_window(pWidget),
+ GDK_SEAT_CAPABILITY_ALL, true, nullptr, nullptr, nullptr, nullptr);
+ return;
+ }
+#endif
+ //else older gtk3
+ GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(pDisplay);
+ GdkDevice* pPointer = gdk_device_manager_get_client_pointer(pDeviceManager);
+ GdkWindow* pWindow = gtk_widget_get_window(pWidget);
+ guint32 nCurrentTime = gtk_get_current_event_time();
+ gdk_device_grab(pPointer, pWindow, GDK_OWNERSHIP_NONE, true, GDK_ALL_EVENTS_MASK, nullptr, nCurrentTime);
+ if (GdkDevice* pKeyboard = gdk_device_get_associated_device(pPointer))
+ gdk_device_grab(pKeyboard, pWindow, GDK_OWNERSHIP_NONE, true, GDK_ALL_EVENTS_MASK, nullptr, nCurrentTime);
+}
+
+void do_ungrab(GtkWidget* pWidget)
+{
+ GdkDisplay *pDisplay = gtk_widget_get_display(pWidget);
+#if GTK_CHECK_VERSION(3, 20, 0)
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ GdkSeat* pSeat = gdk_display_get_default_seat(pDisplay);
+ gdk_seat_ungrab(pSeat);
+ return;
+ }
+#endif
+ //else older gtk3
+ GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(pDisplay);
+ GdkDevice* pPointer = gdk_device_manager_get_client_pointer(pDeviceManager);
+ guint32 nCurrentTime = gtk_get_current_event_time();
+ gdk_device_ungrab(pPointer, nCurrentTime);
+ if (GdkDevice* pKeyboard = gdk_device_get_associated_device(pPointer))
+ gdk_device_ungrab(pKeyboard, nCurrentTime);
+}
+
+GtkPositionType show_menu_older_gtk(GtkWidget* pMenuButton, GtkWindow* pMenu)
+{
+ //place the toplevel just below its launcher button
+ GtkWidget* pToplevel = gtk_widget_get_toplevel(pMenuButton);
+ gint x, y, absx, absy;
+ gtk_widget_translate_coordinates(pMenuButton, pToplevel, 0, 0, &x, &y);
+ GdkWindow *pWindow = gtk_widget_get_window(pToplevel);
+ gdk_window_get_position(pWindow, &absx, &absy);
+
+ x += absx;
+ y += absy;
+
+ gint nButtonHeight = gtk_widget_get_allocated_height(pMenuButton);
+ y += nButtonHeight;
+
+ gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), pMenu);
+ gtk_window_set_transient_for(pMenu, GTK_WINDOW(pToplevel));
+
+ GtkRequisition req;
+ gtk_widget_get_preferred_size(GTK_WIDGET(pMenu), nullptr, &req);
+ gint nMenuWidth = req.width;
+ gint nMenuHeight = req.height;
+
+ 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);
+ gint endx = x + nMenuWidth;
+ if (endx > aWorkArea.Right())
+ {
+ x -= endx - aWorkArea.Right();
+ if (x < 0)
+ x = 0;
+ }
+
+ GtkPositionType ePosUsed = GTK_POS_BOTTOM;
+
+ gint endy = y + nMenuHeight;
+ gint nMissingBelow = endy - aWorkArea.Bottom();
+ if (nMissingBelow > 0)
+ {
+ gint nNewY = y - (nButtonHeight + nMenuHeight);
+ if (nNewY < aWorkArea.Top())
+ {
+ gint nMissingAbove = aWorkArea.Top() - nNewY;
+ 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
+ {
+ y = nNewY;
+ ePosUsed = GTK_POS_TOP;
+ }
+ }
+
+ gtk_window_move(pMenu, x, y);
+
+ return ePosUsed;
+}
+
+bool show_menu_newer_gtk(GtkWidget* pComboBox, GtkWindow* pMenu)
+{
+ 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;
+
+#if defined(GDK_WINDOWING_X11)
+ // 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;
+#endif
+
+ //place the toplevel just below its launcher button
+ GtkWidget* pToplevel = gtk_widget_get_toplevel(pComboBox);
+ gint x, y;
+ gtk_widget_translate_coordinates(pComboBox, pToplevel, 0, 0, &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));
+
+ gint nComboWidth = gtk_widget_get_allocated_width(pComboBox);
+ gint nComboHeight = gtk_widget_get_allocated_height(pComboBox);
+
+ bool bSwapForRTL = SwapForRTL(GTK_WIDGET(pComboBox));
+
+ GdkGravity rect_anchor = !bSwapForRTL ? GDK_GRAVITY_SOUTH_WEST : GDK_GRAVITY_SOUTH_EAST;
+ GdkGravity menu_anchor = !bSwapForRTL ? GDK_GRAVITY_NORTH_WEST : GDK_GRAVITY_NORTH_EAST;
+ GdkRectangle rect {x, y, nComboWidth, nComboHeight };
+ GdkWindow* toplevel = gtk_widget_get_window(GTK_WIDGET(pMenu));
+
+ window_move_to_rect(toplevel, &rect, rect_anchor, menu_anchor,
+ static_cast<GdkAnchorHints>(GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_RESIZE_Y |
+ GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_RESIZE_X),
+ 0, 0);
+
+ return true;
+}
+
+GtkPositionType show_menu(GtkWidget* pMenuButton, GtkWindow* pMenu)
+{
+ // 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 = gtk_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))
+ ePosUsed = show_menu_older_gtk(pMenuButton, pMenu);
+ gtk_widget_show_all(GTK_WIDGET(pMenu));
+ gtk_widget_grab_focus(GTK_WIDGET(pMenu));
+ do_grab(GTK_WIDGET(pMenu));
+
+ return ePosUsed;
+}
+
+class GtkInstanceMenuButton : public GtkInstanceToggleButton, public MenuHelper, public virtual weld::MenuButton
+{
+private:
+ GtkMenuButton* m_pMenuButton;
+ GtkBox* m_pBox;
+ GtkImage* m_pImage;
+ GtkWidget* m_pLabel;
+ //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;
+ GtkWidget* m_pPopover;
+ gulong m_nSignalId;
+
+ static void signalToggled(GtkWidget*, gpointer widget)
+ {
+ GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->toggle_menu();
+ }
+
+ void toggle_menu()
+ {
+ if (!m_pMenuHack)
+ return;
+ if (!get_active())
+ {
+ do_ungrab(GTK_WIDGET(m_pMenuHack));
+
+ gtk_widget_hide(GTK_WIDGET(m_pMenuHack));
+ //put contents back from where the came from
+ GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pMenuHack));
+ g_object_ref(pChild);
+ gtk_container_remove(GTK_CONTAINER(m_pMenuHack), pChild);
+ gtk_container_add(GTK_CONTAINER(m_pPopover), pChild);
+ g_object_unref(pChild);
+
+ // so gdk_window_move_to_rect will work again the next time
+ gtk_widget_unrealize(GTK_WIDGET(m_pMenuHack));
+
+ gtk_widget_set_size_request(GTK_WIDGET(m_pMenuHack), -1, -1);
+
+ // undo show_menu tooltip blocking
+ GtkWidget* pParent = gtk_widget_get_toplevel(GTK_WIDGET(m_pMenuButton));
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
+ if (pFrame)
+ pFrame->UnblockTooltip();
+ }
+ else
+ {
+ //set border width
+ gtk_container_set_border_width(GTK_CONTAINER(m_pMenuHack), gtk_container_get_border_width(GTK_CONTAINER(m_pPopover)));
+
+ //steal popover contents and smuggle into toplevel display window
+ GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pPopover));
+ g_object_ref(pChild);
+ gtk_container_remove(GTK_CONTAINER(m_pPopover), pChild);
+ gtk_container_add(GTK_CONTAINER(m_pMenuHack), pChild);
+ g_object_unref(pChild);
+
+ GtkPositionType ePosUsed = show_menu(m_pMenuHackAlign ? m_pMenuHackAlign : GTK_WIDGET(m_pMenuButton), m_pMenuHack);
+ // 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);
+ }
+ }
+
+ 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
+ {
+ //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 signalButtonRelease(GtkWidget* pWidget, GdkEventButton* pEvent, gpointer widget)
+ {
+ GtkInstanceMenuButton* pThis = static_cast<GtkInstanceMenuButton*>(widget);
+ return pThis->button_release(pWidget, pEvent);
+ }
+
+ bool button_release(GtkWidget* pWidget, 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 xoffset, yoffset;
+ gdk_window_get_root_origin(gtk_widget_get_window(pWidget), &xoffset, &yoffset);
+
+ GtkAllocation alloc;
+ gtk_widget_get_allocation(pWidget, &alloc);
+ xoffset += alloc.x;
+ yoffset += alloc.y;
+
+ gtk_widget_get_allocation(GTK_WIDGET(m_pMenuHack), &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;
+
+ 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;
+ }
+
+ void ensure_image_widget()
+ {
+ if (!m_pImage)
+ {
+ 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);
+ gtk_widget_show(GTK_WIDGET(m_pImage));
+ }
+ }
+
+public:
+ GtkInstanceMenuButton(GtkMenuButton* pMenuButton, GtkWidget* pMenuAlign, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pMenuButton), pBuilder, bTakeOwnership)
+ , MenuHelper(gtk_menu_button_get_popup(pMenuButton), false)
+ , m_pMenuButton(pMenuButton)
+ , m_pImage(nullptr)
+ , m_pMenuHack(nullptr)
+ , m_pMenuHackAlign(pMenuAlign)
+ , m_pPopover(nullptr)
+ , m_nSignalId(0)
+ {
+ m_pLabel = gtk_bin_get_child(GTK_BIN(m_pMenuButton));
+ //do it "manually" so we can have the dropdown image in GtkMenuButtons shown
+ //on the right at the same time as this image is shown on the left
+ g_object_ref(m_pLabel);
+ gtk_container_remove(GTK_CONTAINER(m_pMenuButton), m_pLabel);
+
+ gint nImageSpacing(2);
+ GtkStyleContext *pContext = gtk_widget_get_style_context(GTK_WIDGET(m_pMenuButton));
+ gtk_style_context_get_style(pContext, "image-spacing", &nImageSpacing, nullptr);
+ m_pBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, nImageSpacing));
+
+ gtk_box_pack_start(m_pBox, m_pLabel, false, false, 0);
+ g_object_unref(m_pLabel);
+
+ if (gtk_toggle_button_get_mode(GTK_TOGGLE_BUTTON(m_pMenuButton)))
+ gtk_box_pack_end(m_pBox, gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_BUTTON), false, false, 0);
+
+ gtk_container_add(GTK_CONTAINER(m_pMenuButton), GTK_WIDGET(m_pBox));
+ gtk_widget_show_all(GTK_WIDGET(m_pBox));
+ }
+
+ 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 (pDevice)
+ {
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ gtk_image_set_from_surface(m_pImage, get_underlying_cairo_surface(*pDevice));
+ else
+ {
+ GdkPixbuf* pixbuf = getPixbuf(*pDevice);
+ gtk_image_set_from_pixbuf(m_pImage, pixbuf);
+ g_object_unref(pixbuf);
+ }
+ }
+ else
+ gtk_image_set_from_surface(m_pImage, nullptr);
+ }
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
+ {
+ ensure_image_widget();
+ GdkPixbuf* pixbuf = getPixbuf(rImage);
+ if (pixbuf)
+ {
+ gtk_image_set_from_pixbuf(m_pImage, pixbuf);
+ g_object_unref(pixbuf);
+ }
+ else
+ gtk_image_set_from_surface(m_pImage, nullptr);
+ }
+
+ 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
+ {
+ 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 set_item_help_id(const OString& rIdent, const OString& rHelpId) override
+ {
+ MenuHelper::set_item_help_id(rIdent, rHelpId);
+ }
+
+ virtual OString get_item_help_id(const OString& rIdent) const override
+ {
+ return MenuHelper::get_item_help_id(rIdent);
+ }
+
+ virtual void signal_activate(GtkMenuItem* pItem) override
+ {
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
+ signal_selected(OString(pStr, pStr ? strlen(pStr) : 0));
+ }
+
+ virtual void set_popover(weld::Widget* pPopover) override
+ {
+ GtkInstanceWidget* pPopoverWidget = dynamic_cast<GtkInstanceWidget*>(pPopover);
+ m_pPopover = pPopoverWidget ? pPopoverWidget->getWidget() : nullptr;
+
+#if defined(GDK_WINDOWING_X11)
+ 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))
+ {
+ 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_modal(m_pMenuHack, true);
+ gtk_window_set_resizable(m_pMenuHack, false);
+ m_nSignalId = g_signal_connect(GTK_TOGGLE_BUTTON(m_pMenuButton), "toggled", G_CALLBACK(signalToggled), this);
+ g_signal_connect(m_pMenuHack, "grab-broken-event", G_CALLBACK(signalGrabBroken), this);
+ g_signal_connect(m_pMenuHack, "button-release-event", G_CALLBACK(signalButtonRelease), this);
+ g_signal_connect(m_pMenuHack, "key-press-event", G_CALLBACK(keyPress), this);
+ }
+ }
+#endif
+
+ 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; }";
+ gtk_css_provider_load_from_data(pProvider, data, -1, nullptr);
+ 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);
+ if (m_pPopover)
+ gtk_widget_show_all(m_pPopover);
+ }
+ }
+
+ void set_menu(weld::Menu* pMenu);
+
+ virtual ~GtkInstanceMenuButton() override
+ {
+ 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));
+ }
+ }
+};
+
+class GtkInstanceMenu : public MenuHelper, public virtual weld::Menu
+{
+protected:
+ std::vector<GtkMenuItem*> m_aExtraItems;
+ OString m_sActivated;
+ MenuHelper* m_pTopLevelMenuHelper;
+
+private:
+ virtual void signal_activate(GtkMenuItem* pItem) override
+ {
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
+ m_sActivated = OString(pStr, pStr ? strlen(pStr) : 0);
+ weld::Menu::signal_activate(m_sActivated);
+ }
+
+ 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();
+ }
+
+public:
+ GtkInstanceMenu(GtkMenu* pMenu, bool bTakeOwnership)
+ : MenuHelper(pMenu, bTakeOwnership)
+ , m_pTopLevelMenuHelper(nullptr)
+ {
+ g_object_set_data(G_OBJECT(m_pMenu), "g-lo-GtkInstanceMenu", this);
+ // 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)
+ {
+ // 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);
+ }
+ }
+ }
+
+ virtual OString popup_at_rect(weld::Widget* pParent, const tools::Rectangle &rRect) override
+ {
+ m_sActivated.clear();
+
+ GtkInstanceWidget* pGtkWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
+ assert(pGtkWidget);
+
+ GtkWidget* pWidget = pGtkWidget->getWidget();
+ gtk_menu_attach_to_widget(m_pMenu, pWidget, nullptr);
+
+ //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);
+ 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{static_cast<int>(rRect.Left()), static_cast<int>(rRect.Top()),
+ static_cast<int>(rRect.GetWidth()), static_cast<int>(rRect.GetHeight())};
+ if (SwapForRTL(pWidget))
+ aRect.x = gtk_widget_get_allocated_width(pWidget) - aRect.width - 1 - aRect.x;
+
+ // 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
+ GdkEvent *event = GtkSalFrame::makeFakeKeyPress(pWidget);
+ gtk_main_do_event(event);
+ gdk_event_free(event);
+
+ gtk_menu_popup_at_rect(m_pMenu, gtk_widget_get_window(pWidget), &aRect, GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_NORTH_WEST, nullptr);
+ }
+ else
+#else
+ (void) rRect;
+#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);
+ }
+
+ if (g_main_loop_is_running(pLoop))
+ {
+ gdk_threads_leave();
+ g_main_loop_run(pLoop);
+ gdk_threads_enter();
+ }
+ g_main_loop_unref(pLoop);
+ g_signal_handler_disconnect(m_pMenu, nSignalId);
+ gtk_menu_detach(m_pMenu);
+
+ return m_sActivated;
+ }
+
+ virtual void set_sensitive(const OString& rIdent, bool bSensitive) override
+ {
+ set_item_sensitive(rIdent, bSensitive);
+ }
+
+ 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
+ {
+ clear_extras();
+ clear_items();
+ }
+
+ virtual void insert(int pos, const OUString& rId, const OUString& rStr,
+ const OUString* pIconName, VirtualDevice* pImageSurface,
+ TriState eCheckRadioFalse) override
+ {
+ GtkWidget* pImage = nullptr;
+ if (pIconName)
+ {
+ if (GdkPixbuf* pixbuf = load_icon_by_name(*pIconName))
+ {
+ pImage = gtk_image_new_from_pixbuf(pixbuf);
+ g_object_unref(pixbuf);
+ }
+ }
+ else if (pImageSurface)
+ {
+ pImage = image_new_from_virtual_device(*pImageSurface);
+ }
+
+ GtkWidget *pItem;
+ if (pImage)
+ {
+ GtkWidget *pBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
+ GtkWidget *pLabel = gtk_label_new(MapToGtkAccelerator(rStr).getStr());
+ pItem = eCheckRadioFalse != TRISTATE_INDET ? gtk_check_menu_item_new() : gtk_menu_item_new();
+ gtk_container_add(GTK_CONTAINER(pBox), pImage);
+ gtk_container_add(GTK_CONTAINER(pBox), pLabel);
+ gtk_container_add(GTK_CONTAINER(pItem), 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);
+
+ gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
+ 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);
+ }
+
+ virtual int n_children() const override
+ {
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
+ int nLen = g_list_length(pChildren);
+ g_list_free(pChildren);
+ return nLen;
+ }
+
+ void remove(const OString& rIdent) override
+ {
+ 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())
+ {
+ m_pTopLevelMenuHelper->remove_from_map(pMenuItem);
+ m_aExtraItems.erase(iter);
+ }
+ }
+ MenuHelper::remove_item(rIdent);
+ }
+
+ virtual ~GtkInstanceMenu() override
+ {
+ clear_extras();
+ g_object_steal_data(G_OBJECT(m_pMenu), "g-lo-GtkInstanceMenu");
+ }
+};
+
+ vcl::ImageType GtkToVcl(GtkIconSize eSize)
+ {
+ vcl::ImageType eRet;
+ switch (eSize)
+ {
+ 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;
+ }
+ return eRet;
+ }
+
+ GtkIconSize VclToGtk(vcl::ImageType eSize)
+ {
+ GtkIconSize eRet;
+ 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;
+ }
+ return eRet;
+ }
+}
+
+void GtkInstanceMenuButton::set_menu(weld::Menu* pMenu)
+{
+ GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu);
+ m_pPopover = nullptr;
+ GtkWidget* pMenuWidget = GTK_WIDGET(pPopoverWidget ? pPopoverWidget->getMenu() : nullptr);
+ gtk_menu_button_set_popup(m_pMenuButton, pMenuWidget);
+}
+
+namespace {
+
+class GtkInstanceToolbar : public GtkInstanceWidget, public virtual weld::Toolbar
+{
+private:
+ GtkToolbar* m_pToolbar;
+ GtkCssProvider *m_pMenuButtonProvider;
+
+ std::map<OString, GtkToolItem*> m_aMap;
+ std::map<OString, std::unique_ptr<GtkInstanceMenuButton>> m_aMenuButtonMap;
+
+ // 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);
+ }
+
+ static void collect(GtkWidget* pItem, gpointer widget)
+ {
+ if (GTK_IS_TOOL_ITEM(pItem))
+ {
+ GtkToolItem* pToolItem = GTK_TOOL_ITEM(pItem);
+ GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
+
+ GtkMenuButton* pMenuButton = nullptr;
+ if (GTK_IS_MENU_TOOL_BUTTON(pItem))
+ find_menu_button(pItem, &pMenuButton);
+
+ pThis->add_to_map(pToolItem, pMenuButton);
+ }
+ }
+
+ void add_to_map(GtkToolItem* pToolItem, GtkMenuButton* pMenuButton)
+ {
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pToolItem));
+ OString id(pStr, pStr ? strlen(pStr) : 0);
+ 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
+ gtk_button_set_focus_on_click(GTK_BUTTON(pMenuButton), false);
+ g_signal_connect(pMenuButton, "toggled", G_CALLBACK(signalItemToggled), this);
+
+ if (pMenuButton)
+ {
+ // 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;"
+ "}";
+ const gchar olddata[] = "* { "
+ "padding: 0;"
+ "margin-left: 0px;"
+ "margin-right: 0px;"
+ "}";
+ gtk_css_provider_load_from_data(m_pMenuButtonProvider, gtk_check_version(3, 20, 0) == nullptr ? data : olddata, -1, nullptr);
+ }
+
+ gtk_style_context_add_provider(pButtonContext,
+ GTK_STYLE_PROVIDER(m_pMenuButtonProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ gtk_style_context_add_class(pButtonContext, "small-button");
+ }
+
+ }
+ if (!GTK_IS_TOOL_BUTTON(pToolItem))
+ return;
+ g_signal_connect(pToolItem, "clicked", G_CALLBACK(signalItemClicked), this);
+ }
+
+ static void signalItemClicked(GtkToolButton* pItem, gpointer widget)
+ {
+ GtkInstanceToolbar* pThis = static_cast<GtkInstanceToolbar*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_item_clicked(pItem);
+ }
+
+ void signal_item_clicked(GtkToolButton* pItem)
+ {
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
+ signal_clicked(OString(pStr, pStr ? strlen(pStr) : 0));
+ }
+
+ 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 (auto& a : m_aMenuButtonMap)
+ {
+ if (a.second->getWidget() == GTK_WIDGET(pItem))
+ {
+ signal_toggle_menu(a.first);
+ break;
+ }
+ }
+ }
+
+ static void set_item_image(GtkToolButton* pItem, const css::uno::Reference<css::graphic::XGraphic>& rIcon)
+ {
+ GtkWidget* pImage = nullptr;
+
+ if (GdkPixbuf* pixbuf = getPixbuf(rIcon))
+ {
+ pImage = gtk_image_new_from_pixbuf(pixbuf);
+ g_object_unref(pixbuf);
+ gtk_widget_show(pImage);
+ }
+
+ gtk_tool_button_set_icon_widget(pItem, pImage);
+ }
+
+ void set_item_image(GtkToolButton* pItem, VirtualDevice* pDevice)
+ {
+ GtkWidget* pImage = nullptr;
+
+ if (pDevice)
+ {
+ pImage = image_new_from_virtual_device(*pDevice);
+ gtk_widget_show(pImage);
+ }
+
+ gtk_tool_button_set_icon_widget(pItem, pImage);
+ gtk_widget_queue_draw(GTK_WIDGET(m_pToolbar));
+ }
+
+public:
+ GtkInstanceToolbar(GtkToolbar* pToolbar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pToolbar), pBuilder, bTakeOwnership)
+ , m_pToolbar(pToolbar)
+ , m_pMenuButtonProvider(nullptr)
+ {
+ gtk_container_foreach(GTK_CONTAINER(pToolbar), collect, this);
+ }
+
+ 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();
+
+ GtkToolItem* pToolButton = m_aMap.find(rIdent)->second;
+
+ 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);
+ }
+ }
+
+ enable_item_notify_events();
+ }
+
+ virtual bool get_item_active(const OString& rIdent) const override
+ {
+ GtkToolItem* pToolButton = m_aMap.find(rIdent)->second;
+
+ 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;
+ }
+ }
+
+ 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_separator(int pos, const OUString& rId) override
+ {
+ GtkToolItem* pItem = gtk_separator_tool_item_new();
+ gtk_buildable_set_name(GTK_BUILDABLE(pItem), OUStringToOString(rId, RTL_TEXTENCODING_UTF8).getStr());
+ gtk_toolbar_insert(m_pToolbar, pItem, pos);
+ 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
+ {
+ return gtk_toolbar_get_n_items(m_pToolbar);
+ }
+
+ virtual OString get_item_ident(int nIndex) const override
+ {
+ GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pItem));
+ return OString(pStr, pStr ? strlen(pStr) : 0);
+ }
+
+ virtual void set_item_ident(int nIndex, const OString& rIdent) override
+ {
+ GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
+ gtk_buildable_set_name(GTK_BUILDABLE(pItem), rIdent.getStr());
+ }
+
+ virtual void set_item_label(int nIndex, const OUString& rLabel) override
+ {
+ GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
+ if (!GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem), MapToGtkAccelerator(rLabel).getStr());
+ }
+
+ virtual void set_item_label(const OString& rIdent, const OUString& rLabel) override
+ {
+ GtkToolItem* pItem = m_aMap[rIdent];
+ if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ gtk_tool_button_set_label(GTK_TOOL_BUTTON(pItem), MapToGtkAccelerator(rLabel).getStr());
+ }
+
+ OUString get_item_label(const OString& rIdent) const override
+ {
+ const gchar* pText = gtk_tool_button_get_label(GTK_TOOL_BUTTON(m_aMap.find(rIdent)->second));
+ return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ virtual void set_item_icon_name(const OString& rIdent, const OUString& rIconName) override
+ {
+ GtkToolItem* pItem = m_aMap[rIdent];
+ if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
+ return;
+
+ GtkWidget* pImage = nullptr;
+
+ if (GdkPixbuf* pixbuf = getPixbuf(rIconName))
+ {
+ pImage = gtk_image_new_from_pixbuf(pixbuf);
+ g_object_unref(pixbuf);
+ gtk_widget_show(pImage);
+ }
+
+ gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(pItem), pImage);
+ }
+
+ virtual void set_item_image(const OString& rIdent, const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
+ {
+ GtkToolItem* pItem = m_aMap[rIdent];
+ if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ set_item_image(GTK_TOOL_BUTTON(pItem), rIcon);
+ }
+
+ virtual void set_item_image(const OString& rIdent, VirtualDevice* pDevice) override
+ {
+ GtkToolItem* pItem = m_aMap[rIdent];
+ if (!pItem || !GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ set_item_image(GTK_TOOL_BUTTON(pItem), pDevice);
+ }
+
+ virtual void set_item_image(int nIndex, const css::uno::Reference<css::graphic::XGraphic>& rIcon) override
+ {
+ GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, nIndex);
+ if (!GTK_IS_TOOL_BUTTON(pItem))
+ return;
+ set_item_image(GTK_TOOL_BUTTON(pItem), rIcon);
+ }
+
+ virtual void set_item_tooltip_text(int nIndex, const OUString& rTip) override
+ {
+ GtkToolItem* pItem = gtk_toolbar_get_nth_item(m_pToolbar, 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
+ {
+ GtkToolItem* pItem = m_aMap[rIdent];
+ gtk_widget_set_tooltip_text(GTK_WIDGET(pItem), OUStringToOString(rTip, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ virtual OUString get_item_tooltip_text(const OString& rIdent) const override
+ {
+ GtkToolItem* pItem = m_aMap.find(rIdent)->second;
+ const gchar* pStr = gtk_widget_get_tooltip_text(GTK_WIDGET(pItem));
+ return OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
+ }
+
+ virtual vcl::ImageType get_icon_size() const override
+ {
+ return GtkToVcl(gtk_toolbar_get_icon_size(m_pToolbar));
+ }
+
+ virtual void set_icon_size(vcl::ImageType eType) override
+ {
+ return gtk_toolbar_set_icon_size(m_pToolbar, VclToGtk(eType));
+ }
+
+ virtual sal_uInt16 get_modifier_state() const override
+ {
+ GdkKeymap* pKeymap = gdk_keymap_get_default();
+ guint nState = gdk_keymap_get_modifier_state(pKeymap);
+ return GtkSalFrame::GetKeyModCode(nState);
+ }
+
+ int get_drop_index(const Point& rPoint) const override
+ {
+ return gtk_toolbar_get_drop_index(m_pToolbar, rPoint.X(), rPoint.Y());
+ }
+
+ virtual ~GtkInstanceToolbar() override
+ {
+ for (auto& a : m_aMap)
+ g_signal_handlers_disconnect_by_data(a.second, this);
+ }
+};
+
+class GtkInstanceLinkButton : public GtkInstanceContainer, 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)
+ : GtkInstanceContainer(GTK_CONTAINER(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
+ {
+ ::set_label(GTK_BUTTON(m_pButton), rText);
+ }
+
+ virtual OUString get_label() const override
+ {
+ return ::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);
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::enable_notify_events();
+ g_signal_handler_unblock(m_pButton, m_nSignalId);
+ }
+
+ virtual ~GtkInstanceLinkButton() override
+ {
+ g_signal_handler_disconnect(m_pButton, m_nSignalId);
+ }
+};
+
+class GtkInstanceRadioButton : public GtkInstanceToggleButton, public virtual weld::RadioButton
+{
+public:
+ GtkInstanceRadioButton(GtkRadioButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pButton), pBuilder, bTakeOwnership)
+ {
+ }
+};
+
+class GtkInstanceCheckButton : public GtkInstanceToggleButton, public virtual weld::CheckButton
+{
+public:
+ GtkInstanceCheckButton(GtkCheckButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceToggleButton(GTK_TOGGLE_BUTTON(pButton), pBuilder, bTakeOwnership)
+ {
+ }
+};
+
+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
+ {
+ GdkPixbuf* pixbuf = load_icon_by_name(rIconName);
+ if (!pixbuf)
+ return;
+ gtk_image_set_from_pixbuf(m_pImage, pixbuf);
+ g_object_unref(pixbuf);
+ }
+
+ virtual void set_image(VirtualDevice* pDevice) override
+ {
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ if (pDevice)
+ gtk_image_set_from_surface(m_pImage, get_underlying_cairo_surface(*pDevice));
+ else
+ gtk_image_set_from_surface(m_pImage, nullptr);
+ return;
+ }
+
+ GdkPixbuf* pixbuf = pDevice ? getPixbuf(*pDevice) : nullptr;
+ gtk_image_set_from_pixbuf(m_pImage, pixbuf);
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+
+ virtual void set_image(const css::uno::Reference<css::graphic::XGraphic>& rImage) override
+ {
+ GdkPixbuf* pixbuf = getPixbuf(rImage);
+ gtk_image_set_from_pixbuf(m_pImage, pixbuf);
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+};
+
+class GtkInstanceCalendar : public GtkInstanceWidget, public virtual weld::Calendar
+{
+private:
+ GtkCalendar* m_pCalendar;
+ gulong m_nDaySelectedSignalId;
+ gulong m_nDaySelectedDoubleClickSignalId;
+ gulong m_nKeyPressEventSignalId;
+
+ static void signalDaySelected(GtkCalendar*, gpointer widget)
+ {
+ GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
+ pThis->signal_selected();
+ }
+
+ static void signalDaySelectedDoubleClick(GtkCalendar*, gpointer widget)
+ {
+ GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
+ pThis->signal_activated();
+ }
+
+ bool signal_key_press(GdkEventKey* pEvent)
+ {
+ if (pEvent->keyval == GDK_KEY_Return || pEvent->keyval == GDK_KEY_KP_Enter)
+ {
+ signal_activated();
+ return true;
+ }
+ return false;
+ }
+
+ static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceCalendar* pThis = static_cast<GtkInstanceCalendar*>(widget);
+ return pThis->signal_key_press(pEvent);
+ }
+
+public:
+ GtkInstanceCalendar(GtkCalendar* pCalendar, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pCalendar), pBuilder, bTakeOwnership)
+ , m_pCalendar(pCalendar)
+ , 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))
+ , m_nKeyPressEventSignalId(g_signal_connect(pCalendar, "key-press-event", G_CALLBACK(signalKeyPress), this))
+ {
+ }
+
+ virtual void set_date(const Date& rDate) override
+ {
+ if (!rDate.IsValidAndGregorian())
+ return;
+
+ disable_notify_events();
+ gtk_calendar_select_month(m_pCalendar, rDate.GetMonth() - 1, rDate.GetYear());
+ gtk_calendar_select_day(m_pCalendar, rDate.GetDay());
+ enable_notify_events();
+ }
+
+ virtual Date get_date() const override
+ {
+ guint year, month, day;
+ gtk_calendar_get_date(m_pCalendar, &year, &month, &day);
+ return Date(day, month + 1, year);
+ }
+
+ 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
+ {
+ g_signal_handler_disconnect(m_pCalendar, m_nKeyPressEventSignalId);
+ g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedDoubleClickSignalId);
+ g_signal_handler_disconnect(m_pCalendar, m_nDaySelectedSignalId);
+ }
+};
+
+ PangoAttrList* create_attr_list(const vcl::Font& rFont)
+ {
+ PangoAttrList* pAttrList = pango_attr_list_new();
+ pango_attr_list_insert(pAttrList, pango_attr_family_new(OUStringToOString(rFont.GetFamilyName(), RTL_TEXTENCODING_UTF8).getStr()));
+ pango_attr_list_insert(pAttrList, pango_attr_size_new(rFont.GetFontSize().Height() * PANGO_SCALE));
+ switch (rFont.GetItalic())
+ {
+ case ITALIC_NONE:
+ pango_attr_list_insert(pAttrList, pango_attr_style_new(PANGO_STYLE_NORMAL));
+ break;
+ case ITALIC_NORMAL:
+ pango_attr_list_insert(pAttrList, pango_attr_style_new(PANGO_STYLE_ITALIC));
+ break;
+ case ITALIC_OBLIQUE:
+ pango_attr_list_insert(pAttrList, pango_attr_style_new(PANGO_STYLE_OBLIQUE));
+ break;
+ default:
+ break;
+ }
+ switch (rFont.GetWeight())
+ {
+ case WEIGHT_ULTRALIGHT:
+ pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRALIGHT));
+ break;
+ case WEIGHT_LIGHT:
+ pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_LIGHT));
+ break;
+ case WEIGHT_NORMAL:
+ pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_NORMAL));
+ break;
+ case WEIGHT_BOLD:
+ pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
+ break;
+ case WEIGHT_ULTRABOLD:
+ pango_attr_list_insert(pAttrList, pango_attr_weight_new(PANGO_WEIGHT_ULTRABOLD));
+ break;
+ default:
+ break;
+ }
+ switch (rFont.GetWidthType())
+ {
+ case WIDTH_ULTRA_CONDENSED:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_CONDENSED));
+ break;
+ case WIDTH_EXTRA_CONDENSED:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_CONDENSED));
+ break;
+ case WIDTH_CONDENSED:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_CONDENSED));
+ break;
+ case WIDTH_SEMI_CONDENSED:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_CONDENSED));
+ break;
+ case WIDTH_NORMAL:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_NORMAL));
+ break;
+ case WIDTH_SEMI_EXPANDED:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_SEMI_EXPANDED));
+ break;
+ case WIDTH_EXPANDED:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXPANDED));
+ break;
+ case WIDTH_EXTRA_EXPANDED:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_EXTRA_EXPANDED));
+ break;
+ case WIDTH_ULTRA_EXPANDED:
+ pango_attr_list_insert(pAttrList, pango_attr_stretch_new(PANGO_STRETCH_ULTRA_EXPANDED));
+ break;
+ default:
+ break;
+ }
+ return pAttrList;
+ }
+}
+
+namespace
+{
+ void set_entry_message_type(GtkEntry* pEntry, weld::EntryMessageType eType)
+ {
+ if (eType == weld::EntryMessageType::Error)
+ gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-error");
+ else if (eType == weld::EntryMessageType::Warning)
+ gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning");
+ else
+ gtk_entry_set_icon_from_icon_name(pEntry, GTK_ENTRY_ICON_SECONDARY, nullptr);
+ }
+
+class GtkInstanceEntry : public GtkInstanceWidget, public virtual weld::Entry
+{
+private:
+ GtkEntry* m_pEntry;
+ std::unique_ptr<vcl::Font> m_xFont;
+ gulong m_nChangedSignalId;
+ gulong m_nInsertTextSignalId;
+ gulong m_nCursorPosSignalId;
+ gulong m_nSelectionPosSignalId;
+ gulong m_nActivateSignalId;
+
+ static void signalChanged(GtkEntry*, gpointer widget)
+ {
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_changed();
+ }
+
+ static void signalInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength,
+ gint* position, gpointer widget)
+ {
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_insert_text(pEntry, pNewText, nNewTextLength, position);
+ }
+
+ void signal_insert_text(GtkEntry* pEntry, 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(pEntry, reinterpret_cast<gpointer>(signalInsertText), this);
+ gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position);
+ g_signal_handlers_unblock_by_func(pEntry, reinterpret_cast<gpointer>(signalInsertText), this);
+ }
+ g_signal_stop_emission_by_name(pEntry, "insert-text");
+ }
+
+ static void signalCursorPosition(GtkEntry*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ pThis->signal_cursor_position();
+ }
+
+ static void signalActivate(GtkEntry*, gpointer widget)
+ {
+ GtkInstanceEntry* pThis = static_cast<GtkInstanceEntry*>(widget);
+ pThis->signal_activate();
+ }
+
+protected:
+
+ virtual void signal_activate()
+ {
+ if (m_aActivateHdl.IsSet())
+ {
+ SolarMutexGuard aGuard;
+ if (m_aActivateHdl.Call(*this))
+ g_signal_stop_emission_by_name(m_pEntry, "activate");
+ }
+ }
+
+public:
+ GtkInstanceEntry(GtkEntry* pEntry, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pEntry), pBuilder, bTakeOwnership)
+ , m_pEntry(pEntry)
+ , m_nChangedSignalId(g_signal_connect(pEntry, "changed", G_CALLBACK(signalChanged), this))
+ , m_nInsertTextSignalId(g_signal_connect(pEntry, "insert-text", G_CALLBACK(signalInsertText), this))
+ , m_nCursorPosSignalId(g_signal_connect(pEntry, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
+ , m_nSelectionPosSignalId(g_signal_connect(pEntry, "notify::selection-bound", G_CALLBACK(signalCursorPosition), this))
+ , m_nActivateSignalId(g_signal_connect(pEntry, "activate", G_CALLBACK(signalActivate), this))
+ {
+ }
+
+ virtual void set_text(const OUString& rText) override
+ {
+ disable_notify_events();
+ gtk_entry_set_text(m_pEntry, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+ enable_notify_events();
+ }
+
+ virtual OUString get_text() const override
+ {
+ const gchar* pText = gtk_entry_get_text(m_pEntry);
+ OUString sRet(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ return sRet;
+ }
+
+ virtual void set_width_chars(int nChars) override
+ {
+ disable_notify_events();
+ gtk_entry_set_width_chars(m_pEntry, nChars);
+ gtk_entry_set_max_width_chars(m_pEntry, nChars);
+ enable_notify_events();
+ }
+
+ virtual int get_width_chars() const override
+ {
+ return gtk_entry_get_width_chars(m_pEntry);
+ }
+
+ virtual void set_max_length(int nChars) override
+ {
+ disable_notify_events();
+ gtk_entry_set_max_length(m_pEntry, nChars);
+ enable_notify_events();
+ }
+
+ virtual void select_region(int nStartPos, int nEndPos) override
+ {
+ disable_notify_events();
+ gtk_editable_select_region(GTK_EDITABLE(m_pEntry), nStartPos, nEndPos);
+ enable_notify_events();
+ }
+
+ bool get_selection_bounds(int& rStartPos, int& rEndPos) override
+ {
+ return gtk_editable_get_selection_bounds(GTK_EDITABLE(m_pEntry), &rStartPos, &rEndPos);
+ }
+
+ virtual void replace_selection(const OUString& rText) override
+ {
+ disable_notify_events();
+ gtk_editable_delete_selection(GTK_EDITABLE(m_pEntry));
+ OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gint position = gtk_editable_get_position(GTK_EDITABLE(m_pEntry));
+ gtk_editable_insert_text(GTK_EDITABLE(m_pEntry), sText.getStr(), sText.getLength(),
+ &position);
+ enable_notify_events();
+ }
+
+ virtual void set_position(int nCursorPos) override
+ {
+ disable_notify_events();
+ gtk_editable_set_position(GTK_EDITABLE(m_pEntry), nCursorPos);
+ enable_notify_events();
+ }
+
+ virtual int get_position() const override
+ {
+ return gtk_editable_get_position(GTK_EDITABLE(m_pEntry));
+ }
+
+ virtual void set_editable(bool bEditable) override
+ {
+ gtk_editable_set_editable(GTK_EDITABLE(m_pEntry), bEditable);
+ }
+
+ virtual bool get_editable() const override
+ {
+ return gtk_editable_get_editable(GTK_EDITABLE(m_pEntry));
+ }
+
+ virtual void set_message_type(weld::EntryMessageType eType) override
+ {
+ ::set_entry_message_type(m_pEntry, eType);
+ }
+
+ virtual void disable_notify_events() override
+ {
+ g_signal_handler_block(m_pEntry, m_nActivateSignalId);
+ g_signal_handler_block(m_pEntry, m_nSelectionPosSignalId);
+ g_signal_handler_block(m_pEntry, m_nCursorPosSignalId);
+ g_signal_handler_block(m_pEntry, m_nInsertTextSignalId);
+ g_signal_handler_block(m_pEntry, m_nChangedSignalId);
+ GtkInstanceWidget::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceWidget::enable_notify_events();
+ g_signal_handler_unblock(m_pEntry, m_nChangedSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nInsertTextSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nCursorPosSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nSelectionPosSignalId);
+ g_signal_handler_unblock(m_pEntry, m_nActivateSignalId);
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ m_xFont.reset(new vcl::Font(rFont));
+ PangoAttrList* pAttrList = create_attr_list(rFont);
+ gtk_entry_set_attributes(m_pEntry, pAttrList);
+ pango_attr_list_unref(pAttrList);
+ }
+
+ virtual vcl::Font get_font() override
+ {
+ if (m_xFont)
+ return *m_xFont;
+ return GtkInstanceWidget::get_font();
+ }
+
+ void fire_signal_changed()
+ {
+ signal_changed();
+ }
+
+ virtual void cut_clipboard() override
+ {
+ gtk_editable_cut_clipboard(GTK_EDITABLE(m_pEntry));
+ }
+
+ virtual void copy_clipboard() override
+ {
+ gtk_editable_copy_clipboard(GTK_EDITABLE(m_pEntry));
+ }
+
+ virtual void paste_clipboard() override
+ {
+ gtk_editable_paste_clipboard(GTK_EDITABLE(m_pEntry));
+ }
+
+ virtual void set_placeholder_text(const OUString& rText) override
+ {
+ gtk_entry_set_placeholder_text(m_pEntry, rText.toUtf8().getStr());
+ }
+
+ virtual void grab_focus() override
+ {
+ disable_notify_events();
+ gtk_entry_grab_focus_without_selecting(m_pEntry);
+ enable_notify_events();
+ }
+
+ virtual ~GtkInstanceEntry() override
+ {
+ g_signal_handler_disconnect(m_pEntry, m_nActivateSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nSelectionPosSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nCursorPosSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nInsertTextSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nChangedSignalId);
+ }
+};
+
+ struct Search
+ {
+ OString str;
+ int index;
+ int col;
+ Search(const OUString& 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, const OUString& 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)
+{
+ gint nVerticalSeparator;
+ gtk_widget_style_get(GTK_WIDGET(pTreeView), "vertical-separator", &nVerticalSeparator, nullptr);
+ 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 + 1));
+}
+
+int get_height_rows(int nRowHeight, int nSeparatorHeight, int nRows)
+{
+ return (nRowHeight * nRows) + (nSeparatorHeight * (nRows + 1));
+}
+
+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;
+}
+
+class GtkInstanceTreeView : public GtkInstanceContainer, public virtual weld::TreeView
+{
+private:
+ GtkTreeView* m_pTreeView;
+ GtkTreeStore* m_pTreeStore;
+ 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;
+ std::vector<GtkSortType> m_aSavedSortTypes;
+ std::vector<int> m_aSavedSortColumns;
+ std::vector<int> m_aViewColToModelCol;
+ std::vector<int> m_aModelColToViewCol;
+ bool m_bWorkAroundBadDragRegion;
+ bool m_bInDrag;
+ gint m_nTextCol;
+ gint m_nImageCol;
+ 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;
+ gulong m_nPopupMenuSignalId;
+ gulong m_nKeyPressSignalId;
+ 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);
+ 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(GTK_TREE_MODEL(m_pTreeStore), &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, const OUString* pExpanderName)
+ {
+ gtk_tree_store_insert_with_values(m_pTreeStore, &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(),
+ -1);
+ if (pIconName)
+ {
+ GdkPixbuf* pixbuf = getPixbuf(*pIconName);
+ gtk_tree_store_set(m_pTreeStore, &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);
+
+ gtk_tree_store_set(m_pTreeStore, &iter, m_nImageCol, target, -1);
+ cairo_surface_destroy(target);
+ }
+
+ if (pExpanderName)
+ {
+ GdkPixbuf* pixbuf = getPixbuf(*pExpanderName);
+ gtk_tree_store_set(m_pTreeStore, &iter, m_nExpanderImageCol, 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;
+ }
+
+ OUString get(int pos, int col) const
+ {
+ OUString sRet;
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
+ sRet = get(iter, col);
+ return sRet;
+ }
+
+ gint get_int(const GtkTreeIter& iter, int col) const
+ {
+ gint nRet(-1);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &nRet, -1);
+ return nRet;
+ }
+
+ gint get_int(int pos, int col) const
+ {
+ gint nRet(-1);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
+ nRet = get_int(iter, col);
+ gtk_tree_model_get(pModel, &iter, col, &nRet, -1);
+ return nRet;
+ }
+
+ bool get_bool(const GtkTreeIter& iter, int col) const
+ {
+ gboolean bRet(false);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ gtk_tree_model_get(pModel, const_cast<GtkTreeIter*>(&iter), col, &bRet, -1);
+ return bRet;
+ }
+
+ bool get_bool(int pos, int col) const
+ {
+ bool bRet(false);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
+ bRet = get_bool(iter, col);
+ return bRet;
+ }
+
+ void set_toggle(const GtkTreeIter& iter, TriState eState, int col)
+ {
+ col = get_model_col(col);
+ if (eState == TRISTATE_INDET)
+ {
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter),
+ m_aToggleVisMap[col], true, // checkbuttons are invisible until toggled on or off
+ m_aToggleTriStateMap[col], true, // tristate on
+ -1);
+ }
+ else
+ {
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter),
+ m_aToggleVisMap[col], true, // checkbuttons are invisible until toggled on or off
+ m_aToggleTriStateMap[col], false, // tristate off
+ col, eState == TRISTATE_TRUE, // set toggle state
+ -1);
+ }
+ }
+
+ void set(const GtkTreeIter& iter, int col, const OUString& rText)
+ {
+ OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, aStr.getStr(), -1);
+ }
+
+ void set(int pos, int col, const OUString& rText)
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
+ set(iter, col, rText);
+ }
+
+ void set(const GtkTreeIter& iter, int col, bool bOn)
+ {
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, bOn, -1);
+ }
+
+ void set(int pos, int col, bool bOn)
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
+ set(iter, col, bOn);
+ }
+
+ void set(const GtkTreeIter& iter, int col, gint bInt)
+ {
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, bInt, -1);
+ }
+
+ void set(int pos, int col, gint bInt)
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
+ set(iter, col, bInt);
+ }
+
+ void set(const GtkTreeIter& iter, int col, double fValue)
+ {
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, fValue, -1);
+ }
+
+ void set(int pos, int col, double fValue)
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &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
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+
+ GtkTreePath* pPath = gtk_tree_model_get_path(pModel, &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(pModel, &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)
+ {
+ gtk_tree_store_remove(m_pTreeStore, &aIter.iter);
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ pPlaceHolderPath = gtk_tree_model_get_path(pModel, &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, nullptr);
+ }
+ m_aExpandingPlaceHolderParents.erase(pPlaceHolderPath);
+ gtk_tree_path_free(pPlaceHolderPath);
+ }
+
+ enable_notify_events();
+ return bRet;
+ }
+
+ bool signal_test_collapse_row(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);
+
+ // toggled signal handlers can query get_cursor to get which
+ // node was clicked
+ gtk_tree_view_set_cursor(m_pTreeView, tree_path, nullptr, false);
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ gtk_tree_model_get_iter(pModel, &iter, tree_path);
+
+ gboolean bRet(false);
+ gtk_tree_model_get(pModel, &iter, nCol, &bRet, -1);
+ bRet = !bRet;
+ gtk_tree_store_set(m_pTreeStore, &iter, nCol, bRet, -1);
+
+ gint depth;
+ gint* indices = gtk_tree_path_get_indices_with_depth(tree_path, &depth);
+ int nRow = indices[depth-1];
+
+ set(iter, m_aToggleTriStateMap[nCol], false);
+
+ signal_toggled(std::make_pair(nRow, 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);
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkInstanceTreeIter aGtkIter(nullptr);
+ gtk_tree_model_get_iter(pModel, &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);
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkInstanceTreeIter aGtkIter(nullptr);
+ gtk_tree_model_get_iter(pModel, &aGtkIter.iter, tree_path);
+ gtk_tree_path_free(tree_path);
+
+ OUString sText(pNewText, pNewText ? strlen(pNewText) : 0, RTL_TEXTENCODING_UTF8);
+ if (signal_editing_done(std::pair<const weld::TreeIter&, OUString>(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();
+ }
+
+ int get_model_col(int viewcol) const
+ {
+ return m_aViewColToModelCol[viewcol];
+ }
+
+ int get_view_col(int modelcol) const
+ {
+ return m_aModelColToViewCol[modelcol];
+ }
+
+ void set_column_editable(int nCol, bool bEditable)
+ {
+ 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());
+ }
+
+ 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(GTK_TREE_MODEL(m_pTreeStore), &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);
+ }
+
+ 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_tree_view_get_tooltip_context(pTreeView, &x, &y, keyboard_tip, &pModel, &pPath, &iter))
+ return false;
+ OUString aTooltip = pThis->signal_query_tooltip(GtkInstanceTreeIter(iter));
+ if (aTooltip.isEmpty())
+ return false;
+ 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 true;
+ }
+
+ 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)
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), m_nIdCol + 1, nullptr, -1);
+ else
+ {
+ GdkRGBA aColor{rColor.GetRed()/255.0, rColor.GetGreen()/255.0, rColor.GetBlue()/255.0, 0};
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), m_nIdCol + 1, &aColor, -1);
+ }
+ }
+
+ int get_expander_size() const
+ {
+ gint nExpanderSize;
+ gint nHorizontalSeparator;
+
+ gtk_widget_style_get(GTK_WIDGET(m_pTreeView),
+ "expander-size", &nExpanderSize,
+ "horizontal-separator", &nHorizontalSeparator,
+ nullptr);
+
+ 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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter tmp;
+ GtkTreeIter iter = rGtkIter.iter;
+
+ bool ret = gtk_tree_model_iter_children(pModel, &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(pModel, &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(pModel, &tmp, &iter))
+ {
+ iter = tmp;
+ if (gtk_tree_model_iter_next(pModel, &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)
+ : GtkInstanceContainer(GTK_CONTAINER(pTreeView), pBuilder, bTakeOwnership)
+ , m_pTreeView(pTreeView)
+ , m_pTreeStore(GTK_TREE_STORE(gtk_tree_view_get_model(m_pTreeView)))
+ , m_bWorkAroundBadDragRegion(false)
+ , m_bInDrag(false)
+ , m_nTextCol(-1)
+ , m_nImageCol(-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)
+ , 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))
+ , m_nQueryTooltipSignalId(0)
+ , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTreeView)))
+ , m_pChangeEvent(nullptr)
+ {
+ m_pColumns = gtk_tree_view_get_columns(m_pTreeView);
+ 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);
+ 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);
+ g_object_set_data(G_OBJECT(pCellRenderer), "g-lo-CellIndex", reinterpret_cast<gpointer>(nIndex));
+ if (GTK_IS_CELL_RENDERER_TEXT(pCellRenderer))
+ {
+ if (m_nTextCol == -1)
+ m_nTextCol = nIndex;
+ 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))
+ {
+ 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;
+ }
+ m_aModelColToViewCol.push_back(m_aViewColToModelCol.size());
+ ++nIndex;
+ }
+ g_list_free(pRenderers);
+ m_aViewColToModelCol.push_back(nIndex - 1);
+ }
+
+ 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();
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ m_nRowDeletedSignalId = g_signal_connect(pModel, "row-deleted", G_CALLBACK(signalRowDeleted), this);
+ m_nRowInsertedSignalId = g_signal_connect(pModel, "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");
+ gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn));
+ if (bEnable)
+ {
+ GtkCellRenderer *pRenderer = custom_cell_renderer_surface_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);
+ }
+ }
+
+ virtual void insert(const weld::TreeIter* pParent, int pos, const OUString* pText, const OUString* pId, const OUString* pIconName,
+ VirtualDevice* pImageSurface, const OUString* pExpanderName,
+ 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, pExpanderName);
+ if (bChildrenOnDemand)
+ {
+ GtkTreeIter subiter;
+ OUString sDummy("<dummy>");
+ insert_row(subiter, &iter, -1, nullptr, &sDummy, nullptr, nullptr, nullptr);
+ }
+ if (pRet)
+ {
+ GtkInstanceTreeIter* pGtkRetIter = static_cast<GtkInstanceTreeIter*>(pRet);
+ pGtkRetIter->iter = iter;
+ }
+ enable_notify_events();
+ }
+
+ virtual void set_font_color(int pos, const Color& rColor) override
+ {
+ GtkTreeIter iter;
+ gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(m_pTreeStore), &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(GTK_TREE_MODEL(m_pTreeStore), &iter, nullptr, pos);
+ gtk_tree_store_remove(m_pTreeStore, &iter);
+ enable_notify_events();
+ }
+
+ virtual int find_text(const OUString& rText) const override
+ {
+ Search aSearch(rText, m_nTextCol);
+ gtk_tree_model_foreach(GTK_TREE_MODEL(m_pTreeStore), foreach_find, &aSearch);
+ return aSearch.index;
+ }
+
+ virtual int find_id(const OUString& rId) const override
+ {
+ Search aSearch(rId, m_nIdCol);
+ gtk_tree_model_foreach(GTK_TREE_MODEL(m_pTreeStore), foreach_find, &aSearch);
+ return aSearch.index;
+ }
+
+ virtual void bulk_insert_for_each(int nSourceCount, const std::function<void(weld::TreeIter&, int nSourceIndex)>& func,
+ const std::vector<int>* pFixedWidths) override
+ {
+ freeze();
+ clear();
+ GtkInstanceTreeIter aGtkIter(nullptr);
+
+ if (pFixedWidths)
+ set_column_fixed_widths(*pFixedWidths);
+
+ while (nSourceCount)
+ {
+ // tdf#125241 inserting backwards is massively faster
+ gtk_tree_store_prepend(m_pTreeStore, &aGtkIter.iter, nullptr);
+ func(aGtkIter, --nSourceCount);
+ }
+
+ thaw();
+ }
+
+ virtual void swap(int pos1, int pos2) override
+ {
+ disable_notify_events();
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+
+ GtkTreeIter iter1;
+ gtk_tree_model_iter_nth_child(pModel, &iter1, nullptr, pos1);
+
+ GtkTreeIter iter2;
+ gtk_tree_model_iter_nth_child(pModel, &iter2, nullptr, pos2);
+
+ gtk_tree_store_swap(m_pTreeStore, &iter1, &iter2);
+
+ enable_notify_events();
+ }
+
+ virtual void clear() override
+ {
+ disable_notify_events();
+ gtk_tree_store_clear(m_pTreeStore);
+ 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_pTreeStore);
+ 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_pTreeStore);
+ 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_pTreeStore);
+ 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
+ {
+ GtkSortType eSortType;
+
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
+ gtk_tree_sortable_get_sort_column_id(pSortable, nullptr, &eSortType);
+ return eSortType == GTK_SORT_ASCENDING;
+ }
+
+ virtual void set_sort_indicator(TriState eState, int col) override
+ {
+ if (col == -1)
+ col = get_view_col(m_nTextCol);
+
+ 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
+ {
+ if (col == -1)
+ col = get_view_col(m_nTextCol);
+
+ 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_pTreeStore);
+ gint sort_column_id(0);
+ if (!gtk_tree_sortable_get_sort_column_id(pSortable, &sort_column_id, nullptr))
+ return -1;
+ return get_view_col(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_pTreeStore);
+ gtk_tree_sortable_get_sort_column_id(pSortable, nullptr, &eSortType);
+ int nSortCol = get_model_col(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_pTreeStore);
+ gtk_tree_sortable_sort_column_changed(pSortable);
+ }
+
+ virtual int n_children() const override
+ {
+ return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_pTreeStore), 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(GTK_TREE_MODEL(m_pTreeStore), 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 = gtk_tree_path_new_from_indices(pos, -1);
+ 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 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(GTK_TREE_MODEL(m_pTreeStore), &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
+ {
+ GtkInstanceTreeIter aGtkIter(nullptr);
+ if (get_iter_first(aGtkIter))
+ {
+ do
+ {
+ if (func(aGtkIter))
+ break;
+ } while (iter_next(aGtkIter));
+ }
+ }
+
+ virtual void selected_foreach(const std::function<bool(weld::TreeIter&)>& func) override
+ {
+ 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));
+ }
+
+ virtual void visible_foreach(const std::function<bool(weld::TreeIter&)>& func) override
+ {
+ GtkTreePath* start_path;
+ GtkTreePath* end_path;
+
+ if (gtk_tree_view_get_visible_range(m_pTreeView, &start_path, &end_path))
+ {
+ GtkInstanceTreeIter aGtkIter(nullptr);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ gtk_tree_model_get_iter(pModel, &aGtkIter.iter, start_path);
+
+ do
+ {
+ if (func(aGtkIter))
+ break;
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, &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);
+ }
+ }
+
+ 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)
+ return get(pos, m_nTextCol);
+ return get(pos, get_model_col(col));
+ }
+
+ virtual void set_text(int pos, const OUString& rText, int col) override
+ {
+ if (col == -1)
+ col = m_nTextCol;
+ else
+ col = get_model_col(col);
+ set(pos, col, rText);
+ }
+
+ virtual TriState get_toggle(int pos, int col) const override
+ {
+ col = get_model_col(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
+ {
+ col = get_model_col(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);
+ col = get_model_col(col);
+ // checkbuttons are invisible until toggled on or off
+ set(rGtkIter.iter, m_aToggleVisMap[col], true);
+ if (eState == TRISTATE_INDET)
+ set(rGtkIter.iter, m_aToggleTriStateMap[col], true);
+ else
+ {
+ set(rGtkIter.iter, m_aToggleTriStateMap[col], false);
+ set(rGtkIter.iter, col, eState == TRISTATE_TRUE);
+ }
+ }
+
+ virtual void set_toggle(int pos, TriState eState, int col) override
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &iter, nullptr, pos))
+ set_toggle(iter, eState, col);
+ }
+
+ 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 = get_model_col(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 = get_model_col(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 = get_model_col(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 = get_model_col(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 = get_model_col(col);
+ set(rGtkIter.iter, m_aAlignMap[col], fAlign);
+ }
+
+ virtual void set_text_align(int pos, double fAlign, int col) override
+ {
+ col = get_model_col(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 = get_model_col(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 = get_model_col(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 = get_model_col(col);
+ gtk_tree_store_set(m_pTreeStore, const_cast<GtkTreeIter*>(&iter), col, pixbuf, -1);
+ if (pixbuf)
+ g_object_unref(pixbuf);
+ }
+
+ void set_image(int pos, GdkPixbuf* pixbuf, int col)
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter;
+ if (gtk_tree_model_iter_nth_child(pModel, &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);
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, 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);
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* pathA = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIterA.iter));
+ GtkTreePath* pathB = gtk_tree_model_get_path(pModel, 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)
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+
+ int nCols = gtk_tree_model_get_n_columns(pModel);
+ GValue value;
+
+ GtkTreeIter toiter;
+ gtk_tree_store_insert(m_pTreeStore, &toiter, pGtkParentIter, nIndexInNewParent);
+
+ for (int i = 0; i < nCols; ++i)
+ {
+ memset(&value, 0, sizeof(GValue));
+ gtk_tree_model_get_value(pModel, &rFromIter, i, &value);
+ gtk_tree_store_set_value(m_pTreeStore, &toiter, i, &value);
+ g_value_unset(&value);
+ }
+
+ GtkTreeIter tmpfromiter;
+ if (gtk_tree_model_iter_children(pModel, &tmpfromiter, &rFromIter))
+ {
+ int j = 0;
+ do
+ {
+ move_subtree(tmpfromiter, &toiter, j++);
+ } while (gtk_tree_model_iter_next(pModel, &tmpfromiter));
+ }
+
+ gtk_tree_store_remove(m_pTreeStore, &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)
+ {
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ gtk_tree_model_get_iter(pModel, &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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter Iter;
+ if (gtk_tree_model_iter_parent(pModel, &Iter, const_cast<GtkTreeIter*>(&rGtkIter.iter)))
+ {
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, &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(pModel, 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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ return gtk_tree_model_get_iter_first(pModel, &rGtkIter.iter);
+ }
+
+ virtual bool iter_next_sibling(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ return gtk_tree_model_iter_next(pModel, &rGtkIter.iter);
+ }
+
+ virtual bool iter_previous_sibling(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ return gtk_tree_model_iter_previous(pModel, &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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter iter = rGtkIter.iter;
+ GtkTreeIter tmp = iter;
+ if (gtk_tree_model_iter_previous(pModel, &tmp))
+ {
+ // Move down level(s) until we find the level where the last node exists.
+ int nChildren = gtk_tree_model_iter_n_children(pModel, &tmp);
+ if (!nChildren)
+ rGtkIter.iter = tmp;
+ else
+ last_child(pModel, &rGtkIter.iter, &tmp, nChildren);
+ ret = true;
+ }
+ else
+ {
+ // Move up level
+ if (gtk_tree_model_iter_parent(pModel, &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_next_visible(weld::TreeIter& rIter) const override
+ {
+ return iter_next(rIter, true);
+ }
+
+ virtual bool iter_children(weld::TreeIter& rIter) const override
+ {
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rIter);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter tmp;
+ bool ret = gtk_tree_model_iter_children(pModel, &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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreeIter tmp;
+ auto ret = gtk_tree_model_iter_parent(pModel, &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);
+ gtk_tree_store_remove(m_pTreeStore, 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)
+ gtk_tree_store_remove(m_pTreeStore, &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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, 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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, 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
+ {
+ weld::TreeIter& rNonConstIter = const_cast<weld::TreeIter&>(rIter);
+ GtkInstanceTreeIter& rGtkIter = static_cast<GtkInstanceTreeIter&>(rNonConstIter);
+ GtkTreeIter restore(rGtkIter.iter);
+ bool ret = iter_children(rNonConstIter);
+ rGtkIter.iter = restore;
+ return ret;
+ }
+
+ virtual bool get_row_expanded(const weld::TreeIter& rIter) const override
+ {
+ 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));
+ 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, 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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, 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);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* path = gtk_tree_model_get_path(pModel, 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 = get_model_col(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 = get_model_col(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();
+ g_object_ref(m_pTreeStore);
+ GtkInstanceContainer::freeze();
+ gtk_tree_view_set_model(m_pTreeView, nullptr);
+ if (m_xSorter)
+ {
+ int nSortColumn;
+ GtkSortType eSortType;
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
+ 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 (m_xSorter)
+ {
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeStore);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_aSavedSortColumns.back(), m_aSavedSortTypes.back());
+ m_aSavedSortTypes.pop_back();
+ m_aSavedSortColumns.pop_back();
+ }
+ gtk_tree_view_set_model(m_pTreeView, GTK_TREE_MODEL(m_pTreeStore));
+ GtkInstanceContainer::thaw();
+ g_object_unref(m_pTreeStore);
+ 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;
+ 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);
+ }
+
+ virtual void drag_source_set(const std::vector<GtkTargetEntry>& rGtkTargets, GdkDragAction eDragAction) override
+ {
+ gtk_tree_view_enable_model_drag_source(m_pTreeView, GDK_BUTTON1_MASK, rGtkTargets.data(), rGtkTargets.size(), eDragAction);
+ }
+
+ 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 col, int nStartRow, bool bCaseSensitive)
+ {
+ return ::starts_with(GTK_TREE_MODEL(m_pTreeStore), rStr, get_model_col(col), 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);
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ g_signal_handler_block(pModel, m_nRowDeletedSignalId);
+ g_signal_handler_block(pModel, m_nRowInsertedSignalId);
+
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::enable_notify_events();
+
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ g_signal_handler_unblock(pModel, m_nRowDeletedSignalId);
+ g_signal_handler_unblock(pModel, 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 bHighLightTarget) override
+ {
+ 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)
+ gtk_drag_unhighlight(GTK_WIDGET(m_pTreeView));
+
+ 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
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath *lastpath = get_path_of_last_entry(pModel);
+
+ 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 (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(pModel, &rGtkIter.iter, path);
+ }
+
+ if (m_bInDrag && bHighLightTarget) // bHighLightTarget alone might be sufficient
+ {
+ // 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);
+ GtkTreeModel* pModel = GTK_TREE_MODEL(m_pTreeStore);
+ GtkTreePath* pPath = gtk_tree_model_get_path(pModel, 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
+ {
+ int col = get_view_col(m_nTextCol);
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(g_list_nth_data(m_pColumns, col));
+ assert(pColumn && "wrong column");
+
+ 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));
+
+ // 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))
+ {
+ gtk_drag_unhighlight(pWidget);
+ gtk_drag_highlight(pParent);
+ 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);
+ gtk_drag_unhighlight(pParent);
+ 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 ~GtkInstanceTreeView() override
+ {
+ if (m_pChangeEvent)
+ Application::RemoveUserEvent(m_pChangeEvent);
+ if (m_nQueryTooltipSignalId)
+ g_signal_handler_disconnect(m_pTreeView, m_nQueryTooltipSignalId);
+ g_signal_handler_disconnect(m_pTreeView, m_nKeyPressSignalId);
+ g_signal_handler_disconnect(m_pTreeView, m_nPopupMenuSignalId);
+ GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore);
+ g_signal_handler_disconnect(pModel, m_nRowDeletedSignalId);
+ g_signal_handler_disconnect(pModel, 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_SURFACE(pCellRenderer))
+ continue;
+ g_object_set_property(G_OBJECT(pCellRenderer), "instance", &value);
+ }
+ g_list_free(pRenderers);
+ }
+ g_list_free(m_pColumns);
+ }
+};
+
+void ensure_device(CustomCellRendererSurface *cellsurface, weld::Widget* pWidget)
+{
+ if (!cellsurface->device)
+ {
+ cellsurface->device = VclPtr<VirtualDevice>::Create();
+ cellsurface->device->SetBackground(COL_TRANSPARENT);
+ // expand the point size of the desired font to the equivalent pixel size
+ if (vcl::Window* pDefaultDevice = dynamic_cast<vcl::Window*>(Application::GetDefaultDevice()))
+ pDefaultDevice->SetPointFont(*cellsurface->device, pWidget->get_font());
+ }
+}
+
+}
+
+IMPL_LINK_NOARG(GtkInstanceTreeView, async_signal_changed, void*, void)
+{
+ m_pChangeEvent = nullptr;
+ signal_changed();
+}
+
+IMPL_LINK_NOARG(GtkInstanceTreeView, async_stop_cell_editing, void*, void)
+{
+ end_editing();
+}
+
+namespace {
+
+class GtkInstanceIconView : public GtkInstanceContainer, 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;
+ ImplSVEvent* m_pSelectionChangeEvent;
+
+ DECL_LINK(async_signal_selection_changed, void*, void);
+
+ 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();
+ }
+
+ void insert_item(GtkTreeIter& iter, int pos, const OUString* pId, const OUString* pText, const OUString* pIconName)
+ {
+ gtk_tree_store_insert_with_values(m_pTreeStore, &iter, nullptr, pos,
+ m_nTextCol, !pText ? nullptr : OUStringToOString(*pText, RTL_TEXTENCODING_UTF8).getStr(),
+ m_nIdCol, !pId ? nullptr : OUStringToOString(*pId, 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);
+ }
+ }
+
+ 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)
+ : GtkInstanceContainer(GTK_CONTAINER(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))
+ , 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))
+ , m_pSelectionChangeEvent(nullptr)
+ {
+ m_nIdCol = m_nTextCol + 1;
+ }
+
+ 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 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();
+ g_object_ref(m_pTreeStore);
+ GtkInstanceContainer::freeze();
+ gtk_icon_view_set_model(m_pIconView, nullptr);
+ enable_notify_events();
+ }
+
+ virtual void thaw() override
+ {
+ disable_notify_events();
+ gtk_icon_view_set_model(m_pIconView, GTK_TREE_MODEL(m_pTreeStore));
+ GtkInstanceContainer::thaw();
+ g_object_unref(m_pTreeStore);
+ 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);
+
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::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);
+
+ g_signal_handler_disconnect(m_pIconView, m_nItemActivatedSignalId);
+ g_signal_handler_disconnect(m_pIconView, m_nSelectionChangedSignalId);
+ }
+};
+
+}
+
+IMPL_LINK_NOARG(GtkInstanceIconView, async_signal_selection_changed, void*, void)
+{
+ m_pSelectionChangeEvent = nullptr;
+ signal_selection_changed();
+}
+
+namespace {
+
+class GtkInstanceSpinButton : public GtkInstanceEntry, 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);
+ GtkInstanceEntry::signal_activate();
+ }
+
+ double toGtk(int nValue) const
+ {
+ return static_cast<double>(nValue) / Power10(get_digits());
+ }
+
+ int fromGtk(double fValue) const
+ {
+ return FRound(fValue * Power10(get_digits()));
+ }
+
+public:
+ GtkInstanceSpinButton(GtkSpinButton* pButton, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceEntry(GTK_ENTRY(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)
+ {
+ }
+
+ virtual int get_value() const override
+ {
+ return fromGtk(gtk_spin_button_get_value(m_pButton));
+ }
+
+ virtual void set_value(int 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)
+ {
+ gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+
+ 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)
+ {
+ gtk_entry_set_text(GTK_ENTRY(m_pButton), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+ m_bBlank = false;
+ }
+ }
+ enable_notify_events();
+ }
+
+ virtual void set_range(int min, int max) override
+ {
+ disable_notify_events();
+ gtk_spin_button_set_range(m_pButton, toGtk(min), toGtk(max));
+ enable_notify_events();
+ }
+
+ virtual void get_range(int& min, int& 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 disable_notify_events() override
+ {
+ g_signal_handler_block(m_pButton, m_nValueChangedSignalId);
+ GtkInstanceEntry::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceEntry::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);
+ }
+};
+
+class GtkInstanceFormattedSpinButton : public GtkInstanceEntry, public virtual weld::FormattedSpinButton
+{
+private:
+ GtkSpinButton* m_pButton;
+ SvNumberFormatter* m_pFormatter;
+ Color* m_pLastOutputColor;
+ sal_uInt32 m_nFormatKey;
+ bool m_bTreatAsNumber;
+ gulong m_nValueChangedSignalId;
+ gulong m_nOutputSignalId;
+ gulong m_nInputSignalId;
+
+ bool signal_output()
+ {
+ if (!m_pFormatter)
+ return false;
+ double dVal = get_value();
+ OUString sNewText;
+ if (m_pFormatter->IsTextFormat(m_nFormatKey))
+ {
+ // first convert the number as string in standard format
+ OUString sTemp;
+ m_pFormatter->GetOutputString(dVal, 0, sTemp, &m_pLastOutputColor);
+ // then encode the string in the corresponding text format
+ m_pFormatter->GetOutputString(sTemp, m_nFormatKey, sNewText, &m_pLastOutputColor);
+ }
+ else
+ {
+ m_pFormatter->GetInputLineString(dVal, m_nFormatKey, sNewText);
+ }
+ set_text(sNewText);
+ 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)
+ {
+ if (!m_pFormatter)
+ return 0;
+
+ sal_uInt32 nFormatKey = m_nFormatKey; // IsNumberFormat changes the FormatKey!
+
+ if (m_pFormatter->IsTextFormat(nFormatKey) && m_bTreatAsNumber)
+ // for detection of values like "1,1" in fields that are formatted as text
+ nFormatKey = 0;
+
+ OUString sText(get_text());
+
+ // special treatment for percentage formatting
+ if (m_pFormatter->GetType(m_nFormatKey) == SvNumFormatType::PERCENT)
+ {
+ // the language of our format
+ LanguageType eLanguage = m_pFormatter->GetEntry(m_nFormatKey)->GetLanguage();
+ // the default number format for this language
+ sal_uLong nStandardNumericFormat = m_pFormatter->GetStandardFormat(SvNumFormatType::NUMBER, eLanguage);
+
+ sal_uInt32 nTempFormat = nStandardNumericFormat;
+ double dTemp;
+ if (m_pFormatter->IsNumberFormat(sText, nTempFormat, dTemp) &&
+ SvNumFormatType::NUMBER == m_pFormatter->GetType(nTempFormat))
+ // the string is equivalent to a number formatted one (has no % sign) -> append it
+ sText += "%";
+ // (with this, an input of '3' becomes '3%', which then by the formatter is translated
+ // into 0.03. Without this, the formatter would give us the double 3 for an input '3',
+ // which equals 300 percent.
+ }
+ if (!m_pFormatter->IsNumberFormat(sText, nFormatKey, *value))
+ return GTK_INPUT_ERROR;
+
+ 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)
+ : GtkInstanceEntry(GTK_ENTRY(pButton), pBuilder, bTakeOwnership)
+ , m_pButton(pButton)
+ , m_pFormatter(nullptr)
+ , m_pLastOutputColor(nullptr)
+ , m_nFormatKey(0)
+ , m_bTreatAsNumber(true)
+ , 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))
+ {
+ }
+
+ virtual double get_value() const override
+ {
+ return gtk_spin_button_get_value(m_pButton);
+ }
+
+ virtual void set_value(double value) override
+ {
+ disable_notify_events();
+ gtk_spin_button_set_value(m_pButton, value);
+ enable_notify_events();
+ }
+
+ virtual void set_range(double min, double max) override
+ {
+ disable_notify_events();
+ gtk_spin_button_set_range(m_pButton, min, max);
+ enable_notify_events();
+ }
+
+ virtual void get_range(double& min, double& max) const override
+ {
+ gtk_spin_button_get_range(m_pButton, &min, &max);
+ }
+
+ virtual void set_formatter(SvNumberFormatter* pFormatter) override
+ {
+ m_pFormatter = pFormatter;
+
+ // calc the default format key from the Office's UI locale
+ if (m_pFormatter)
+ {
+ // get the Office's locale and translate
+ LanguageType eSysLanguage = Application::GetSettings().GetUILanguageTag().getLanguageType( false);
+ // get the standard numeric format for this language
+ m_nFormatKey = m_pFormatter->GetStandardFormat( SvNumFormatType::NUMBER, eSysLanguage );
+ }
+ else
+ m_nFormatKey = 0;
+ signal_output();
+ }
+
+ virtual SvNumberFormatter* get_formatter() override
+ {
+ return m_pFormatter;
+ }
+
+ virtual sal_Int32 get_format_key() const override
+ {
+ return m_nFormatKey;
+ }
+
+ virtual void set_format_key(sal_Int32 nFormatKey) override
+ {
+ m_nFormatKey = nFormatKey;
+ }
+
+ virtual void treat_as_number(bool bSet) override
+ {
+ m_bTreatAsNumber = bSet;
+ }
+
+ virtual void set_digits(unsigned int digits) override
+ {
+ disable_notify_events();
+ gtk_spin_button_set_digits(m_pButton, digits);
+ enable_notify_events();
+ }
+
+ 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);
+ }
+};
+
+class GtkInstanceLabel : public GtkInstanceWidget, public virtual weld::Label
+{
+private:
+ GtkLabel* m_pLabel;
+
+ void set_text_color(const Color& rColor)
+ {
+ guint16 nRed = rColor.GetRed() << 8;
+ guint16 nGreen = rColor.GetRed() << 8;
+ guint16 nBlue = rColor.GetBlue() << 8;
+
+ PangoAttrList* pAttrs = pango_attr_list_new();
+ pango_attr_list_insert(pAttrs, pango_attr_background_new(nRed, nGreen, nBlue));
+ gtk_label_set_attributes(m_pLabel, pAttrs);
+ pango_attr_list_unref(pAttrs);
+ }
+
+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_message_type(weld::EntryMessageType eType) override
+ {
+ if (eType == weld::EntryMessageType::Error)
+ set_text_color(Application::GetSettings().GetStyleSettings().GetHighlightColor());
+ else if (eType == weld::EntryMessageType::Warning)
+ set_text_color(COL_YELLOW);
+ else
+ gtk_label_set_attributes(m_pLabel, nullptr);
+ }
+
+ virtual void set_font(const vcl::Font& rFont) override
+ {
+ PangoAttrList* pAttrList = create_attr_list(rFont);
+ gtk_label_set_attributes(m_pLabel, pAttrList);
+ pango_attr_list_unref(pAttrList);
+ }
+};
+
+}
+
+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 {
+
+class GtkInstanceTextView : public GtkInstanceContainer, public virtual weld::TextView
+{
+private:
+ GtkTextView* m_pTextView;
+ GtkTextBuffer* m_pTextBuffer;
+ GtkAdjustment* m_pVAdjustment;
+ gulong m_nChangedSignalId;
+ gulong m_nCursorPosSignalId;
+ gulong m_nVAdjustChangedSignalId;
+
+ static void signalChanged(GtkTextView*, gpointer widget)
+ {
+ GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_changed();
+ }
+
+ static void signalCursorPosition(GtkTextView*, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceTextView* pThis = static_cast<GtkInstanceTextView*>(widget);
+ pThis->signal_cursor_position();
+ }
+
+ 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)
+ : GtkInstanceContainer(GTK_CONTAINER(pTextView), pBuilder, bTakeOwnership)
+ , m_pTextView(pTextView)
+ , m_pTextBuffer(gtk_text_view_get_buffer(pTextView))
+ , m_pVAdjustment(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(pTextView)))
+ , m_nChangedSignalId(g_signal_connect(m_pTextBuffer, "changed", G_CALLBACK(signalChanged), this))
+ , m_nCursorPosSignalId(g_signal_connect(m_pTextBuffer, "notify::cursor-position", G_CALLBACK(signalCursorPosition), this))
+ , m_nVAdjustChangedSignalId(g_signal_connect(m_pVAdjustment, "value-changed", G_CALLBACK(signalVAdjustValueChanged), this))
+ {
+ }
+
+ 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();
+ GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
+ OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gtk_text_buffer_set_text(pBuffer, sText.getStr(), sText.getLength());
+ enable_notify_events();
+ }
+
+ virtual OUString get_text() const override
+ {
+ GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
+ GtkTextIter start, end;
+ gtk_text_buffer_get_bounds(pBuffer, &start, &end);
+ char* pStr = gtk_text_buffer_get_text(pBuffer, &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();
+ GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
+ gtk_text_buffer_delete_selection(pBuffer, false, gtk_text_view_get_editable(m_pTextView));
+ OString sText(OUStringToOString(rText, RTL_TEXTENCODING_UTF8));
+ gtk_text_buffer_insert_at_cursor(pBuffer, sText.getStr(), sText.getLength());
+ enable_notify_events();
+ }
+
+ virtual bool get_selection_bounds(int& rStartPos, int& rEndPos) override
+ {
+ GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
+ GtkTextIter start, end;
+ gtk_text_buffer_get_selection_bounds(pBuffer, &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();
+ GtkTextBuffer* pBuffer = gtk_text_view_get_buffer(m_pTextView);
+ GtkTextIter start, end;
+ gtk_text_buffer_get_iter_at_offset(pBuffer, &start, nStartPos);
+ gtk_text_buffer_get_iter_at_offset(pBuffer, &end, nEndPos);
+ gtk_text_buffer_select_range(pBuffer, &start, &end);
+ GtkTextMark* mark = gtk_text_buffer_create_mark(pBuffer, "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 void set_monospace(bool bMonospace) override
+ {
+ gtk_text_view_set_monospace(m_pTextView, bMonospace);
+ }
+
+ 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);
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkInstanceContainer::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);
+ }
+
+ 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
+ {
+ g_signal_handler_disconnect(m_pVAdjustment, m_nVAdjustChangedSignalId);
+ g_signal_handler_disconnect(m_pTextBuffer, m_nChangedSignalId);
+ g_signal_handler_disconnect(m_pTextBuffer, m_nCursorPosSignalId);
+ }
+};
+
+// IMHandler
+class IMHandler;
+
+AtkObject* (*default_drawing_area_get_accessible)(GtkWidget *widget);
+
+class GtkInstanceDrawingArea : public GtkInstanceWidget, public virtual weld::DrawingArea
+{
+private:
+ GtkDrawingArea* m_pDrawingArea;
+ a11yref m_xAccessible;
+ AtkObject *m_pAccessible;
+ ScopedVclPtrInstance<VirtualDevice> m_xDevice;
+ std::unique_ptr<IMHandler> m_xIMHandler;
+ cairo_surface_t* m_pSurface;
+ gulong m_nDrawSignalId;
+ gulong m_nStyleUpdatedSignalId;
+ gulong m_nQueryTooltip;
+ gulong m_nPopupMenu;
+ gulong m_nScrollEvent;
+
+ static gboolean signalDraw(GtkWidget*, cairo_t* cr, gpointer widget)
+ {
+ GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
+ SolarMutexGuard aGuard;
+ pThis->signal_draw(cr);
+ return false;
+ }
+ void signal_draw(cairo_t* cr)
+ {
+ GdkRectangle rect;
+ if (!m_pSurface || !gdk_cairo_get_clip_rectangle(cr, &rect))
+ return;
+ 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
+ {
+ m_xDevice->SetOutputSizePixel(Size(nWidth, nHeight));
+ m_pSurface = get_underlying_cairo_surface(*m_xDevice);
+ GtkInstanceWidget::signal_size_allocate(nWidth, nHeight);
+ }
+ static void signalStyleUpdated(GtkWidget*, gpointer widget)
+ {
+ GtkInstanceDrawingArea* pThis = static_cast<GtkInstanceDrawingArea*>(widget);
+ SolarMutexGuard aGuard;
+ return pThis->signal_style_updated();
+ }
+ 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);
+ }
+ bool signal_scroll(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);
+ }
+public:
+ GtkInstanceDrawingArea(GtkDrawingArea* pDrawingArea, GtkInstanceBuilder* pBuilder, const a11yref& rA11y, bool bTakeOwnership)
+ : GtkInstanceWidget(GTK_WIDGET(pDrawingArea), pBuilder, bTakeOwnership)
+ , m_pDrawingArea(pDrawingArea)
+ , m_xAccessible(rA11y)
+ , m_pAccessible(nullptr)
+ , m_xDevice(DeviceFormat::DEFAULT)
+ , m_pSurface(nullptr)
+ , m_nDrawSignalId(g_signal_connect(m_pDrawingArea, "draw", G_CALLBACK(signalDraw), this))
+ , m_nStyleUpdatedSignalId(g_signal_connect(m_pDrawingArea,"style-updated", G_CALLBACK(signalStyleUpdated), this))
+ , m_nQueryTooltip(g_signal_connect(m_pDrawingArea, "query-tooltip", G_CALLBACK(signalQueryTooltip), this))
+ , 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))
+ {
+ 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());
+ }
+
+ 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;
+ }
+
+ 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));
+ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(m_pDrawingArea)), pCursor);
+ }
+
+ 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);
+ }
+
+ 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
+ {
+ 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());
+ }
+
+ virtual void queue_resize() override
+ {
+ gtk_widget_queue_resize(GTK_WIDGET(m_pDrawingArea));
+ }
+
+ 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_parent should only be called on a vcl impl");
+ return uno::Reference<css::accessibility::XAccessibleRelationSet>();
+ }
+
+ virtual Point get_accessible_location() override
+ {
+ AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
+ gint x(0), y(0);
+ if (pAtkObject && ATK_IS_COMPONENT(pAtkObject))
+ atk_component_get_extents(ATK_COMPONENT(pAtkObject), &x, &y, nullptr, nullptr, ATK_XY_WINDOW);
+ return Point(x, y);
+ }
+
+ virtual void set_accessible_name(const OUString& rName) override
+ {
+ AtkObject* pAtkObject = default_drawing_area_get_accessible(m_pWidget);
+ if (!pAtkObject)
+ return;
+ atk_object_set_name(pAtkObject, OUStringToOString(rName, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ virtual OUString get_accessible_name() const override
+ {
+ 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);
+ }
+
+ virtual OUString get_accessible_description() const override
+ {
+ 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);
+ }
+
+ 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
+ {
+ g_object_steal_data(G_OBJECT(m_pDrawingArea), "g-lo-GtkInstanceDrawingArea");
+ if (m_pAccessible)
+ g_object_unref(m_pAccessible);
+ css::uno::Reference<css::lang::XComponent> xComp(m_xAccessible, css::uno::UNO_QUERY);
+ if (xComp.is())
+ xComp->dispose();
+ g_signal_handler_disconnect(m_pDrawingArea, m_nScrollEvent);
+ g_signal_handler_disconnect(m_pDrawingArea, m_nPopupMenu);
+ g_signal_handler_disconnect(m_pDrawingArea, m_nQueryTooltip);
+ g_signal_handler_disconnect(m_pDrawingArea, m_nStyleUpdatedSignalId);
+ g_signal_handler_disconnect(m_pDrawingArea, m_nDrawSignalId);
+ }
+
+ virtual OutputDevice& get_ref_device() override
+ {
+ return *m_xDevice;
+ }
+
+ bool signal_command(const CommandEvent& rCEvt)
+ {
+ return m_aCommandHdl.Call(rCEvt);
+ }
+};
+
+class IMHandler
+{
+private:
+ GtkInstanceDrawingArea* m_pArea;
+ 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_nFocusInSignalId(g_signal_connect(m_pArea->getWidget(), "focus-in-event", G_CALLBACK(signalFocusIn), this))
+ , m_nFocusOutSignalId(g_signal_connect(m_pArea->getWidget(), "focus-out-event", G_CALLBACK(signalFocusOut), this))
+ , m_bExtTextInput(false)
+ {
+ 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);
+
+ GtkWidget* pWidget = m_pArea->getWidget();
+ if (!gtk_widget_get_realized(pWidget))
+ gtk_widget_realize(pWidget);
+ GdkWindow* pWin = gtk_widget_get_window(pWidget);
+ gtk_im_context_set_client_window(m_pIMContext, pWin);
+ 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);
+ }
+
+ static gboolean signalFocusIn(GtkWidget*, GdkEvent*, gpointer im_handler)
+ {
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+ pThis->signalFocus(true);
+ return false;
+ }
+
+ static gboolean signalFocusOut(GtkWidget*, GdkEvent*, gpointer im_handler)
+ {
+ IMHandler* pThis = static_cast<IMHandler*>(im_handler);
+ pThis->signalFocus(false);
+ return false;
+ }
+
+ ~IMHandler()
+ {
+ EndExtTextInput();
+
+ g_signal_handler_disconnect(m_pArea->getWidget(), m_nFocusOutSignalId);
+ g_signal_handler_disconnect(m_pArea->getWidget(), m_nFocusInSignalId);
+
+ // first give IC a chance to deinitialize
+ gtk_im_context_set_client_window(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);
+
+ // 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);
+
+ 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);
+
+ OUString sSurroundingText;
+ int nCursorIndex = pThis->m_pArea->im_context_get_surrounding(sSurroundingText);
+
+ if (nCursorIndex != -1)
+ {
+ OString sUTF = OUStringToOString(sSurroundingText, RTL_TEXTENCODING_UTF8);
+ OUString sCursorText(sSurroundingText.copy(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);
+
+ OUString sSurroundingText;
+ sal_Int32 nCursorIndex = pThis->m_pArea->im_context_get_surrounding(sSurroundingText);
+
+ if (nCursorIndex != -1)
+ {
+ // Note that offset and n_chars are in characters not in bytes
+ // which differs from the usage other places in GtkIMContext
+
+ if (nOffset > 0)
+ {
+ while (nCursorIndex < sSurroundingText.getLength())
+ sSurroundingText.iterateCodePoints(&nCursorIndex, 1);
+ }
+ else if (nOffset < 0)
+ {
+ while (nCursorIndex > 0)
+ sSurroundingText.iterateCodePoints(&nCursorIndex, -1);
+ }
+
+ sal_Int32 nCursorEndIndex(nCursorIndex);
+ sal_Int32 nCount(0);
+ while (nCount < nChars && nCursorEndIndex < sSurroundingText.getLength())
+ ++nCount;
+
+ bRet = pThis->m_pArea->im_context_delete_surrounding(Selection(nCursorIndex, nCursorEndIndex));
+ }
+
+ 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);
+ 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);
+ pThis->updateIMSpotLocation();
+ pThis->EndExtTextInput();
+ }
+};
+
+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);
+}
+
+}
+
+namespace {
+
+GtkBuilder* makeComboBoxBuilder()
+{
+ OUString aUri(VclBuilderContainer::getUIRootDir() + "vcl/ui/combobox.ui");
+ OUString aPath;
+ osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
+ return gtk_builder_new_from_file(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr());
+}
+
+struct GtkTreeRowReferenceDeleter
+{
+ void operator()(GtkTreeRowReference* p) const
+ {
+ gtk_tree_row_reference_free(p);
+ }
+};
+
+// 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_activate(GtkMenuItem*) override
+ {
+ gtk_toggle_button_set_active(m_pComboBox, false);
+ }
+};
+
+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;
+ std::unique_ptr<CustomRenderMenuButtonHelper> m_xCustomMenuButtonHelper;
+ std::unique_ptr<vcl::Font> m_xFont;
+ 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)
+ set_active_text(aText);
+ 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 toggle_menu()
+ {
+ 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;
+ }
+
+ do_ungrab(GTK_WIDGET(m_pMenuWindow));
+
+ gtk_widget_hide(GTK_WIDGET(m_pMenuWindow));
+
+ // 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 = gtk_widget_get_toplevel(m_pToggleButton);
+ GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr;
+ if (pFrame)
+ pFrame->UnblockTooltip();
+ }
+ 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);
+
+ show_menu(pComboBox, m_pMenuWindow);
+ }
+ }
+
+ virtual void signal_popup_toggled() override
+ {
+ m_aQuickSelectionEngine.Reset();
+
+ toggle_menu();
+
+ bool bIsShown = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton));
+ if (m_bPopupActive != bIsShown)
+ {
+ m_bPopupActive = bIsShown;
+ ComboBox::signal_popup_toggled();
+ if (!m_bPopupActive)
+ {
+ //restore focus to the entry view when the popup is gone, which
+ //is what the vcl case does, to ease the transition a little
+ grab_focus();
+ }
+ }
+ }
+
+ 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, const OUString& 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(const OUString& 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(GtkTreePath* path)
+ {
+ bool bFound = false;
+ for (auto& a : m_aSeparatorRows)
+ {
+ 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;
+ }
+
+ 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 = gtk_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(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ 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 ? 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 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 deactive
+ 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)
+ {
+ if (pos == -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
+ {
+ GtkTreePath* 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);
+ gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false);
+ if (m_pCellView)
+ gtk_cell_view_set_displayed_row(m_pCellView, path);
+ 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
+ {
+ //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(gtk_widget_get_window(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;
+ }
+ }
+
+ 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(const OUString& rStr, bool bSearchMRU) const
+ {
+ return find(rStr, m_nTextCol, bSearchMRU);
+ }
+
+ int find_id_including_mru(const OUString& 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, const OUString& 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, "", 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, const OUString& rText, const OUString* pId, const OUString* pIconName, 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)
+ {
+ 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;
+ }
+
+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_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);
+ 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
+ {
+ if (m_nMRUCount && pos != -1)
+ pos += (m_nMRUCount + 1);
+ set_active_including_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();
+ if (!bKeepExisting)
+ clear();
+ GtkTreeIter iter;
+ for (const auto& rItem : rItems)
+ {
+ insert_row(GTK_LIST_STORE(m_pTreeModel), iter, -1, 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
+ {
+ if (m_nMRUCount && pos != -1)
+ pos += (m_nMRUCount + 1);
+ insert_including_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_entry_font(const vcl::Font& rFont) override
+ {
+ m_xFont.reset(new vcl::Font(rFont));
+ PangoAttrList* pAttrList = create_attr_list(rFont);
+ assert(m_pEntry);
+ gtk_entry_set_attributes(GTK_ENTRY(m_pEntry), pAttrList);
+ pango_attr_list_unref(pAttrList);
+ }
+
+ virtual vcl::Font get_entry_font() override
+ {
+ if (m_xFont)
+ return *m_xFont;
+ 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();
+ g_object_ref(m_pTreeModel);
+ GtkInstanceContainer::freeze();
+ gtk_tree_view_set_model(m_pTreeView, nullptr);
+ 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 (m_xSorter)
+ {
+ GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
+ }
+ gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
+
+ GtkInstanceContainer::thaw();
+ g_object_unref(m_pTreeModel);
+ 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(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this);
+ weld::Widget::connect_focus_in(rLink);
+ }
+
+ virtual void connect_focus_out(const Link<Widget&, void>& rLink) override
+ {
+ if (!m_nToggleFocusOutSignalId)
+ m_nToggleFocusOutSignalId = g_signal_connect(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this);
+ weld::Widget::connect_focus_out(rLink);
+ }
+
+ virtual void grab_focus() override
+ {
+ disable_notify_events();
+ if (m_pEntry)
+ gtk_widget_grab_focus(m_pEntry);
+ else
+ gtk_widget_grab_focus(m_pToggleButton);
+ enable_notify_events();
+ }
+
+ 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_surface_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
+ {
+ 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);
+ }
+};
+
+}
+
+bool custom_cell_renderer_surface_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);
+
+ CustomCellRendererSurface *cellsurface = CUSTOM_CELL_RENDERER_SURFACE(cell);
+
+ GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(g_value_get_pointer(&value));
+
+ Size aSize;
+
+ if (pWidget)
+ {
+ ensure_device(cellsurface, pWidget);
+ if (GtkInstanceTreeView* pTreeView = dynamic_cast<GtkInstanceTreeView*>(pWidget))
+ aSize = pTreeView->call_signal_custom_get_size(*cellsurface->device, sId);
+ else if (GtkInstanceComboBox* pComboBox = dynamic_cast<GtkInstanceComboBox*>(pWidget))
+ aSize = pComboBox->call_signal_custom_get_size(*cellsurface->device);
+ }
+
+ 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_surface_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);
+
+ CustomCellRendererSurface *cellsurface = CUSTOM_CELL_RENDERER_SURFACE(cell);
+
+ GtkInstanceWidget* pWidget = static_cast<GtkInstanceWidget*>(g_value_get_pointer(&value));
+
+ if (!pWidget)
+ return;
+
+ 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);
+
+ if (GtkInstanceTreeView* pTreeView = dynamic_cast<GtkInstanceTreeView*>(pWidget))
+ pTreeView->call_signal_custom_render(*cellsurface->device, tools::Rectangle(Point(0, 0), aSize), flags & GTK_CELL_RENDERER_SELECTED, sId);
+ else if (GtkInstanceComboBox* pComboBox = dynamic_cast<GtkInstanceComboBox*>(pWidget))
+ pComboBox->call_signal_custom_render(*cellsurface->device, tools::Rectangle(Point(0, 0), aSize), flags & GTK_CELL_RENDERER_SELECTED, sId);
+ cairo_surface_mark_dirty(pSurface);
+
+ cairo_set_source_surface(cr, pSurface, cell_area->x, cell_area->y);
+ cairo_paint(cr);
+}
+
+namespace {
+
+class GtkInstanceEntryTreeView : public GtkInstanceContainer, public virtual weld::EntryTreeView
+{
+private:
+ GtkInstanceEntry* m_pEntry;
+ GtkInstanceTreeView* m_pTreeView;
+ gulong m_nKeyPressSignalId;
+ gulong m_nEntryInsertTextSignalId;
+ guint m_nAutoCompleteIdleId;
+ bool m_bAutoCompleteCaseSensitive;
+ bool m_bTreeChange;
+
+ 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);
+ }
+
+ 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, 0, nStart, true);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case insensitive, but from start
+ nPos = m_pTreeView->starts_with(aStartText, 0, 0, true);
+ }
+
+ if (!m_bAutoCompleteCaseSensitive)
+ {
+ // Try match case insensitive from current position
+ nPos = m_pTreeView->starts_with(aStartText, 0, nStart, false);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case insensitive, but from start
+ nPos = m_pTreeView->starts_with(aStartText, 0, 0, false);
+ }
+ }
+
+ if (nPos == -1)
+ {
+ // Try match case sensitive from current position
+ nPos = m_pTreeView->starts_with(aStartText, 0, nStart, true);
+ if (nPos == -1 && nStart != 0)
+ {
+ // Try match case sensitive, but from start
+ nPos = m_pTreeView->starts_with(aStartText, 0, 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:
+ GtkInstanceEntryTreeView(GtkContainer* pContainer, GtkInstanceBuilder* pBuilder, bool bTakeOwnership,
+ std::unique_ptr<weld::Entry> xEntry, std::unique_ptr<weld::TreeView> xTreeView)
+ : 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();
+ m_nKeyPressSignalId = g_signal_connect(pWidget, "key-press-event", G_CALLBACK(signalKeyPress), this);
+ 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_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);
+ g_signal_handler_block(pWidget, m_nKeyPressSignalId);
+ m_pTreeView->disable_notify_events();
+ GtkInstanceContainer::disable_notify_events();
+ }
+
+ virtual void enable_notify_events() override
+ {
+ GtkWidget* pWidget = m_pEntry->getWidget();
+ g_signal_handler_unblock(pWidget, m_nKeyPressSignalId);
+ g_signal_handler_unblock(pWidget, m_nEntryInsertTextSignalId);
+ m_pTreeView->enable_notify_events();
+ GtkInstanceContainer::disable_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();
+ g_signal_handler_disconnect(pWidget, m_nKeyPressSignalId);
+ g_signal_handler_disconnect(pWidget, m_nEntryInsertTextSignalId);
+ }
+};
+
+class GtkInstanceExpander : public GtkInstanceContainer, public virtual weld::Expander
+{
+private:
+ GtkExpander* m_pExpander;
+ gulong m_nSignalId;
+
+ static void signalExpanded(GtkExpander* pExpander, GParamSpec*, gpointer widget)
+ {
+ GtkInstanceExpander* pThis = static_cast<GtkInstanceExpander*>(widget);
+ SolarMutexGuard aGuard;
+
+ GtkWidget *pToplevel = gtk_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);
+ }
+
+ pThis->signal_expanded();
+ }
+
+public:
+ GtkInstanceExpander(GtkExpander* pExpander, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
+ : GtkInstanceContainer(GTK_CONTAINER(pExpander), pBuilder, bTakeOwnership)
+ , m_pExpander(pExpander)
+ , m_nSignalId(g_signal_connect(m_pExpander, "notify::expanded", G_CALLBACK(signalExpanded), this))
+ {
+ }
+
+ 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
+ {
+ g_signal_handler_disconnect(m_pExpander, m_nSignalId);
+ }
+};
+
+ gboolean signalTooltipQuery(GtkWidget* pWidget, gint /*x*/, gint /*y*/,
+ gboolean /*keyboard_mode*/, GtkTooltip *tooltip)
+ {
+ const ImplSVHelpData& aHelpData = ImplGetSVHelpData();
+ if (aHelpData.mbBalloonHelp)
+ {
+ /*Current 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;
+ }
+ }
+
+ /*This is how I would prefer things to be, only a few like this though*/
+ 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;
+ }
+ }
+
+ const char* pDesc = gtk_widget_get_tooltip_text(pWidget);
+ if (pDesc && pDesc[0])
+ {
+ gtk_tooltip_set_text(tooltip, pDesc);
+ return true;
+ }
+
+ return false;
+ }
+}
+
+namespace
+{
+
+AtkObject* drawing_area_get_accessibity(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_accessibity;
+ 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;
+ }
+}
+
+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;
+ std::vector<GtkLabel*> m_aMnemonicLabels;
+
+ VclPtr<SystemChildWindow> m_xInterimGlue;
+
+ 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);
+ const gchar* icon_name;
+ gtk_image_get_icon_name(pImage, &icon_name, nullptr);
+ if (icon_name)
+ {
+ OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
+ GdkPixbuf* pixbuf = load_icon_by_name_theme_lang(aIconName, m_aIconTheme, m_aUILang);
+ if (pixbuf)
+ {
+ gtk_image_set_from_pixbuf(pImage, pixbuf);
+ g_object_unref(pixbuf);
+ }
+ }
+ }
+ else if (GTK_IS_TOOL_BUTTON(pWidget))
+ {
+ GtkToolButton* pToolButton = GTK_TOOL_BUTTON(pWidget);
+ const gchar* icon_name = gtk_tool_button_get_icon_name(pToolButton);
+ if (icon_name)
+ {
+ OUString aIconName(icon_name, strlen(icon_name), RTL_TEXTENCODING_UTF8);
+ GdkPixbuf* pixbuf = load_icon_by_name_theme_lang(aIconName, m_aIconTheme, m_aUILang);
+ if (pixbuf)
+ {
+ GtkWidget* pImage = gtk_image_new_from_pixbuf(pixbuf);
+ g_object_unref(pixbuf);
+ 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);
+ }
+ }
+
+ //set helpids
+ const gchar* pStr = gtk_buildable_get_name(GTK_BUILDABLE(pWidget));
+ size_t nLen = pStr ? strlen(pStr) : 0;
+ if (nLen)
+ {
+ OString sBuildableName(pStr, nLen);
+ 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);
+ }
+
+ // expand placeholder and collect potentially missing mnemonics
+ if (GTK_IS_BUTTON(pWidget))
+ {
+ GtkButton* pButton = GTK_BUTTON(pWidget);
+ if (m_pStringReplace != nullptr)
+ {
+ OUString aLabel(get_label(pButton));
+ if (!aLabel.isEmpty())
+ set_label(pButton, (*m_pStringReplace)(aLabel));
+ }
+ if (gtk_button_get_use_underline(pButton) && !gtk_button_get_use_stock(pButton))
+ m_aMnemonicButtons.push_back(pButton);
+ }
+ else if (GTK_IS_LABEL(pWidget))
+ {
+ GtkLabel* pLabel = GTK_LABEL(pWidget);
+ if (m_pStringReplace != nullptr)
+ {
+ 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 != nullptr)
+ {
+ 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);
+ }
+ }
+ else if (GTK_IS_WINDOW(pWidget))
+ {
+ if (m_pStringReplace != nullptr) {
+ 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));
+ }
+public:
+ GtkInstanceBuilder(GtkWidget* pParent, const OUString& rUIRoot, const OUString& rUIFile, SystemChildWindow* pInterimGlue)
+ : weld::Builder()
+ , m_pStringReplace(Translate::GetReadStringHook())
+ , m_pParentWidget(pParent)
+ , m_nNotifySignalId(0)
+ , m_xInterimGlue(pInterimGlue)
+ {
+ OUString sHelpRoot(rUIFile);
+ ensure_intercept_drawing_area_accessibility();
+ ensure_disable_ctrl_page_up_down_bindings();
+
+ sal_Int32 nIdx = sHelpRoot.lastIndexOf('.');
+ if (nIdx != -1)
+ sHelpRoot = sHelpRoot.copy(0, nIdx);
+ sHelpRoot += OUString('/');
+ m_aUtf8HelpRoot = OUStringToOString(sHelpRoot, RTL_TEXTENCODING_UTF8);
+ m_aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ m_aUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
+
+ OUString aUri(rUIRoot + rUIFile);
+ OUString aPath;
+ osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
+ m_pBuilder = gtk_builder_new();
+ m_nNotifySignalId = g_signal_connect_data(G_OBJECT(m_pBuilder), "notify", G_CALLBACK(signalNotify), this, nullptr, G_CONNECT_AFTER);
+ auto rc = gtk_builder_add_from_file(m_pBuilder, OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr(), nullptr);
+ assert(rc && "could not load UI file");
+ (void) rc;
+
+ 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());
+ }
+ }
+
+ void GenerateMissingMnemonics()
+ {
+ MnemonicGenerator aMnemonicGenerator('_');
+ for (const auto a : m_aMnemonicButtons)
+ aMnemonicGenerator.RegisterMnemonic(get_label(a));
+ for (const auto a : m_aMnemonicLabels)
+ aMnemonicGenerator.RegisterMnemonic(get_label(a));
+
+ for (const auto a : m_aMnemonicButtons)
+ {
+ OUString aLabel(get_label(a));
+ OUString aNewLabel = aMnemonicGenerator.CreateMnemonic(aLabel);
+ if (aLabel == aNewLabel)
+ continue;
+ set_label(a, aNewLabel);
+ }
+ 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();
+ 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", false));
+ if (xNotebook)
+ {
+ if (GtkInstanceContainer* pPage = dynamic_cast<GtkInstanceContainer*>(xNotebook->get_page(xNotebook->get_current_page_ident())))
+ {
+ GtkWidget* pContainer = pPage->getWidget();
+ GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pContainer));
+ GList* pChild = g_list_first(pChildren);
+ if (pChild)
+ {
+ GtkWidget* pPageWidget = static_cast<GtkWidget*>(pChild->data);
+ sPageHelpId = ::get_help_id(pPageWidget);
+ }
+ g_list_free(pChildren);
+ }
+ }
+ return sPageHelpId;
+ }
+
+ virtual ~GtkInstanceBuilder() override
+ {
+ g_slist_free(m_pObjectList);
+ g_object_unref(m_pBuilder);
+ 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_widget_get_toplevel(pWidget) == pWidget && !GTK_IS_POPOVER(pWidget))
+ gtk_container_add(GTK_CONTAINER(m_pParentWidget), pWidget);
+ }
+
+ virtual std::unique_ptr<weld::MessageDialog> weld_message_dialog(const OString &id, bool bTakeOwnership) 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(gtk_widget_get_toplevel(m_pParentWidget)));
+ return std::make_unique<GtkInstanceMessageDialog>(pMessageDialog, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Assistant> weld_assistant(const OString &id, bool bTakeOwnership) 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(gtk_widget_get_toplevel(m_pParentWidget)));
+ return std::make_unique<GtkInstanceAssistant>(pAssistant, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Dialog> weld_dialog(const OString &id, bool bTakeOwnership) 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(gtk_widget_get_toplevel(m_pParentWidget)));
+ return std::make_unique<GtkInstanceDialog>(pDialog, this, bTakeOwnership);
+ }
+
+ 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));
+ gtk_container_add(GTK_CONTAINER(pContentArea), pTopLevel);
+ gtk_widget_show_all(pTopLevel);
+ }
+
+ if (m_pParentWidget)
+ gtk_window_set_transient_for(pDialog, GTK_WINDOW(gtk_widget_get_toplevel(m_pParentWidget)));
+ return std::make_unique<GtkInstanceDialog>(pDialog, this, true);
+ }
+
+ virtual std::unique_ptr<weld::Window> weld_window(const OString &id, bool bTakeOwnership) override
+ {
+ GtkWindow* pWindow = GTK_WINDOW(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ return pWindow ? std::make_unique<GtkInstanceWindow>(pWindow, this, bTakeOwnership) : nullptr;
+ }
+
+ virtual std::unique_ptr<weld::Widget> weld_widget(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Container> weld_container(const OString &id, bool bTakeOwnership) override
+ {
+ GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pContainer)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
+ return std::make_unique<GtkInstanceContainer>(pContainer, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Box> weld_box(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Paned> weld_paned(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Frame> weld_frame(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::ScrolledWindow> weld_scrolled_window(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Notebook> weld_notebook(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Button> weld_button(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::MenuButton> weld_menu_button(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::LinkButton> weld_link_button(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::ToggleButton> weld_toggle_button(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::RadioButton> weld_radio_button(const OString &id, bool bTakeOwnership) override
+ {
+ GtkRadioButton* pRadioButton = GTK_RADIO_BUTTON(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pRadioButton)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pRadioButton));
+ return std::make_unique<GtkInstanceRadioButton>(pRadioButton, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::CheckButton> weld_check_button(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Scale> weld_scale(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::ProgressBar> weld_progress_bar(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Spinner> weld_spinner(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Image> weld_image(const OString &id, bool bTakeOwnership) override
+ {
+ GtkImage* pImage = GTK_IMAGE(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pImage)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pImage));
+ return std::make_unique<GtkInstanceImage>(pImage, this, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Calendar> weld_calendar(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Entry> weld_entry(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::SpinButton> weld_spin_button(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::MetricSpinButton> weld_metric_spin_button(const OString& id, FieldUnit eUnit,
+ bool bTakeOwnership) override
+ {
+ return std::make_unique<weld::MetricSpinButton>(weld_spin_button(id, bTakeOwnership), eUnit);
+ }
+
+ virtual std::unique_ptr<weld::FormattedSpinButton> weld_formatted_spin_button(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::TimeSpinButton> weld_time_spin_button(const OString& id, TimeFieldFormat eFormat,
+ bool bTakeOwnership) override
+ {
+ return std::make_unique<weld::TimeSpinButton>(weld_spin_button(id, bTakeOwnership), eFormat);
+ }
+
+ virtual std::unique_ptr<weld::ComboBox> weld_combo_box(const OString &id, bool bTakeOwnership) 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));
+
+ /* 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::TreeView> weld_tree_view(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::IconView> weld_icon_view(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::EntryTreeView> weld_entry_tree_view(const OString& containerid, const OString& entryid, const OString& treeviewid, bool bTakeOwnership) override
+ {
+ GtkContainer* pContainer = GTK_CONTAINER(gtk_builder_get_object(m_pBuilder, containerid.getStr()));
+ if (!pContainer)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pContainer));
+ return std::make_unique<GtkInstanceEntryTreeView>(pContainer, this, bTakeOwnership,
+ weld_entry(entryid, bTakeOwnership),
+ weld_tree_view(treeviewid, bTakeOwnership));
+ }
+
+ virtual std::unique_ptr<weld::Label> weld_label(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::TextView> weld_text_view(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Expander> weld_expander(const OString &id, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::DrawingArea> weld_drawing_area(const OString &id, const a11yref& rA11y,
+ FactoryFunction /*pUITestFactoryFunction*/, void* /*pUserData*/, bool bTakeOwnership) 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, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Menu> weld_menu(const OString &id, bool bTakeOwnership) override
+ {
+ GtkMenu* pMenu = GTK_MENU(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pMenu)
+ return nullptr;
+ return std::make_unique<GtkInstanceMenu>(pMenu, bTakeOwnership);
+ }
+
+ virtual std::unique_ptr<weld::Toolbar> weld_toolbar(const OString &id, bool bTakeOwnership) override
+ {
+ GtkToolbar* pToolbar = GTK_TOOLBAR(gtk_builder_get_object(m_pBuilder, id.getStr()));
+ if (!pToolbar)
+ return nullptr;
+ auto_add_parentless_widgets_to_container(GTK_WIDGET(pToolbar));
+ return std::make_unique<GtkInstanceToolbar>(pToolbar, this, bTakeOwnership);
+ }
+
+ 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)
+ {
+ // 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)
+ {
+ GList* pChildren = gtk_container_get_children(pContainer);
+ GList* pChild = g_list_first(pChildren);
+ if (pChild)
+ {
+ GtkWidget* pContentWidget = static_cast<GtkWidget*>(pChild->data);
+ sHelpId = ::get_help_id(pContentWidget);
+ }
+ g_list_free(pChildren);
+ }
+ }
+ }
+ 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;
+ }
+}
+
+weld::Builder* GtkInstance::CreateBuilder(weld::Widget* pParent, const OUString& rUIRoot, const OUString& rUIFile)
+{
+ GtkInstanceWidget* pParentWidget = dynamic_cast<GtkInstanceWidget*>(pParent);
+ if (pParent && !pParentWidget) //remove when complete
+ return SalInstance::CreateBuilder(pParent, rUIRoot, rUIFile);
+ GtkWidget* pBuilderParent = pParentWidget ? pParentWidget->getWidget() : nullptr;
+ return new GtkInstanceBuilder(pBuilderParent, rUIRoot, rUIFile, nullptr);
+}
+
+// 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;
+}
+
+weld::Builder* GtkInstance::CreateInterimBuilder(vcl::Window* pParent, const OUString& rUIRoot, const OUString& rUIFile)
+{
+ // 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);
+ gtk_widget_show_all(pWindow);
+
+ // build the widget tree as a child of the GtkEventBox GtkGrid parent
+ return new GtkInstanceBuilder(pWindow, rUIRoot, rUIFile, xEmbedWindow.get());
+}
+
+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(gtk_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);
+ gtk_container_add(GTK_CONTAINER(pParent), pGstWidget);
+ g_object_unref(pGstWidget);
+ gtk_widget_show_all(pParent);
+
+ return pVideosink;
+#else
+ (void)pWindow;
+ return nullptr;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3gtkobject.cxx b/vcl/unx/gtk3/gtk3gtkobject.cxx
new file mode 100644
index 000000000..cb60406ec
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3gtkobject.cxx
@@ -0,0 +1,463 @@
+/* -*- 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>
+
+GtkSalObjectBase::GtkSalObjectBase(GtkSalFrame* pParent)
+ : m_pSocket(nullptr)
+ , m_pParent(pParent)
+ , m_pRegion(nullptr)
+{
+ if (!m_pParent)
+ return;
+}
+
+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
+ m_aSystemData.aWindow = m_pParent->GetNativeWindowHandle(m_pSocket);
+ 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::Gtk3;
+ 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 );
+}
+
+GtkSalObjectBase::~GtkSalObjectBase()
+{
+ if( m_pRegion )
+ {
+ cairo_region_destroy( m_pRegion );
+ }
+}
+
+GtkSalObject::~GtkSalObject()
+{
+ if( m_pSocket )
+ {
+ // 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 );
+ }
+}
+
+void GtkSalObject::ResetClipRegion()
+{
+ if( m_pSocket )
+ gdk_window_shape_combine_region( gtk_widget_get_window(m_pSocket), nullptr, 0, 0 );
+}
+
+void GtkSalObjectBase::BeginSetClipRegion( sal_uInt32 )
+{
+ if (m_pRegion)
+ cairo_region_destroy(m_pRegion);
+ m_pRegion = cairo_region_create();
+}
+
+void GtkSalObjectBase::UnionClipRegion( long nX, long nY, long nWidth, 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( m_pSocket )
+ gdk_window_shape_combine_region( gtk_widget_get_window(m_pSocket), m_pRegion, 0, 0 );
+}
+
+void GtkSalObject::SetPosSize(long nX, long nY, long nWidth, 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 );
+ m_pParent->nopaint_container_resize_children(GTK_CONTAINER(pContainer));
+ }
+}
+
+void GtkSalObject::Reparent(SalFrame* pFrame)
+{
+ GtkSalFrame* pNewParent = static_cast<GtkSalFrame*>(pFrame);
+ if (m_pSocket)
+ {
+ GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pSocket));
+
+ gint nX(0), nY(0);
+ gtk_container_child_get(GTK_CONTAINER(pContainer), m_pSocket,
+ "x", &nX,
+ "y", &nY,
+ nullptr);
+
+ g_object_ref(m_pSocket);
+ gtk_container_remove(GTK_CONTAINER(pContainer), m_pSocket);
+
+ 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);
+ GtkRequisition size;
+ gtk_widget_get_preferred_size(m_pSocket, nullptr, &size);
+ 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;
+}
+
+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;
+}
+
+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( 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 ) ) );
+}
+
+GtkSalObjectWidgetClip::GtkSalObjectWidgetClip(GtkSalFrame* pParent, bool bShow)
+ : GtkSalObjectBase(pParent)
+ , m_pScrolledWindow(nullptr)
+{
+ if( !pParent )
+ return;
+
+ m_pScrolledWindow = gtk_scrolled_window_new(nullptr, nullptr);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(m_pScrolledWindow),
+ GTK_POLICY_EXTERNAL, GTK_POLICY_EXTERNAL);
+ g_signal_connect(m_pScrolledWindow, "scroll-event", G_CALLBACK(signalScroll), this);
+
+ // insert into container
+ gtk_fixed_put( pParent->getFixedContainer(),
+ m_pScrolledWindow,
+ 0, 0 );
+
+ // deliberately without adjustments to avoid gtk's auto adjustment on changing focus
+ GtkWidget* pViewPort = gtk_viewport_new(nullptr, nullptr);
+
+ // force in a fake background of a suitable color
+ GtkStyleContext *pWidgetContext = gtk_widget_get_style_context(pViewPort);
+ GtkCssProvider* pBgCssProvider = gtk_css_provider_new();
+ OUString sColor = Application::GetSettings().GetStyleSettings().GetDialogColor().AsRGBHexString();
+ OUString aBuffer = "* { background-color: #" + sColor + "; }";
+ OString aResult = OUStringToOString(aBuffer, RTL_TEXTENCODING_UTF8);
+ gtk_css_provider_load_from_data(pBgCssProvider, aResult.getStr(), aResult.getLength(), nullptr);
+ gtk_style_context_add_provider(pWidgetContext, GTK_STYLE_PROVIDER(pBgCssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ gtk_container_add(GTK_CONTAINER(m_pScrolledWindow), pViewPort);
+ gtk_widget_show(pViewPort);
+
+ // our plug window
+ m_pSocket = gtk_grid_new();
+ gtk_container_add(GTK_CONTAINER(pViewPort), m_pSocket);
+ gtk_widget_show(m_pSocket);
+
+ Show(bShow);
+
+ Init();
+
+ g_signal_connect( G_OBJECT(m_pSocket), "destroy", G_CALLBACK(signalDestroy), this );
+}
+
+GtkSalObjectWidgetClip::~GtkSalObjectWidgetClip()
+{
+ if( m_pSocket )
+ {
+ // remove socket from parent frame's fixed container
+ 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 );
+ }
+}
+
+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 )
+ {
+ 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);
+ gtk_widget_size_allocate(m_pScrolledWindow, &allocation);
+
+ 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(long nX, long nY, long nWidth, long nHeight)
+{
+ m_aRect = tools::Rectangle(Point(nX, nY), Size(nWidth, nHeight));
+ if (m_pSocket)
+ {
+ GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pScrolledWindow));
+ gtk_widget_set_size_request(m_pSocket, nWidth, nHeight);
+ ApplyClipRegion();
+ m_pParent->nopaint_container_resize_children(GTK_CONTAINER(pContainer));
+ }
+}
+
+void GtkSalObjectWidgetClip::Reparent(SalFrame* pFrame)
+{
+ GtkSalFrame* pNewParent = static_cast<GtkSalFrame*>(pFrame);
+ if (m_pSocket)
+ {
+ GtkFixed* pContainer = GTK_FIXED(gtk_widget_get_parent(m_pScrolledWindow));
+
+ gint nX(0), nY(0);
+ gtk_container_child_get(GTK_CONTAINER(pContainer), m_pScrolledWindow,
+ "x", &nX,
+ "y", &nY,
+ nullptr);
+
+ g_object_ref(m_pScrolledWindow);
+ gtk_container_remove(GTK_CONTAINER(pContainer), m_pScrolledWindow);
+
+ 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 )
+ {
+ if( bVisible )
+ gtk_widget_show(m_pScrolledWindow);
+ else
+ gtk_widget_hide(m_pScrolledWindow);
+ }
+}
+
+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;
+ }
+}
+
+gboolean GtkSalObjectWidgetClip::signalScroll(GtkWidget* pScrolledWindow, GdkEvent* pEvent, gpointer object)
+{
+ GtkSalObjectWidgetClip* pThis = static_cast<GtkSalObjectWidgetClip*>(object);
+ return pThis->signal_scroll(pScrolledWindow, pEvent);
+}
+
+// forward the wheel scroll events onto the main window instead
+bool GtkSalObjectWidgetClip::signal_scroll(GtkWidget*, GdkEvent* pEvent)
+{
+ GtkWidget* pEventWidget = gtk_get_event_widget(pEvent);
+
+ GtkWidget* pMouseEventWidget = m_pParent->getMouseEventWidget();
+
+ gint 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;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3gtkprintwrapper.cxx b/vcl/unx/gtk3/gtk3gtkprintwrapper.cxx
new file mode 100644
index 000000000..0066fa004
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3gtkprintwrapper.cxx
@@ -0,0 +1,153 @@
+/* -*- 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/gtkprintwrapper.hxx>
+
+namespace vcl::unx
+{
+
+GtkPrintWrapper::GtkPrintWrapper()
+{
+}
+
+GtkPrintWrapper::~GtkPrintWrapper()
+{
+}
+
+bool GtkPrintWrapper::supportsPrinting() const
+{
+ (void) this; // loplugin:staticmethods
+ return true;
+}
+
+bool GtkPrintWrapper::supportsPrintSelection() const
+{
+ (void) this; // loplugin:staticmethods
+ return true;
+}
+
+GtkPageSetup* GtkPrintWrapper::page_setup_new() const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_page_setup_new();
+}
+
+GtkPrintJob* GtkPrintWrapper::print_job_new(const gchar* title, GtkPrinter* printer, GtkPrintSettings* settings, GtkPageSetup* page_setup) const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_print_job_new(title, printer, settings, page_setup);
+}
+
+void GtkPrintWrapper::print_job_send(GtkPrintJob* job, GtkPrintJobCompleteFunc callback, gpointer user_data, GDestroyNotify dnotify) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_job_send(job, callback, user_data, dnotify);
+}
+
+gboolean GtkPrintWrapper::print_job_set_source_file(GtkPrintJob* job, const gchar* filename, GError** error) const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_print_job_set_source_file(job, filename, error);
+}
+
+const gchar* GtkPrintWrapper::print_settings_get(GtkPrintSettings* settings, const gchar* key) const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_print_settings_get(settings, key);
+}
+
+gboolean GtkPrintWrapper::print_settings_get_collate(GtkPrintSettings* settings) const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_print_settings_get_collate(settings);
+}
+
+void GtkPrintWrapper::print_settings_set_collate(GtkPrintSettings* settings, gboolean collate) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_settings_set_collate(settings, collate);
+}
+
+gint GtkPrintWrapper::print_settings_get_n_copies(GtkPrintSettings* settings) const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_print_settings_get_n_copies(settings);
+}
+
+void GtkPrintWrapper::print_settings_set_n_copies(GtkPrintSettings* settings, gint num_copies) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_settings_set_n_copies(settings, num_copies);
+}
+
+GtkPageRange* GtkPrintWrapper::print_settings_get_page_ranges(GtkPrintSettings* settings, gint* num_ranges) const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_print_settings_get_page_ranges(settings, num_ranges);
+}
+
+void GtkPrintWrapper::print_settings_set_print_pages(GtkPrintSettings* settings, GtkPrintPages pages) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_settings_set_print_pages(settings, pages);
+}
+
+GtkWidget* GtkPrintWrapper::print_unix_dialog_new() const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_print_unix_dialog_new(nullptr, nullptr);
+}
+
+void GtkPrintWrapper::print_unix_dialog_add_custom_tab(GtkPrintUnixDialog* dialog, GtkWidget* child, GtkWidget* tab_label) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_unix_dialog_add_custom_tab(dialog, child, tab_label);
+}
+
+GtkPrinter* GtkPrintWrapper::print_unix_dialog_get_selected_printer(GtkPrintUnixDialog* dialog) const
+{
+ (void) this; // loplugin:staticmethods
+ GtkPrinter* pRet = gtk_print_unix_dialog_get_selected_printer(dialog);
+ g_object_ref(G_OBJECT(pRet));
+ return pRet;
+}
+
+void GtkPrintWrapper::print_unix_dialog_set_manual_capabilities(GtkPrintUnixDialog* dialog, GtkPrintCapabilities capabilities) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_unix_dialog_set_manual_capabilities(dialog, capabilities);
+}
+
+GtkPrintSettings* GtkPrintWrapper::print_unix_dialog_get_settings(GtkPrintUnixDialog* dialog) const
+{
+ (void) this; // loplugin:staticmethods
+ return gtk_print_unix_dialog_get_settings(dialog);
+}
+
+void GtkPrintWrapper::print_unix_dialog_set_settings(GtkPrintUnixDialog* dialog, GtkPrintSettings* settings) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_unix_dialog_set_settings(dialog, settings);
+}
+
+void GtkPrintWrapper::print_unix_dialog_set_support_selection(GtkPrintUnixDialog* dialog, gboolean support_selection) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_unix_dialog_set_support_selection(dialog, support_selection);
+}
+
+void GtkPrintWrapper::print_unix_dialog_set_has_selection(GtkPrintUnixDialog* dialog, gboolean has_selection) const
+{
+ (void) this; // loplugin:staticmethods
+ gtk_print_unix_dialog_set_has_selection(dialog, has_selection);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3gtksalmenu.cxx b/vcl/unx/gtk3/gtk3gtksalmenu.cxx
new file mode 100644
index 000000000..262187f16
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3gtksalmenu.cxx
@@ -0,0 +1,1409 @@
+/* -*- 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/floatwin.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/pngwrite.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()
+{
+ 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)
+ {
+ OString sCommand(action_name);
+
+ sal_Int32 nIndex = 0;
+ OString sWindow = sCommand.getToken(0, '-', nIndex);
+ OString sGtkSalMenu = sCommand.getToken(0, '-', nIndex);
+ OString sItemId = sCommand.getToken(0, '-', nIndex);
+
+ GtkSalMenu* pSalSubMenu = reinterpret_cast<GtkSalMenu*>(sGtkSalMenu.toInt64());
+
+ assert(sWindow == "window" && pSalSubMenu);
+ (void) sWindow;
+
+ return MenuAndId(pSalSubMenu, sItemId.toInt32());
+ }
+}
+
+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( GetFrame()->GetWindow() ) );
+
+ 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 );
+ }
+
+ g_object_unref( 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_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);
+}
+
+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;
+}
+
+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();
+
+ GtkWidget *pWidget = gtk_menu_new_from_model(mpMenuModel);
+ gtk_menu_attach_to_widget(GTK_MENU(pWidget), mpFrame->getMouseEventWidget(), nullptr);
+ 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);
+ g_signal_connect_swapped(G_OBJECT(pWidget), "deactivate", G_CALLBACK(g_main_loop_quit), pLoop);
+
+#if GTK_CHECK_VERSION(3,22,0)
+ if (gtk_check_version(3, 22, 0) == nullptr)
+ {
+ 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;
+ }
+
+ 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())};
+
+ GdkWindow* gdkWindow = gtk_widget_get_window(mpFrame->getMouseEventWidget());
+ gtk_menu_popup_at_rect(GTK_MENU(pWidget), 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(pWidget), nullptr, nullptr, MenuPositionFunc,
+ &aPos, nButton, nTime);
+ }
+
+ if (g_main_loop_is_running(pLoop))
+ {
+ gdk_threads_leave();
+ g_main_loop_run(pLoop);
+ gdk_threads_enter();
+ }
+ g_main_loop_unref(pLoop);
+
+ mpVCLMenu->Deactivate();
+
+ gtk_widget_insert_action_group(mpFrame->getMouseEventWidget(), "win", nullptr);
+
+ gtk_widget_destroy(pWidget);
+
+ g_object_unref(mpActionGroup);
+ ClearActionGroupAndMenuModel();
+
+ mpFrame = nullptr;
+
+ return true;
+}
+
+/*
+ * GtkSalMenu
+ */
+
+GtkSalMenu::GtkSalMenu( bool bMenuBar ) :
+ mbInActivateCallback( false ),
+ mbMenuBar( bMenuBar ),
+ mbNeedsUpdate( false ),
+ mbReturnFocusToDocument( false ),
+ mbAddedGrab( false ),
+ mpMenuBarContainerWidget( nullptr ),
+ mpMenuAllowShrinkWidget( nullptr ),
+ mpMenuBarWidget( 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));
+ maUpdateMenuBarIdle.SetDebugName("Native Gtk Menu Update Idle");
+}
+
+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;
+
+ DestroyMenuBarWidget();
+
+ if (mpMenuModel)
+ g_object_unref(mpMenuModel);
+
+ maItems.clear();
+
+ 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;
+ 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());
+}
+
+void GtkSalMenu::ShowCloseButton(bool bShow)
+{
+ assert(mbMenuBar);
+ if (!mpMenuBarContainerWidget)
+ return;
+
+ if (!bShow)
+ {
+ if (mpCloseButton)
+ {
+ gtk_widget_destroy(mpCloseButton);
+ mpCloseButton = nullptr;
+ }
+ return;
+ }
+
+ MenuBar *pVclMenuBar = static_cast<MenuBar*>(mpVCLMenu.get());
+ mpCloseButton = gtk_button_new();
+ g_signal_connect(mpCloseButton, "clicked", G_CALLBACK(CloseMenuBar), pVclMenuBar);
+
+ gtk_button_set_relief(GTK_BUTTON(mpCloseButton), GTK_RELIEF_NONE);
+ gtk_button_set_focus_on_click(GTK_BUTTON(mpCloseButton), false);
+ gtk_widget_set_can_focus(mpCloseButton, false);
+
+ GtkStyleContext *pButtonContext = gtk_widget_get_style_context(GTK_WIDGET(mpCloseButton));
+
+ GtkCssProvider *pProvider = gtk_css_provider_new();
+ static const gchar data[] = "* { "
+ "padding: 0;"
+ "margin-left: 8px;"
+ "margin-right: 8px;"
+ "min-width: 18px;"
+ "min-height: 18px;"
+ "}";
+ const gchar olddata[] = "* { "
+ "padding: 0;"
+ "margin-left: 8px;"
+ "margin-right: 8px;"
+ "}";
+ gtk_css_provider_load_from_data(pProvider, gtk_check_version(3, 20, 0) == nullptr ? data : olddata, -1, nullptr);
+ gtk_style_context_add_provider(pButtonContext,
+ GTK_STYLE_PROVIDER(pProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ gtk_style_context_add_class(pButtonContext, "flat");
+ gtk_style_context_add_class(pButtonContext, "small-button");
+
+ GIcon* icon = g_themed_icon_new_with_default_fallbacks("window-close-symbolic");
+ GtkWidget* image = gtk_image_new_from_gicon(icon, GTK_ICON_SIZE_MENU);
+ gtk_widget_show(image);
+ g_object_unref(icon);
+
+ OUString sToolTip(VclResId(SV_HELPTEXT_CLOSEDOCUMENT));
+ gtk_widget_set_tooltip_text(mpCloseButton,
+ OUStringToOString(sToolTip, RTL_TEXTENCODING_UTF8).getStr());
+
+ gtk_widget_set_valign(mpCloseButton, GTK_ALIGN_CENTER);
+
+ gtk_container_add(GTK_CONTAINER(mpCloseButton), image);
+ gtk_grid_attach(GTK_GRID(mpMenuBarContainerWidget), GTK_WIDGET(mpCloseButton), 1, 0, 1, 1);
+ gtk_widget_show_all(mpCloseButton);
+}
+
+//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)
+ {
+ gtk_grab_remove(mpMenuBarWidget);
+ mbAddedGrab = false;
+ }
+ if (!mbReturnFocusToDocument)
+ gtk_widget_grab_focus(GTK_WIDGET(mpFrame->getEventBox()));
+ else
+ mpFrame->GetWindow()->GrabFocusToDocument();
+ mbReturnFocusToDocument = false;
+}
+
+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;
+}
+
+//The GtkSalMenu is owner 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;
+
+ //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));
+ mbReturnFocusToDocument = true;
+ return true;
+}
+
+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);
+}
+
+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);
+
+ mpMenuAllowShrinkWidget = gtk_scrolled_window_new(nullptr, nullptr);
+ // tdf#129634 don't allow this scrolled window as a candidate to tab into
+ gtk_widget_set_can_focus(GTK_WIDGET(mpMenuAllowShrinkWidget), false);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(mpMenuAllowShrinkWidget), GTK_SHADOW_NONE);
+ // 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);
+
+ mpMenuBarWidget = gtk_menu_bar_new_from_model(mpMenuModel);
+
+ gtk_widget_insert_action_group(mpMenuBarWidget, "win", mpActionGroup);
+ gtk_widget_set_hexpand(GTK_WIDGET(mpMenuBarWidget), true);
+ gtk_widget_set_hexpand(mpMenuAllowShrinkWidget, true);
+ gtk_container_add(GTK_CONTAINER(mpMenuAllowShrinkWidget), mpMenuBarWidget);
+
+ g_signal_connect(G_OBJECT(mpMenuBarWidget), "deactivate", G_CALLBACK(MenuBarReturnFocus), this);
+ g_signal_connect(G_OBJECT(mpMenuBarWidget), "key-press-event", G_CALLBACK(MenuBarSignalKey), this);
+
+ gtk_widget_show_all(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);
+ gtk_css_provider_load_from_data(mpMenuBarContainerProvider, aResult.getStr(), aResult.getLength(), nullptr);
+ 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;"
+ "}";
+ gtk_css_provider_load_from_data(mpMenuBarProvider, data, -1, nullptr);
+ gtk_style_context_add_provider(pMenuBarContext,
+ GTK_STYLE_PROVIDER(mpMenuBarProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+ maPersonaBitmap = rPersonaBitmap;
+}
+
+void GtkSalMenu::DestroyMenuBarWidget()
+{
+ if (mpMenuBarContainerWidget)
+ {
+ gtk_widget_destroy(mpMenuBarContainerWidget);
+ mpMenuBarContainerWidget = 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();
+ GdkWindow* gdkWindow = gtk_widget_get_window( 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 );
+}
+
+namespace
+{
+ void DestroyMemoryStream(gpointer data)
+ {
+ SvMemoryStream* pMemStm = static_cast<SvMemoryStream*>(data);
+ delete pMemStm;
+ }
+}
+
+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, const OUString& rKeyName )
+{
+ SolarMutexGuard aGuard;
+
+ if ( rKeyName.isEmpty() )
+ 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();
+ if (pTopLevel->mpMenuBarWidget)
+ {
+ // 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
+ gtk_menu_shell_deactivate(GTK_MENU_SHELL(pTopLevel->mpMenuBarWidget));
+ }
+ pTopLevel->GetMenu()->HandleMenuCommandEvent(pSalSubMenu->GetMenu(), aMenuAndId.second);
+}
+
+void GtkSalMenu::ActivateAllSubmenus(Menu* pMenuBar)
+{
+ for (GtkSalMenuItem* pSalItem : maItems)
+ {
+ if ( pSalItem->mpSubMenu != nullptr )
+ {
+ // 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 (!pSalItem->mpSubMenu->mbInActivateCallback)
+ {
+ pSalItem->mpSubMenu->mbInActivateCallback = true;
+ pMenuBar->HandleMenuActivateEvent(pSalItem->mpSubMenu->GetMenu());
+ pSalItem->mpSubMenu->mbInActivateCallback = false;
+ pSalItem->mpSubMenu->ActivateAllSubmenus(pMenuBar);
+ pSalItem->mpSubMenu->Update();
+ pMenuBar->HandleMenuDeActivateEvent(pSalItem->mpSubMenu->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;
+ GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
+ Menu* pVclMenu = pSalMenu->GetMenu();
+ 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;
+ GtkSalMenu* pTopLevel = pSalMenu->GetTopLevel();
+ Menu* pVclMenu = pSalMenu->GetMenu();
+ 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() ) )
+ {
+ 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/gtk3gtksys.cxx b/vcl/unx/gtk3/gtk3gtksys.cxx
new file mode 100644
index 000000000..379c87343
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3gtksys.cxx
@@ -0,0 +1,275 @@
+/* -*- 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/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() : SalGenericSystem()
+{
+ mpDisplay = gdk_display_get_default();
+ countScreenMonitors();
+ // 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
+ ;
+ }
+};
+
+}
+
+/**
+ * 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);
+ }
+}
+
+SalX11Screen
+GtkSalSystem::getXScreenFromDisplayScreen(unsigned int nScreen)
+{
+ gint nMonitor;
+
+ GdkScreen *pScreen = getScreenMonitorFromIdx (nScreen, nMonitor);
+ if (!pScreen)
+ return SalX11Screen (0);
+ if (!DLSYM_GDK_IS_X11_DISPLAY(mpDisplay))
+ return SalX11Screen (0);
+ return SalX11Screen (gdk_x11_screen_get_screen_number (pScreen));
+}
+
+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);
+}
+
+unsigned int GtkSalSystem::GetDisplayScreenCount()
+{
+ gint nMonitor;
+ (void)getScreenMonitorFromIdx (G_MAXINT, nMonitor);
+ return G_MAXINT - nMonitor;
+}
+
+bool GtkSalSystem::IsUnifiedDisplay()
+{
+ return gdk_display_get_n_screens (mpDisplay) == 1;
+}
+
+namespace {
+int _fallback_get_primary_monitor (GdkScreen *pScreen)
+{
+ // Use monitor name as primacy heuristic
+ int max = gdk_screen_get_n_monitors (pScreen);
+ for (int i = 0; i < max; ++i)
+ {
+ char *name = gdk_screen_get_monitor_plug_name (pScreen, i);
+ bool bLaptop = (name && !g_ascii_strncasecmp (name, "LVDS", 4));
+ g_free (name);
+ if (bLaptop)
+ return i;
+ }
+ return 0;
+}
+
+int _get_primary_monitor (GdkScreen *pScreen)
+{
+ static int (*get_fn) (GdkScreen *) = nullptr;
+ get_fn = gdk_screen_get_primary_monitor;
+ // Perhaps we have a newer gtk+ with this symbol:
+ if (!get_fn)
+ {
+ get_fn = reinterpret_cast<int(*)(GdkScreen*)>(osl_getAsciiFunctionSymbol(nullptr,
+ "gdk_screen_get_primary_monitor"));
+ }
+ if (!get_fn)
+ get_fn = _fallback_get_primary_monitor;
+ if (get_fn)
+ return get_fn (pScreen);
+ else
+ return 0;
+}
+} // end anonymous namespace
+
+unsigned int GtkSalSystem::GetDisplayBuiltInScreen()
+{
+ GdkScreen *pDefault = gdk_display_get_default_screen (mpDisplay);
+ int idx = getScreenIdxFromPtr (pDefault);
+ return idx + _get_primary_monitor (pDefault);
+}
+
+tools::Rectangle GtkSalSystem::GetDisplayScreenPosSizePixel (unsigned int nScreen)
+{
+ gint nMonitor;
+ GdkScreen *pScreen;
+ GdkRectangle aRect;
+ pScreen = getScreenMonitorFromIdx (nScreen, nMonitor);
+ if (!pScreen)
+ return tools::Rectangle();
+ gdk_screen_get_monitor_geometry (pScreen, nMonitor, &aRect);
+ 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);
+}
+
+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;
+
+ gtk_widget_destroy (GTK_WIDGET (pDialog));
+
+ return nButton;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3hudawareness.cxx b/vcl/unx/gtk3/gtk3hudawareness.cxx
new file mode 100644
index 000000000..dddad28da
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3hudawareness.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
+{
+ GDBusConnection *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 = static_cast<GDBusConnection*>(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/gtk3salnativewidgets-gtk.cxx b/vcl/unx/gtk3/gtk3salnativewidgets-gtk.cxx
new file mode 100644
index 000000000..50e894f40
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3salnativewidgets-gtk.cxx
@@ -0,0 +1,3731 @@
+/* -*- 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/decoview.hxx>
+#include <vcl/settings.hxx>
+#include <unx/fontmanager.hxx>
+
+#include "cairo_gtk3_cairo.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::mpToolbarSeperatorStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpCheckButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpCheckButtonCheckStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpRadioButtonStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpRadioButtonRadioStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpSpinStyle = nullptr;
+GtkStyleContext* GtkSalGraphics::mpSpinEntryStyle = 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;
+
+bool GtkSalGraphics::style_loaded = false;
+/************************************************************************
+ * State conversion
+ ************************************************************************/
+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;
+ gtk_style_context_get_padding(mpSpinUpStyle, gtk_style_context_get_state(mpSpinUpStyle), &padding);
+ gtk_style_context_get_border(mpSpinUpStyle, gtk_style_context_get_state(mpSpinUpStyle), &border);
+
+ gint buttonWidth = icon_size + padding.left + padding.right +
+ border.left + border.right;
+
+ gint buttonHeight = icon_size + padding.top + padding.bottom +
+ border.top + border.bottom;
+
+ tools::Rectangle buttonRect;
+ buttonRect.SetSize(Size(buttonWidth, buttonHeight));
+ buttonRect.setY(aAreaRect.Top());
+ buttonRect.SetBottom( buttonRect.Top() + aAreaRect.GetHeight() );
+ tools::Rectangle partRect(buttonRect);
+ if ( nPart == ControlPart::ButtonUp )
+ {
+ if (AllSettings::GetLayoutRTL())
+ partRect.setX(aAreaRect.Left());
+ else
+ partRect.setX(aAreaRect.Left() + (aAreaRect.GetWidth() - buttonRect.GetWidth()));
+ }
+ else if( nPart == ControlPart::ButtonDown )
+ {
+ if (AllSettings::GetLayoutRTL())
+ partRect.setX(aAreaRect.Left() + buttonRect.GetWidth());
+ else
+ partRect.setX(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;
+}
+
+namespace
+{
+ void QuerySize(GtkStyleContext *pContext, Size &rSize)
+ {
+ GtkBorder margin, border, padding;
+ GtkStateFlags stateflags = gtk_style_context_get_state (pContext);
+
+ gtk_style_context_get_margin(pContext, stateflags, &margin);
+ gtk_style_context_get_border(pContext, stateflags, &border);
+ gtk_style_context_get_padding(pContext, stateflags, &padding);
+
+ int nMinWidth, nMinHeight;
+ 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<long>(rSize.Width(), nMinWidth), std::max<long>(rSize.Height(), nMinHeight));
+ }
+}
+
+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 buttonWidth;
+ gint buttonHeight;
+
+ 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;
+
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ 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.setX(aAreaRect.Left());
+ buttonRect.setY(aAreaRect.Top());
+ }
+ else if (nPart == ControlPart::ButtonLeft)
+ {
+ aSize.setWidth( aSize.Width() * nFirst );
+ buttonRect.setX(aAreaRect.Left());
+ buttonRect.setY(aAreaRect.Top());
+ }
+ else if (nPart == ControlPart::ButtonDown)
+ {
+ aSize.setHeight( aSize.Height() * nSecond );
+ buttonRect.setX(aAreaRect.Left());
+ buttonRect.setY(aAreaRect.Top() + aAreaRect.GetHeight() - aSize.Height());
+ }
+ else if (nPart == ControlPart::ButtonRight)
+ {
+ aSize.setWidth( aSize.Width() * nSecond );
+ buttonRect.setX(aAreaRect.Left() + aAreaRect.GetWidth() - aSize.Width());
+ buttonRect.setY(aAreaRect.Top());
+ }
+
+ buttonRect.SetSize(aSize);
+
+ return buttonRect;
+ }
+
+ gint slider_width;
+ gint stepper_size;
+ gint stepper_spacing;
+ gint trough_border;
+
+ // Grab some button style attributes
+ gtk_style_context_get_style( pScrollbarStyle,
+ "slider-width", &slider_width,
+ "stepper-size", &stepper_size,
+ "trough-border", &trough_border,
+ "stepper-spacing", &stepper_spacing, nullptr );
+
+ if ( ( nPart == ControlPart::ButtonUp ) || ( nPart == ControlPart::ButtonDown ) )
+ {
+ buttonWidth = slider_width + 2 * trough_border;
+ buttonHeight = stepper_size + trough_border + stepper_spacing;
+ }
+ else
+ {
+ buttonWidth = stepper_size + trough_border + stepper_spacing;
+ buttonHeight = slider_width + 2 * trough_border;
+ }
+
+ if ( nPart == ControlPart::ButtonUp )
+ {
+ buttonHeight *= nFirst;
+ buttonHeight -= 1;
+ buttonRect.setX( aAreaRect.Left() );
+ buttonRect.setY( aAreaRect.Top() );
+ }
+ else if ( nPart == ControlPart::ButtonLeft )
+ {
+ buttonWidth *= nFirst;
+ buttonWidth -= 1;
+ buttonRect.setX( aAreaRect.Left() );
+ buttonRect.setY( aAreaRect.Top() );
+ }
+ else if ( nPart == ControlPart::ButtonDown )
+ {
+ buttonHeight *= nSecond;
+ buttonRect.setX( aAreaRect.Left() );
+ buttonRect.setY( aAreaRect.Top() + aAreaRect.GetHeight() - buttonHeight );
+ }
+ else if ( nPart == ControlPart::ButtonRight )
+ {
+ buttonWidth *= nSecond;
+ buttonRect.setX( aAreaRect.Left() + aAreaRect.GetWidth() - buttonWidth );
+ buttonRect.setY( aAreaRect.Top() );
+ }
+
+ buttonRect.SetSize( Size( buttonWidth, buttonHeight ) );
+
+ return buttonRect;
+}
+
+static GtkWidget* gCacheWindow;
+static GtkWidget* gDumbContainer;
+static GtkWidget* gSpinBox;
+static GtkWidget* gEntryBox;
+static GtkWidget* gComboBox;
+static GtkWidget* gListBox;
+static GtkWidget* gMenuBarWidget;
+static GtkWidget* gMenuItemMenuBarWidget;
+static GtkWidget* gCheckMenuItemWidget;
+static GtkWidget* gTreeViewWidget;
+
+namespace
+{
+ void style_context_set_state(GtkStyleContext* context, GtkStateFlags flags)
+ {
+ do
+ {
+ gtk_style_context_set_state(context, flags);
+ }
+ while ((context = gtk_style_context_get_parent(context)));
+ }
+
+ class StyleContextSave
+ {
+ private:
+ std::vector<std::pair<GtkStyleContext*, GtkStateFlags>> m_aStates;
+ public:
+ void save(GtkStyleContext* context)
+ {
+ do
+ {
+ m_aStates.emplace_back(context, gtk_style_context_get_state(context));
+ }
+ while ((context = gtk_style_context_get_parent(context)));
+ }
+ 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();
+ }
+ };
+
+ 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;
+ gtk_style_context_get_margin(pContext, gtk_style_context_get_state(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;
+ gtk_style_context_get_border(pContext, gtk_style_context_get_state(pContext), &border);
+ gtk_style_context_get_padding(pContext, gtk_style_context_get_state(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;
+ }
+}
+
+void GtkSalGraphics::PaintScrollbar(GtkStyleContext *context,
+ cairo_t *cr,
+ const tools::Rectangle& rControlRectangle,
+ ControlPart nPart,
+ const ImplControlValue& rValue )
+{
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ 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 )
+ {
+ 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;
+ gtk_style_context_get_margin(pScrollbarSliderStyle, stateFlags, &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);
+ }
+
+ return;
+ }
+
+ OSL_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;
+ gint trough_border = 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;
+ scrollbarRect.SetSize( Size( scrollbarRect.GetWidth() + 1,
+ scrollbarRect.GetHeight() + 1 ) );
+
+ if ( (scrollbarRect.GetWidth() <= 1) || (scrollbarRect.GetHeight() <= 1) )
+ return;
+
+ // Grab some button style attributes
+ gtk_style_context_get_style( context,
+ "slider_width", &slider_width,
+ "stepper_size", &stepper_size,
+ "trough_border", &trough_border, 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 );
+ gint magic = trough_border ? 1 : 0;
+ gint slider_side = slider_width + (trough_border * 2);
+
+ if ( nPart == ControlPart::DrawBackgroundHorz )
+ {
+ scrollbarRect.Move( 0, (scrollbarRect.GetHeight() - slider_side) / 2 );
+ scrollbarRect.SetSize( Size( scrollbarRect.GetWidth(), slider_side ) );
+
+ scrollbarOrientation = GTK_ORIENTATION_HORIZONTAL;
+ arrow1Angle = G_PI * 3 / 2;
+ arrow2Angle = G_PI / 2;
+
+ if ( has_backward )
+ {
+ button12BoundRect.Move( stepper_size - trough_border,
+ (scrollbarRect.GetHeight() - slider_width) / 2 );
+ }
+
+ button11BoundRect.Move( trough_border, (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+(trough_border+1)/2, (scrollbarRect.GetHeight() - slider_width) / 2 );
+ button21BoundRect.Move( (trough_border+1)/2, (scrollbarRect.GetHeight() - slider_width) / 2 );
+ }
+ else
+ {
+ button22BoundRect.Move( (trough_border+1)/2, (scrollbarRect.GetHeight() - slider_width) / 2 );
+ }
+
+ button21BoundRect.SetSize( Size( stepper_size, slider_width ) );
+ button22BoundRect.SetSize( Size( stepper_size, slider_width ) );
+
+ thumbRect.SetBottom( thumbRect.Top() + slider_width - 1 );
+ // Make sure the thumb is at least the default width (so we don't get tiny thumbs),
+ // but if the VCL gives us a size smaller than the theme's default thumb size,
+ // honor the VCL size
+ thumbRect.AdjustRight(magic );
+ // Center vertically in the track
+ thumbRect.Move( 0, (scrollbarRect.GetHeight() - slider_width) / 2 );
+ }
+ else
+ {
+ scrollbarRect.Move( (scrollbarRect.GetWidth() - slider_side) / 2, 0 );
+ scrollbarRect.SetSize( Size( slider_side, scrollbarRect.GetHeight() ) );
+
+ scrollbarOrientation = GTK_ORIENTATION_VERTICAL;
+ arrow1Angle = 0;
+ arrow2Angle = G_PI;
+
+ if ( has_backward )
+ {
+ button12BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2,
+ stepper_size + trough_border );
+ }
+ button11BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, trough_border );
+ 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+(trough_border+1)/2 );
+ button21BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, (trough_border+1)/2 );
+ }
+ else
+ {
+ button22BoundRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, (trough_border+1)/2 );
+ }
+
+ button21BoundRect.SetSize( Size( slider_width, stepper_size ) );
+ button22BoundRect.SetSize( Size( slider_width, stepper_size ) );
+
+ thumbRect.SetRight( thumbRect.Left() + slider_width - 1 );
+
+ thumbRect.AdjustBottom(magic );
+ // Center horizontally in the track
+ thumbRect.Move( (scrollbarRect.GetWidth() - slider_width) / 2, 0 );
+ }
+
+ 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() );
+
+ // ----------------- TROUGH
+ GtkStyleContext* pScrollbarTroughStyle = scrollbarOrientation == GTK_ORIENTATION_VERTICAL ?
+ mpVScrollbarTroughStyle : mpHScrollbarTroughStyle;
+ gtk_render_background(pScrollbarTroughStyle, cr, 0, 0,
+ scrollbarRect.GetWidth(), scrollbarRect.GetHeight() );
+ gtk_render_frame(pScrollbarTroughStyle, cr, 0, 0,
+ scrollbarRect.GetWidth(), scrollbarRect.GetHeight() );
+
+ // ----------------- THUMB
+ if ( has_slider )
+ {
+ 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;
+ gtk_style_context_get_margin(pScrollbarSliderStyle, stateFlags, &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);
+ }
+
+ 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_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()) );
+ }
+ 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()) );
+ }
+}
+
+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);
+
+ gtk_style_context_get_padding(context, stateFlags, &padding);
+ gtk_style_context_get_border(context, stateFlags, &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;
+ arrowRect.SetSize(Size(iconWidth, iconHeight));
+ arrowRect.setX( buttonRect.Left() + (buttonRect.GetWidth() - arrowRect.GetWidth()) / 2 );
+ arrowRect.setY( buttonRect.Top() + (buttonRect.GetHeight() - arrowRect.GetHeight()) / 2 );
+
+ 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)
+ gtk_style_context_get_padding(mpListboxButtonStyle, gtk_style_context_get_state(mpListboxButtonStyle), &padding);
+ else
+ gtk_style_context_get_padding(mpButtonStyle, gtk_style_context_get_state(mpButtonStyle), &padding);
+
+ gint nArrowWidth = FALLBACK_ARROW_SIZE;
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ gtk_style_context_get(mpComboboxButtonArrowStyle,
+ gtk_style_context_get_state(mpComboboxButtonArrowStyle),
+ "min-width", &nArrowWidth, nullptr);
+ }
+ else
+ {
+ nArrowWidth = nArrowWidth * gtk_style_context_get_scale (mpComboboxButtonArrowStyle);
+ }
+
+ 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 (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ 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);
+ }
+ }
+ else
+ {
+ if (nType == ControlType::Combobox)
+ {
+ arrow_width = arrow_width * gtk_style_context_get_scale (mpComboboxButtonArrowStyle);
+ arrow_height = arrow_height * gtk_style_context_get_scale (mpComboboxButtonArrowStyle);
+ }
+ else if (nType == ControlType::Listbox)
+ {
+ arrow_width = arrow_width * gtk_style_context_get_scale (mpListboxButtonArrowStyle);
+ arrow_height = arrow_height * gtk_style_context_get_scale (mpListboxButtonArrowStyle);
+ }
+ }
+
+ 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_iter_set_object_nameFunc set_object_name)
+{
+ gtk_widget_path_append_type(pSiblingsPath, GTK_TYPE_ENTRY);
+ set_object_name(pSiblingsPath, -1, "entry");
+ gtk_widget_path_iter_add_class(pSiblingsPath, -1, "combo");
+}
+
+static void appendComboButton(GtkWidgetPath* pSiblingsPath, gtk_widget_path_iter_set_object_nameFunc set_object_name)
+{
+ gtk_widget_path_append_type(pSiblingsPath, GTK_TYPE_BUTTON);
+ set_object_name(pSiblingsPath, -1, "button");
+ gtk_widget_path_iter_add_class(pSiblingsPath, -1, "combo");
+}
+
+static GtkWidgetPath* buildLTRComboSiblingsPath(gtk_widget_path_iter_set_object_nameFunc set_object_name)
+{
+ GtkWidgetPath* pSiblingsPath = gtk_widget_path_new();
+
+ appendComboEntry(pSiblingsPath, set_object_name);
+ appendComboButton(pSiblingsPath, set_object_name);
+
+ return pSiblingsPath;
+}
+
+static GtkWidgetPath* buildRTLComboSiblingsPath(gtk_widget_path_iter_set_object_nameFunc set_object_name)
+{
+ GtkWidgetPath* pSiblingsPath = gtk_widget_path_new();
+
+ appendComboButton(pSiblingsPath, set_object_name);
+ appendComboEntry(pSiblingsPath, set_object_name);
+
+ 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 = gtk_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::createNewContext(GtkControlPart ePart, gtk_widget_path_iter_set_object_nameFunc set_object_name)
+{
+ switch (ePart)
+ {
+ case GtkControlPart::ToplevelWindow:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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(set_object_name);
+ gtk_widget_path_append_with_siblings(path, pSiblingsPath, 1);
+ }
+ else
+ {
+ pSiblingsPath = buildLTRComboSiblingsPath(set_object_name);
+ 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(set_object_name);
+ gtk_widget_path_append_with_siblings(path, pSiblingsPath, 0);
+ }
+ else
+ {
+ pSiblingsPath = buildLTRComboSiblingsPath(set_object_name);
+ 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);
+ 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);
+ 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);
+ 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);
+ set_object_name(path, -1, "spinbutton");
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_HORIZONTAL);
+ return makeContext(path, mpWindowStyle);
+ }
+ case GtkControlPart::SpinButtonEntry:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpSpinStyle));
+ gtk_widget_path_append_type(path, G_TYPE_NONE);
+ set_object_name(path, -1, "entry");
+ return makeContext(path, mpSpinStyle);
+ }
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ set_object_name(path, -1, "separator");
+ return makeContext(path, mpSeparatorMenuItemStyle);
+ }
+ }
+
+ return nullptr;
+}
+
+#ifndef GTK_STYLE_CLASS_POPUP
+#define GTK_STYLE_CLASS_POPUP "popup"
+#endif
+#ifndef GTK_STYLE_CLASS_LABEL
+#define GTK_STYLE_CLASS_LABEL "label"
+#endif
+
+GtkStyleContext* GtkSalGraphics::createOldContext(GtkControlPart ePart)
+{
+ switch (ePart)
+ {
+ case GtkControlPart::ToplevelWindow:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_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_add_class(path, -1, GTK_STYLE_CLASS_BUTTON);
+ gtk_widget_path_iter_add_class(path, -1, "button");
+ gtk_widget_path_iter_add_class(path, -1, "text-button");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::LinkButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_LINK_BUTTON);
+ gtk_widget_path_iter_add_class(path, -1, "text-button");
+ 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_add_class(path, -1, GTK_STYLE_CLASS_CHECK);
+ gtk_widget_path_iter_add_class(path, -1, "text-button");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::CheckButtonCheck:
+ return mpCheckButtonStyle;
+ case GtkControlPart::RadioButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_RADIO_BUTTON);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_RADIO);
+ gtk_widget_path_iter_add_class(path, -1, "text-button");
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::RadioButtonRadio:
+ return mpRadioButtonStyle;
+ case GtkControlPart::ComboboxBoxButtonBoxArrow:
+ case GtkControlPart::ListboxBoxButtonBoxArrow:
+ {
+ return (ePart == GtkControlPart::ComboboxBoxButtonBoxArrow)
+ ? mpComboboxButtonStyle : mpListboxButtonStyle;
+ }
+ case GtkControlPart::Entry:
+ case GtkControlPart::ComboboxBoxEntry:
+ case GtkControlPart::SpinButtonEntry:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_ENTRY);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_ENTRY);
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::Combobox:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_COMBO_BOX_TEXT);
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::Listbox:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_COMBO_BOX);
+ return makeContext(path, nullptr);
+ }
+ case GtkControlPart::ComboboxBoxButton:
+ case GtkControlPart::ListboxBoxButton:
+ {
+ GtkStyleContext *pParent =
+ (ePart == GtkControlPart::ComboboxBoxButton ) ? mpComboboxStyle : mpListboxStyle;
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(pParent));
+ gtk_widget_path_append_type(path, GTK_TYPE_TOGGLE_BUTTON);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_BUTTON);
+ gtk_widget_path_iter_add_class(path, -1, "the-button-in-the-combobox");
+ return makeContext(path, pParent);
+ }
+ case GtkControlPart::SpinButton:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_SPIN_BUTTON);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_ENTRY);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_HORIZONTAL);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_SPINBUTTON);
+ return makeContext(path, nullptr);
+ }
+ 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_add_class(path, -1, GTK_STYLE_CLASS_SPINBUTTON);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_BUTTON);
+ 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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_SCROLLBAR);
+ gtk_widget_path_iter_add_class(path, -1, "contents");
+ return makeContext(path, pParent);
+ }
+ case GtkControlPart::ScrollbarHorizontalTrough:
+ case GtkControlPart::ScrollbarVerticalTrough:
+ {
+ 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_add_class(path, -1, GTK_STYLE_CLASS_SCROLLBAR);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_TROUGH);
+ return makeContext(path, pParent);
+ }
+ case GtkControlPart::ScrollbarHorizontalSlider:
+ case GtkControlPart::ScrollbarVerticalSlider:
+ {
+ GtkStyleContext *pParent =
+ (ePart == GtkControlPart::ScrollbarVerticalSlider) ? 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_add_class(path, -1, GTK_STYLE_CLASS_SCROLLBAR);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_SLIDER);
+ return makeContext(path, pParent);
+ }
+ case GtkControlPart::ScrollbarHorizontalButton:
+ case GtkControlPart::ScrollbarVerticalButton:
+ {
+ 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_add_class(path, -1, GTK_STYLE_CLASS_SCROLLBAR);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_PROGRESSBAR);
+ 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_add_class(path, -1, GTK_STYLE_CLASS_NOTEBOOK);
+ gtk_widget_path_iter_add_class(path, -1, "frame");
+ return makeContext(path, mpWindowStyle);
+ }
+ case GtkControlPart::NotebookStack:
+ return mpNotebookStyle;
+ case GtkControlPart::NotebookHeader:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpNotebookStyle));
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_HEADER);
+ gtk_widget_path_iter_add_class(path, -1, "top");
+ return makeContext(path, gtk_style_context_get_parent(mpNotebookStyle));
+ }
+ case GtkControlPart::NotebookHeaderTabs:
+ return 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_add_class(path, -1, GTK_STYLE_CLASS_HEADER);
+ gtk_widget_path_iter_add_class(path, -1, "top");
+ gtk_widget_path_iter_add_region(path, -1, GTK_STYLE_REGION_TAB, static_cast<GtkRegionFlags>(GTK_REGION_EVEN | GTK_REGION_FIRST));
+ 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, GTK_TYPE_LABEL);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_LABEL);
+ return makeContext(path, mpNotebookHeaderTabsTabStyle);
+ }
+ case GtkControlPart::NotebookHeaderTabsTabActiveLabel:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpNotebookHeaderTabsTabStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_LABEL);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_LABEL);
+ gtk_widget_path_iter_add_class(path, -1, "active-page");
+ return makeContext(path, mpNotebookHeaderTabsTabStyle);
+ }
+ case GtkControlPart::NotebookHeaderTabsTabHoverLabel:
+ {
+ GtkWidgetPath *path = gtk_widget_path_copy(gtk_style_context_get_path(mpNotebookHeaderTabsTabStyle));
+ gtk_widget_path_append_type(path, GTK_TYPE_LABEL);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_LABEL);
+ gtk_widget_path_iter_add_class(path, -1, "prelight-page");
+ return makeContext(path, mpNotebookHeaderTabsTabStyle);
+ }
+ case GtkControlPart::FrameBorder:
+ {
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_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, GTK_TYPE_WINDOW);
+ gtk_widget_path_iter_add_class(path, -1, GTK_STYLE_CLASS_POPUP);
+ gtk_widget_path_iter_add_class(path, -1, "background");
+ 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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_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, GTK_TYPE_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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_CHECK);
+ 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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_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_add_class(path, -1, GTK_STYLE_CLASS_SEPARATOR);
+ return makeContext(path, mpSeparatorMenuItemStyle);
+ }
+ case GtkControlPart::ComboboxBox:
+ case GtkControlPart::ListboxBox:
+ case GtkControlPart::ComboboxBoxButtonBox:
+ case GtkControlPart::ListboxBoxButtonBox:
+ return nullptr;
+ default:
+ break;
+ }
+
+ return mpButtonStyle;
+}
+
+GtkStyleContext* GtkSalGraphics::createStyleContext(gtk_widget_path_iter_set_object_nameFunc set_object_name,
+ GtkControlPart ePart)
+{
+ if (set_object_name)
+ return createNewContext(ePart, set_object_name);
+ return createOldContext(ePart);
+}
+
+namespace
+{
+ GtkStateFlags ACTIVE_TAB()
+ {
+#if GTK_CHECK_VERSION(3,20,0)
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ return GTK_STATE_FLAG_CHECKED;
+#endif
+ return GTK_STATE_FLAG_ACTIVE;
+ }
+}
+
+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)
+{
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ 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;
+ }
+ else
+ {
+ gfloat arrow_scaling = 1.0;
+ gtk_style_context_get_style(context, "arrow-scaling", &arrow_scaling, nullptr);
+ gfloat arrow_size = 11 * arrow_scaling;
+ return arrow_size;
+ }
+}
+
+namespace
+{
+ void draw_vertical_separator(GtkStyleContext *context, cairo_t *cr, const tools::Rectangle& rControlRegion)
+ {
+ long nX = 0;
+ long nY = 0;
+
+ const bool bNewStyle = gtk_check_version(3, 20, 0) == nullptr;
+
+ gint nSeparatorWidth = 1;
+
+ if (bNewStyle)
+ {
+ 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);
+
+ if (bNewStyle)
+ {
+ gtk_render_background(context, cr, nX, nY, nSeparatorWidth, nHeight);
+ gtk_render_frame(context, cr, nX, nY, nSeparatorWidth, nHeight);
+ }
+ else
+ {
+ gtk_render_line(context, cr, nX, nY, nX, nY + nHeight);
+ }
+ }
+
+ void draw_horizontal_separator(GtkStyleContext *context, cairo_t *cr, const tools::Rectangle& rControlRegion)
+ {
+ long nX = 0;
+ long nY = 0;
+
+ const bool bNewStyle = gtk_check_version(3, 20, 0) == nullptr;
+
+ gint nSeparatorHeight = 1;
+
+ if (bNewStyle)
+ {
+ 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);
+
+ if (bNewStyle)
+ {
+ gtk_render_background(context, cr, nX, nY, nWidth, nSeparatorHeight);
+ gtk_render_frame(context, cr, nX, nY, nWidth, nSeparatorHeight);
+ }
+ else
+ {
+ gtk_render_line(context, cr, nX, nY, nX + nWidth, nY);
+ }
+ }
+}
+
+void GtkSalGraphics::handleDamage(const tools::Rectangle& rDamagedRegion)
+{
+ assert(m_pWidgetDraw);
+ assert(!rDamagedRegion.IsEmpty());
+ mpFrame->damaged(rDamagedRegion.Left(), rDamagedRegion.Top(), rDamagedRegion.GetWidth(), rDamagedRegion.GetHeight());
+}
+
+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;
+ const gchar *styleClass = 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:
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ context = mpCheckMenuItemCheckStyle;
+ else
+ {
+ context = gtk_widget_get_style_context(gCheckMenuItemWidget);
+ styleClass = GTK_STYLE_CLASS_CHECK;
+ }
+ renderType = RenderType::Check;
+ nType = ControlType::Checkbox;
+ if (nState & ControlState::PRESSED)
+ {
+ flags = static_cast<GtkStateFlags>(flags | GTK_STATE_FLAG_CHECKED);
+ }
+ break;
+ case ControlPart::MenuItemRadioMark:
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ context = mpRadioMenuItemRadioStyle;
+ else
+ {
+ context = gtk_widget_get_style_context(gCheckMenuItemWidget);
+ styleClass = GTK_STYLE_CLASS_RADIO;
+ }
+ 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:
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ context = mpMenuItemArrowStyle;
+ else
+ {
+ context = gtk_widget_get_style_context(gCheckMenuItemWidget);
+ styleClass = GTK_STYLE_CLASS_ARROW;
+ }
+ 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 = mpToolbarSeperatorStyle;
+ 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;
+ break;
+ 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 | ACTIVE_TAB());
+ renderType = RenderType::TabItem;
+ break;
+ case ControlType::WindowBackground:
+ context = gtk_widget_get_style_context(gtk_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(false);
+ clipRegion(cr);
+ cairo_translate(cr, rControlRegion.Left(), rControlRegion.Top());
+
+ long nX = 0;
+ long nY = 0;
+ long nWidth = rControlRegion.GetWidth();
+ long nHeight = rControlRegion.GetHeight();
+
+ StyleContextSave aContextState;
+ aContextState.save(context);
+ style_context_set_state(context, flags);
+
+ if (styleClass)
+ {
+ gtk_style_context_add_class(context, styleClass);
+ }
+
+ // 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();
+ gtk_css_provider_load_from_data(pBgCssProvider, aResult.getStr(), aResult.getLength(), nullptr);
+ 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;
+
+ gtk_style_context_get_border(context, flags, &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);
+
+ long nProgressWidth = rValue.getNumericVal();
+ if (nProgressWidth)
+ {
+ GtkBorder padding;
+ gtk_style_context_get_padding(context, gtk_style_context_get_state(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:
+ {
+ if (gtk_check_version(3, 20, 0) != nullptr)
+ {
+ 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 (styleClass)
+ {
+ gtk_style_context_remove_class(context, styleClass);
+ }
+ 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);
+ long nHeight = std::max<long>(rControlRegion.GetHeight(), aReq.height);
+ return tools::Rectangle(rControlRegion.TopLeft(), Size(rControlRegion.GetWidth(), nHeight));
+}
+
+static tools::Rectangle AdjustRectForTextBordersPadding(GtkStyleContext* pStyle, long nContentWidth, long nContentHeight, const tools::Rectangle& rControlRegion)
+{
+ GtkBorder border;
+ gtk_style_context_get_border(pStyle, gtk_style_context_get_state(pStyle), &border);
+
+ GtkBorder padding;
+ gtk_style_context_get_padding(pStyle, gtk_style_context_get_state(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, point;
+
+ 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;
+ gtk_style_context_get_border(pButtonStyle, gtk_style_context_get_state(pButtonStyle), &border);
+
+ GtkBorder padding;
+ gtk_style_context_get_padding(pButtonStyle, gtk_style_context_get_state(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 );
+
+ 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;
+ DrawFrameFlags nStyle = static_cast<DrawFrameFlags>(rValue.getNumericVal() & 0xfff0);
+ if (nStyle & DrawFrameFlags::NoDraw)
+ {
+ GtkBorder padding;
+ gtk_style_context_get_padding(mpFrameInStyle, gtk_style_context_get_state(mpFrameInStyle), &padding);
+
+ GtkBorder border;
+ gtk_style_context_get_border(mpFrameInStyle, gtk_style_context_get_state(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
+ rNativeContentRegion = rControlRegion;
+ }
+ else
+ {
+ return false;
+ }
+
+ rNativeBoundingRegion = aEditRect;
+ rNativeContentRegion = rNativeBoundingRegion;
+
+ return true;
+}
+/************************************************************************
+ * 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 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);
+}
+
+vcl::Font pango_to_vcl(const PangoFontDescription* font, const css::lang::Locale& rLocale)
+{
+ OString aFamily = pango_font_description_get_family( font );
+ int nPangoHeight = pango_font_description_get_size( 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 nPointHeight = nPangoHeight/PANGO_SCALE;
+
+ vcl::Font aFont( aInfo.m_aFamilyName, Size( 0, nPointHeight ) );
+ 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 = gtk_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();
+ GdkRGBA color;
+
+ // text colors
+ GdkRGBA text_color;
+ style_context_set_state(pStyle, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_get_color(pStyle, gtk_style_context_get_state(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
+ GdkRGBA background_color;
+ gtk_style_context_get_background_color(pStyle, gtk_style_context_get_state(pStyle), &background_color);
+
+ ::Color aBackColor = getColor( background_color );
+ aStyleSet.BatchSetBackgrounds( aBackColor );
+
+ // UI font
+ vcl::Font aFont(getFont(pStyle, rSettings.GetUILanguageTag().getLocale()));
+
+ 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);
+ gtk_style_context_get_color(pStyle, gtk_style_context_get_state(pStyle), &text_color);
+ aTextColor = getColor(text_color);
+ aStyleSet.SetDefaultButtonRolloverTextColor(aTextColor);
+ aStyleSet.SetButtonRolloverTextColor(aTextColor);
+ aStyleSet.SetDefaultActionButtonRolloverTextColor(aTextColor);
+ aStyleSet.SetActionButtonRolloverTextColor(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);
+ gtk_style_context_get_color(mpButtonStyle, gtk_style_context_get_state(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));
+ gtk_style_context_get_color(mpButtonStyle, gtk_style_context_get_state(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();
+ }
+
+ // 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_bg_color, tooltip_fg_color;
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_get_color(pCStyle, gtk_style_context_get_state(pCStyle), &tooltip_fg_color);
+ gtk_style_context_get_background_color(pCStyle, gtk_style_context_get_state(pCStyle), &tooltip_bg_color);
+
+ aContextState.restore();
+ g_object_unref( pCStyle );
+
+ aStyleSet.SetHelpColor( getColor( tooltip_bg_color ));
+ aStyleSet.SetHelpTextColor( getColor( tooltip_fg_color ));
+ }
+
+ {
+ // 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 );
+ aContextState.save(pCStyle);
+
+ // highlighting colors
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_SELECTED);
+ gtk_style_context_get_background_color(pCStyle, gtk_style_context_get_state(pCStyle), &text_color);
+ ::Color aHighlightColor = getColor( text_color );
+ gtk_style_context_get_color(pCStyle, gtk_style_context_get_state(pCStyle), &text_color);
+ ::Color aHighlightTextColor = getColor( text_color );
+ aStyleSet.SetHighlightColor( aHighlightColor );
+ aStyleSet.SetHighlightTextColor( aHighlightTextColor );
+
+ // field background color
+ GdkRGBA field_background_color;
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_get_background_color(pCStyle, gtk_style_context_get_state(pCStyle), &field_background_color);
+
+ ::Color aBackFieldColor = getColor( field_background_color );
+ aStyleSet.SetFieldColor( aBackFieldColor );
+ // This baby is the default page/paper color
+ aStyleSet.SetWindowColor( aBackFieldColor );
+
+ // Cursor width
+ gfloat caretAspectRatio = 0.04f;
+ gtk_style_context_get_style( pCStyle, "cursor-aspect-ratio", &caretAspectRatio, nullptr );
+ // 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);
+ gtk_style_context_get_color(pCStyle, gtk_style_context_get_state(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();
+ g_object_unref( pCStyle );
+
+ // Tab colors
+ aStyleSet.SetActiveTabColor( aBackFieldColor ); // same as the window color.
+ aStyleSet.SetInactiveTabColor( aBackColor );
+ }
+
+ // menu disabled entries handling
+ aStyleSet.SetSkipDisabledInMenus( true );
+ aStyleSet.SetPreferredContextMenuShortcuts( false );
+
+ aContextState.save(mpMenuItemLabelStyle);
+
+ // menu colors
+ style_context_set_state(mpMenuStyle, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_get_background_color( mpMenuStyle, gtk_style_context_get_state(mpMenuStyle), &background_color );
+ aBackColor = getColor( background_color );
+ aStyleSet.SetMenuColor( aBackColor );
+
+ // menu bar
+ style_context_set_state(mpMenuBarStyle, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_get_background_color( mpMenuBarStyle, gtk_style_context_get_state(mpMenuBarStyle), &background_color );
+ aBackColor = getColor( background_color );
+ aStyleSet.SetMenuBarColor( aBackColor );
+ aStyleSet.SetMenuBarRolloverColor( aBackColor );
+
+ style_context_set_state(mpMenuBarItemStyle, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_get_color( mpMenuBarItemStyle, gtk_style_context_get_state(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);
+ gtk_style_context_get_color( mpMenuBarItemStyle, gtk_style_context_get_state(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);
+ gtk_style_context_get_color(mpMenuItemLabelStyle, gtk_style_context_get_state(mpMenuItemLabelStyle), &color);
+ aTextColor = getColor(color);
+ aStyleSet.SetMenuTextColor(aTextColor);
+
+ style_context_set_state(mpMenuItemLabelStyle, GTK_STATE_FLAG_PRELIGHT);
+ gtk_style_context_get_background_color( mpMenuItemLabelStyle, gtk_style_context_get_state(mpMenuItemLabelStyle), &background_color );
+ ::Color aHighlightColor = getColor( background_color );
+ aStyleSet.SetMenuHighlightColor( aHighlightColor );
+
+ gtk_style_context_get_color( mpMenuItemLabelStyle, gtk_style_context_get_state(mpMenuItemLabelStyle), &color );
+ ::Color aHighlightTextColor = getColor( color );
+ aStyleSet.SetMenuHighlightTextColor( aHighlightTextColor );
+
+ aContextState.restore();
+
+ // hyperlink colors
+ aContextState.save(mpLinkButtonStyle);
+ style_context_set_state(mpLinkButtonStyle, GTK_STATE_FLAG_LINK);
+ gtk_style_context_get_color(mpLinkButtonStyle, gtk_style_context_get_state(mpLinkButtonStyle), &text_color);
+ aStyleSet.SetLinkColor(getColor(text_color));
+ style_context_set_state(mpLinkButtonStyle, GTK_STATE_FLAG_VISITED);
+ gtk_style_context_get_color(mpLinkButtonStyle, gtk_style_context_get_state(mpLinkButtonStyle), &text_color);
+ aStyleSet.SetVisitedLinkColor(getColor(text_color));
+ aContextState.restore();
+
+ {
+ GtkStyleContext *pCStyle = mpNotebookHeaderTabsTabLabelStyle;
+ aContextState.save(pCStyle);
+ style_context_set_state(pCStyle, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_get_color(pCStyle, gtk_style_context_get_state(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);
+ gtk_style_context_get_color(pCStyle, gtk_style_context_get_state(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);
+ gtk_style_context_get_color(pCStyle, gtk_style_context_get_state(pCStyle), &text_color);
+ aTextColor = getColor( text_color );
+ aStyleSet.SetTabRolloverTextColor(aTextColor);
+ aContextState.restore();
+ }
+
+ {
+ GtkStyleContext *pCStyle = mpNotebookHeaderTabsTabActiveLabelStyle;
+ aContextState.save(pCStyle);
+ style_context_set_state(pCStyle, ACTIVE_TAB());
+ gtk_style_context_get_color(pCStyle, gtk_style_context_get_state(pCStyle), &text_color);
+ aTextColor = getColor( text_color );
+ aStyleSet.SetTabHighlightTextColor(aTextColor);
+ aContextState.restore();
+ }
+
+ // 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;
+
+ // Grab some button style attributes
+ if (gtk_check_version(3, 20, 0) == nullptr)
+ {
+ 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);
+ aStyleSet.SetMinThumbSize(min_slider_length);
+ }
+ else
+ {
+ gint slider_width = 14;
+ gint trough_border = 1;
+
+ gtk_style_context_get_style(mpVScrollbarStyle,
+ "slider-width", &slider_width,
+ "trough-border", &trough_border,
+ "min-slider-length", &min_slider_length,
+ nullptr);
+ aStyleSet.SetScrollBarSize(slider_width + 2*trough_border);
+ gint magic = trough_border ? 1 : 0;
+ aStyleSet.SetMinThumbSize(min_slider_length - magic);
+ }
+
+ // 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 );
+
+ // finally update the collected settings
+ rSettings.SetStyleSettings( aStyleSet );
+#if OSL_DEBUG_LEVEL > 1
+ gchar* pThemeName = NULL;
+ g_object_get( pSettings, "gtk-theme-name", &pThemeName, nullptr );
+ SAL_INFO("vcl.gtk3", "Theme name is \""
+ << pThemeName
+ << "\".");
+ g_free(pThemeName);
+#endif
+
+ return true;
+}
+
+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;
+}
+
+#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
+ GtkWidget *pWidget = GTK_WIDGET(mpFrame->getFixedContainer());
+ gtk_widget_queue_draw(pWidget);
+}
+
+namespace {
+
+void getStyleContext(GtkStyleContext** style, GtkWidget* widget)
+{
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), widget);
+ *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 (gCacheWindow)
+ gtk_widget_destroy(gCacheWindow);
+}
+
+GtkSalGraphics::GtkSalGraphics( GtkSalFrame *pFrame, GtkWidget *pWindow )
+ : SvpSalGraphics(),
+ 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 */
+ gtk_widget_path_iter_set_object_nameFunc set_object_name =
+ reinterpret_cast<gtk_widget_path_iter_set_object_nameFunc>(osl_getAsciiFunctionSymbol(nullptr,
+ "gtk_widget_path_iter_set_object_name"));
+
+ gCacheWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gDumbContainer = gtk_fixed_new();
+ gtk_container_add(GTK_CONTAINER(gCacheWindow), gDumbContainer);
+ gtk_widget_realize(gDumbContainer);
+ gtk_widget_realize(gCacheWindow);
+
+ gEntryBox = gtk_entry_new();
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gEntryBox);
+
+ mpWindowStyle = createStyleContext(set_object_name, GtkControlPart::ToplevelWindow);
+ mpEntryStyle = createStyleContext(set_object_name, GtkControlPart::Entry);
+
+ getStyleContext(&mpTextViewStyle, gtk_text_view_new());
+
+ mpButtonStyle = createStyleContext(set_object_name, GtkControlPart::Button);
+ mpLinkButtonStyle = createStyleContext(set_object_name, GtkControlPart::LinkButton);
+
+ 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);
+ mpToolbarSeperatorStyle = 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));
+
+ mpVScrollbarStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarVertical);
+ mpVScrollbarContentsStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarVerticalContents);
+ mpVScrollbarTroughStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarVerticalTrough);
+ mpVScrollbarSliderStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarVerticalSlider);
+ mpVScrollbarButtonStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarVerticalButton);
+ mpHScrollbarStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarHorizontal);
+ mpHScrollbarContentsStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarHorizontalContents);
+ mpHScrollbarTroughStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarHorizontalTrough);
+ mpHScrollbarSliderStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarHorizontalSlider);
+ mpHScrollbarButtonStyle = createStyleContext(set_object_name, GtkControlPart::ScrollbarHorizontalButton);
+
+ mpCheckButtonStyle = createStyleContext(set_object_name, GtkControlPart::CheckButton);
+ mpCheckButtonCheckStyle = createStyleContext(set_object_name, GtkControlPart::CheckButtonCheck);
+
+ mpRadioButtonStyle = createStyleContext(set_object_name, GtkControlPart::RadioButton);
+ mpRadioButtonRadioStyle = createStyleContext(set_object_name, GtkControlPart::RadioButtonRadio);
+
+ /* Spinbutton */
+ gSpinBox = gtk_spin_button_new(nullptr, 0, 0);
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gSpinBox);
+ mpSpinStyle = createStyleContext(set_object_name, GtkControlPart::SpinButton);
+ mpSpinEntryStyle = createStyleContext(set_object_name, GtkControlPart::SpinButtonEntry);
+ mpSpinUpStyle = createStyleContext(set_object_name, GtkControlPart::SpinButtonUpButton);
+ mpSpinDownStyle = createStyleContext(set_object_name, GtkControlPart::SpinButtonDownButton);
+
+ /* NoteBook */
+ mpNotebookStyle = createStyleContext(set_object_name, GtkControlPart::Notebook);
+ mpNotebookStackStyle = createStyleContext(set_object_name, GtkControlPart::NotebookStack);
+ mpNotebookHeaderStyle = createStyleContext(set_object_name, GtkControlPart::NotebookHeader);
+ mpNotebookHeaderTabsStyle = createStyleContext(set_object_name, GtkControlPart::NotebookHeaderTabs);
+ mpNotebookHeaderTabsTabStyle = createStyleContext(set_object_name, GtkControlPart::NotebookHeaderTabsTab);
+ mpNotebookHeaderTabsTabLabelStyle = createStyleContext(set_object_name, GtkControlPart::NotebookHeaderTabsTabLabel);
+ mpNotebookHeaderTabsTabActiveLabelStyle = createStyleContext(set_object_name, GtkControlPart::NotebookHeaderTabsTabActiveLabel);
+ mpNotebookHeaderTabsTabHoverLabelStyle = createStyleContext(set_object_name, GtkControlPart::NotebookHeaderTabsTabHoverLabel);
+
+ /* Combobox */
+ gComboBox = gtk_combo_box_text_new_with_entry();
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gComboBox);
+ mpComboboxStyle = createStyleContext(set_object_name, GtkControlPart::Combobox);
+ mpComboboxBoxStyle = createStyleContext(set_object_name, GtkControlPart::ComboboxBox);
+ mpComboboxEntryStyle = createStyleContext(set_object_name, GtkControlPart::ComboboxBoxEntry);
+ mpComboboxButtonStyle = createStyleContext(set_object_name, GtkControlPart::ComboboxBoxButton);
+ mpComboboxButtonBoxStyle = createStyleContext(set_object_name, GtkControlPart::ComboboxBoxButtonBox);
+ mpComboboxButtonArrowStyle = createStyleContext(set_object_name, 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(set_object_name, GtkControlPart::Listbox);
+ mpListboxBoxStyle = createStyleContext(set_object_name, GtkControlPart::ListboxBox);
+ mpListboxButtonStyle = createStyleContext(set_object_name, GtkControlPart::ListboxBoxButton);
+ mpListboxButtonBoxStyle = createStyleContext(set_object_name, GtkControlPart::ListboxBoxButtonBox);
+ mpListboxButtonArrowStyle = createStyleContext(set_object_name, GtkControlPart::ListboxBoxButtonBoxArrow);
+
+ /* Menu bar */
+ gMenuBarWidget = gtk_menu_bar_new();
+ gMenuItemMenuBarWidget = gtk_menu_item_new_with_label( "b" );
+ gtk_menu_shell_append(GTK_MENU_SHELL(gMenuBarWidget), gMenuItemMenuBarWidget);
+ gtk_container_add(GTK_CONTAINER(gDumbContainer), gMenuBarWidget);
+
+ mpMenuBarStyle = createStyleContext(set_object_name, GtkControlPart::MenuBar);
+ mpMenuBarItemStyle = createStyleContext(set_object_name, GtkControlPart::MenuBarItem);
+
+ /* Menu */
+ mpMenuWindowStyle = createStyleContext(set_object_name, GtkControlPart::MenuWindow);
+ mpMenuStyle = createStyleContext(set_object_name, GtkControlPart::Menu);
+ GtkWidget *menu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(gMenuItemMenuBarWidget), menu);
+
+ /* Menu Items */
+ gCheckMenuItemWidget = gtk_check_menu_item_new_with_label("M");
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), gCheckMenuItemWidget);
+
+ mpMenuItemStyle = createStyleContext(set_object_name, GtkControlPart::MenuItem);
+ mpMenuItemLabelStyle = createStyleContext(set_object_name, GtkControlPart::MenuItemLabel);
+ mpMenuItemArrowStyle = createStyleContext(set_object_name, GtkControlPart::MenuItemArrow);
+ mpCheckMenuItemStyle = createStyleContext(set_object_name, GtkControlPart::CheckMenuItem);
+ mpCheckMenuItemCheckStyle = createStyleContext(set_object_name, GtkControlPart::CheckMenuItemCheck);
+ mpRadioMenuItemStyle = createStyleContext(set_object_name, GtkControlPart::RadioMenuItem);
+ mpRadioMenuItemRadioStyle = createStyleContext(set_object_name, GtkControlPart::RadioMenuItemRadio);
+ mpSeparatorMenuItemStyle = createStyleContext(set_object_name, GtkControlPart::SeparatorMenuItem);
+ mpSeparatorMenuItemSeparatorStyle = createStyleContext(set_object_name, GtkControlPart::SeparatorMenuItemSeparator);
+
+ /* Frames */
+ mpFrameOutStyle = mpFrameInStyle = createStyleContext(set_object_name, 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(set_object_name, GtkControlPart::ProgressBar);
+ mpProgressBarTroughStyle = createStyleContext(set_object_name, GtkControlPart::ProgressBarTrough);
+ mpProgressBarProgressStyle = createStyleContext(set_object_name, GtkControlPart::ProgressBarProgress);
+
+ gtk_widget_show_all(gDumbContainer);
+}
+
+void GtkSalGraphics::GetResolution(sal_Int32& rDPIX, sal_Int32& rDPIY)
+{
+ char* pForceDpi;
+ if ((pForceDpi = getenv("SAL_FORCEDPI")))
+ {
+ OString sForceDPI(pForceDpi);
+ rDPIX = rDPIY = sForceDPI.toInt32();
+ return;
+ }
+
+ 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;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtk3salprn-gtk.cxx b/vcl/unx/gtk3/gtk3salprn-gtk.cxx
new file mode 100644
index 000000000..62e88c919
--- /dev/null
+++ b/vcl/unx/gtk3/gtk3salprn-gtk.cxx
@@ -0,0 +1,954 @@
+/* -*- 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/gtkprintwrapper.hxx>
+
+#include <unx/gtk/gtkdata.hxx>
+#include <unx/gtk/gtkframe.hxx>
+#include <unx/gtk/gtkinst.hxx>
+#include <unx/gtk/gtkprn.hxx>
+
+#include <configsettings.hxx>
+#include <vcl/help.hxx>
+#include <vcl/print.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+
+#include <gtk/gtk.h>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/view/PrintableState.hpp>
+
+#include <officecfg/Office/Common.hxx>
+
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+
+#include <cstring>
+#include <map>
+
+namespace beans = com::sun::star::beans;
+namespace uno = com::sun::star::uno;
+namespace view = com::sun::star::view;
+
+using vcl::unx::GtkPrintWrapper;
+
+namespace {
+
+class GtkPrintDialog
+{
+public:
+ explicit GtkPrintDialog(vcl::PrinterController& io_rController);
+ bool run();
+ GtkPrinter* getPrinter() const
+ {
+ return m_xWrapper->print_unix_dialog_get_selected_printer(GTK_PRINT_UNIX_DIALOG(m_pDialog));
+ }
+ GtkPrintSettings* getSettings() const
+ {
+ return m_xWrapper->print_unix_dialog_get_settings(GTK_PRINT_UNIX_DIALOG(m_pDialog));
+ }
+ void updateControllerPrintRange();
+
+ ~GtkPrintDialog();
+
+ static void UIOption_CheckHdl(GtkWidget* i_pWidget, GtkPrintDialog* io_pThis)
+ {
+ io_pThis->impl_UIOption_CheckHdl(i_pWidget);
+ }
+ static void UIOption_RadioHdl(GtkWidget* i_pWidget, GtkPrintDialog* io_pThis)
+ {
+ io_pThis->impl_UIOption_RadioHdl(i_pWidget);
+ }
+ static void UIOption_SelectHdl(GtkWidget* i_pWidget, GtkPrintDialog* io_pThis)
+ {
+ io_pThis->impl_UIOption_SelectHdl(i_pWidget);
+ }
+
+private:
+ beans::PropertyValue* impl_queryPropertyValue(GtkWidget* i_pWidget) const;
+ void impl_checkOptionalControlDependencies();
+
+ void impl_UIOption_CheckHdl(GtkWidget* i_pWidget);
+ void impl_UIOption_RadioHdl(GtkWidget* i_pWidget);
+ void impl_UIOption_SelectHdl(GtkWidget* i_pWidget);
+
+ void impl_initDialog();
+ void impl_initCustomTab();
+ void impl_initPrintContent(uno::Sequence<sal_Bool> const& i_rDisabled);
+
+ void impl_readFromSettings();
+ void impl_storeToSettings() const;
+
+private:
+ GtkWidget* m_pDialog;
+ vcl::PrinterController& m_rController;
+ std::map<GtkWidget*, OUString> m_aControlToPropertyMap;
+ std::map<GtkWidget*, sal_Int32> m_aControlToNumValMap;
+ std::shared_ptr<GtkPrintWrapper> m_xWrapper;
+};
+
+}
+
+struct GtkSalPrinter_Impl
+{
+ OString m_sSpoolFile;
+ OUString m_sJobName;
+ GtkPrinter* m_pPrinter;
+ GtkPrintSettings* m_pSettings;
+
+ GtkSalPrinter_Impl();
+ ~GtkSalPrinter_Impl();
+};
+
+GtkSalPrinter_Impl::GtkSalPrinter_Impl()
+ : m_pPrinter(nullptr)
+ , m_pSettings(nullptr)
+{
+}
+
+GtkSalPrinter_Impl::~GtkSalPrinter_Impl()
+{
+ if (m_pPrinter)
+ {
+ g_object_unref(G_OBJECT(m_pPrinter));
+ m_pPrinter = nullptr;
+ }
+ if (m_pSettings)
+ {
+ g_object_unref(G_OBJECT(m_pSettings));
+ m_pSettings = nullptr;
+ }
+}
+
+namespace
+{
+
+GtkInstance const&
+lcl_getGtkSalInstance()
+{
+ // we _know_ this is GtkInstance
+ return *static_cast<GtkInstance*>(GetGtkSalData()->m_pInstance);
+}
+
+bool
+lcl_useSystemPrintDialog()
+{
+ return officecfg::Office::Common::Misc::UseSystemPrintDialog::get()
+ && officecfg::Office::Common::Misc::ExperimentalMode::get()
+ && lcl_getGtkSalInstance().getPrintWrapper()->supportsPrinting();
+}
+
+}
+
+GtkSalPrinter::GtkSalPrinter(SalInfoPrinter* const i_pInfoPrinter)
+ : PspSalPrinter(i_pInfoPrinter)
+{
+}
+
+GtkSalPrinter::~GtkSalPrinter() = default;
+
+bool
+GtkSalPrinter::impl_doJob(
+ const OUString* const i_pFileName,
+ const OUString& i_rJobName,
+ const OUString& i_rAppName,
+ ImplJobSetup* const io_pSetupData,
+ const bool i_bCollate,
+ vcl::PrinterController& io_rController)
+{
+ io_rController.setJobState(view::PrintableState_JOB_STARTED);
+ io_rController.jobStarted();
+ const bool bJobStarted(
+ PspSalPrinter::StartJob(i_pFileName, i_rJobName, i_rAppName,
+ 1/*i_nCopies*/, i_bCollate, true, io_pSetupData))
+ ;
+
+ if (bJobStarted)
+ {
+ io_rController.createProgressDialog();
+ const int nPages(io_rController.getFilteredPageCount());
+ for (int nPage(0); nPage != nPages; ++nPage)
+ {
+ if (nPage == nPages - 1)
+ io_rController.setLastPage(true);
+ io_rController.printFilteredPage(nPage);
+ }
+ io_rController.setJobState(view::PrintableState_JOB_COMPLETED);
+ }
+
+ return bJobStarted;
+}
+
+bool
+GtkSalPrinter::StartJob(
+ const OUString* const i_pFileName,
+ const OUString& i_rJobName,
+ const OUString& i_rAppName,
+ ImplJobSetup* io_pSetupData,
+ vcl::PrinterController& io_rController)
+{
+ if (!lcl_useSystemPrintDialog())
+ return PspSalPrinter::StartJob(i_pFileName, i_rJobName, i_rAppName, io_pSetupData, io_rController);
+
+ assert(!m_xImpl);
+
+ m_xImpl.reset(new GtkSalPrinter_Impl());
+ m_xImpl->m_sJobName = i_rJobName;
+
+ OString sFileName;
+ if (i_pFileName)
+ sFileName = OUStringToOString(*i_pFileName, osl_getThreadTextEncoding());
+
+ GtkPrintDialog aDialog(io_rController);
+ if (!aDialog.run())
+ {
+ io_rController.abortJob();
+ return false;
+ }
+ aDialog.updateControllerPrintRange();
+ m_xImpl->m_pPrinter = aDialog.getPrinter();
+ m_xImpl->m_pSettings = aDialog.getSettings();
+
+ //To-Do proper name, watch for encodings
+ sFileName = OString("/tmp/hacking.ps");
+ m_xImpl->m_sSpoolFile = sFileName;
+
+ OUString aFileName = OStringToOUString(sFileName, osl_getThreadTextEncoding());
+
+ //To-Do, swap ps/pdf for gtk_printer_accepts_ps()/gtk_printer_accepts_pdf() ?
+
+ return impl_doJob(&aFileName, i_rJobName, i_rAppName, io_pSetupData, /*bCollate*/false, io_rController);
+}
+
+bool
+GtkSalPrinter::EndJob()
+{
+ bool bRet = PspSalPrinter::EndJob();
+
+ if (!lcl_useSystemPrintDialog())
+ return bRet;
+
+ assert(m_xImpl);
+
+ if (!bRet || m_xImpl->m_sSpoolFile.isEmpty())
+ return bRet;
+
+ std::shared_ptr<GtkPrintWrapper> const xWrapper(lcl_getGtkSalInstance().getPrintWrapper());
+
+ GtkPageSetup* pPageSetup = xWrapper->page_setup_new();
+
+ GtkPrintJob* const pJob = xWrapper->print_job_new(
+ OUStringToOString(m_xImpl->m_sJobName, RTL_TEXTENCODING_UTF8).getStr(),
+ m_xImpl->m_pPrinter, m_xImpl->m_pSettings, pPageSetup);
+
+ GError* error = nullptr;
+ bRet = xWrapper->print_job_set_source_file(pJob, m_xImpl->m_sSpoolFile.getStr(), &error);
+ if (bRet)
+ xWrapper->print_job_send(pJob, nullptr, nullptr, nullptr);
+ else
+ {
+ //To-Do, do something with this
+ SAL_WARN("vcl.gtk3", "error was " << error->message);
+ g_error_free(error);
+ }
+
+ g_object_unref(pPageSetup);
+ m_xImpl.reset();
+
+ //To-Do, remove temp spool file
+
+ return bRet;
+}
+
+namespace
+{
+
+void
+lcl_setHelpText(
+ GtkWidget* const io_pWidget,
+ const uno::Sequence<OUString>& i_rHelpTexts,
+ const sal_Int32 i_nIndex)
+{
+ if (i_nIndex >= 0 && i_nIndex < i_rHelpTexts.getLength())
+ gtk_widget_set_tooltip_text(io_pWidget,
+ OUStringToOString(i_rHelpTexts.getConstArray()[i_nIndex], RTL_TEXTENCODING_UTF8).getStr());
+}
+
+GtkWidget*
+lcl_makeFrame(
+ GtkWidget* const i_pChild,
+ const OUString &i_rText,
+ const uno::Sequence<OUString> &i_rHelpTexts,
+ sal_Int32* const io_pCurHelpText)
+{
+ GtkWidget* const pLabel = gtk_label_new(nullptr);
+ lcl_setHelpText(pLabel, i_rHelpTexts, !io_pCurHelpText ? 0 : (*io_pCurHelpText)++);
+ gtk_misc_set_alignment(GTK_MISC(pLabel), 0.0, 0.5);
+
+ {
+ gchar* const pText = g_markup_printf_escaped("<b>%s</b>",
+ OUStringToOString(i_rText, RTL_TEXTENCODING_UTF8).getStr());
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(pLabel), pText);
+ g_free(pText);
+ }
+
+ GtkWidget* const pFrame = gtk_vbox_new(FALSE, 6);
+ gtk_box_pack_start(GTK_BOX(pFrame), pLabel, FALSE, FALSE, 0);
+
+ GtkWidget* const pAlignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(pAlignment), 0, 0, 12, 0);
+ gtk_box_pack_start(GTK_BOX(pFrame), pAlignment, FALSE, FALSE, 0);
+
+ gtk_container_add(GTK_CONTAINER(pAlignment), i_pChild);
+ return pFrame;
+}
+
+void
+lcl_extractHelpTextsOrIds(
+ const beans::PropertyValue& rEntry,
+ uno::Sequence<OUString>& rHelpStrings)
+{
+ if (!(rEntry.Value >>= rHelpStrings))
+ {
+ OUString aHelpString;
+ if (rEntry.Value >>= aHelpString)
+ {
+ rHelpStrings.realloc(1);
+ *rHelpStrings.getArray() = aHelpString;
+ }
+ }
+}
+
+GtkWidget*
+lcl_combo_box_text_new()
+{
+ return gtk_combo_box_text_new();
+}
+
+void
+lcl_combo_box_text_append(GtkWidget* const pWidget, gchar const* const pText)
+{
+ gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(pWidget), pText);
+}
+
+}
+
+GtkPrintDialog::GtkPrintDialog(vcl::PrinterController& io_rController)
+ : m_rController(io_rController)
+ , m_xWrapper(lcl_getGtkSalInstance().getPrintWrapper())
+{
+ assert(m_xWrapper->supportsPrinting());
+ impl_initDialog();
+ impl_initCustomTab();
+ impl_readFromSettings();
+}
+
+void
+GtkPrintDialog::impl_initDialog()
+{
+ //To-Do, like fpicker, set UI language
+ m_pDialog = m_xWrapper->print_unix_dialog_new();
+
+ vcl::Window* const pTopWindow(Application::GetActiveTopWindow());
+ if (pTopWindow)
+ {
+ GtkSalFrame* const pFrame(dynamic_cast<GtkSalFrame*>(pTopWindow->ImplGetFrame()));
+ if (pFrame)
+ {
+ GtkWindow* const pParent(GTK_WINDOW(pFrame->getWindow()));
+ if (pParent)
+ gtk_window_set_transient_for(GTK_WINDOW(m_pDialog), pParent);
+ }
+ }
+
+ m_xWrapper->print_unix_dialog_set_manual_capabilities(GTK_PRINT_UNIX_DIALOG(m_pDialog),
+ GtkPrintCapabilities(GTK_PRINT_CAPABILITY_COPIES
+ | GTK_PRINT_CAPABILITY_COLLATE
+ | GTK_PRINT_CAPABILITY_REVERSE
+ | GTK_PRINT_CAPABILITY_GENERATE_PS
+ | GTK_PRINT_CAPABILITY_NUMBER_UP
+ | GTK_PRINT_CAPABILITY_NUMBER_UP_LAYOUT
+ ));
+}
+
+void
+GtkPrintDialog::impl_initCustomTab()
+{
+ typedef std::vector<std::pair<GtkWidget*, OUString> > CustomTabs_t;
+
+ const uno::Sequence<beans::PropertyValue>& rOptions(m_rController.getUIOptions());
+ std::map<OUString, GtkWidget*> aPropertyToDependencyRowMap;
+ CustomTabs_t aCustomTabs;
+ GtkWidget* pCurParent = nullptr;
+ GtkWidget* pCurTabPage = nullptr;
+ GtkWidget* pCurSubGroup = nullptr;
+ bool bIgnoreSubgroup = false;
+ for (const auto& rOption : rOptions)
+ {
+ uno::Sequence<beans::PropertyValue> aOptProp;
+ rOption.Value >>= aOptProp;
+
+ OUString aCtrlType;
+ OUString aText;
+ OUString aPropertyName;
+ uno::Sequence<OUString> aChoices;
+ uno::Sequence<sal_Bool> aChoicesDisabled;
+ uno::Sequence<OUString> aHelpTexts;
+ sal_Int64 nMinValue = 0, nMaxValue = 0;
+ sal_Int32 nCurHelpText = 0;
+ OUString aDependsOnName;
+ sal_Int32 nDependsOnValue = 0;
+ bool bUseDependencyRow = false;
+ bool bIgnore = false;
+ GtkWidget* pGroup = nullptr;
+ bool bGtkInternal = false;
+
+ //Fix fdo#69381
+ //Next options if this one is empty
+ if (!aOptProp.hasElements())
+ continue;
+
+ for (const beans::PropertyValue& rEntry : std::as_const(aOptProp))
+ {
+ if ( rEntry.Name == "Text" )
+ {
+ OUString aValue;
+ rEntry.Value >>= aValue;
+ aText = aValue.replace('~', '_');
+ }
+ else if ( rEntry.Name == "ControlType" )
+ rEntry.Value >>= aCtrlType;
+ else if ( rEntry.Name == "Choices" )
+ rEntry.Value >>= aChoices;
+ else if ( rEntry.Name == "ChoicesDisabled" )
+ rEntry.Value >>= aChoicesDisabled;
+ else if ( rEntry.Name == "Property" )
+ {
+ beans::PropertyValue aVal;
+ rEntry.Value >>= aVal;
+ aPropertyName = aVal.Name;
+ }
+ else if ( rEntry.Name == "DependsOnName" )
+ rEntry.Value >>= aDependsOnName;
+ else if ( rEntry.Name == "DependsOnEntry" )
+ rEntry.Value >>= nDependsOnValue;
+ else if ( rEntry.Name == "AttachToDependency" )
+ rEntry.Value >>= bUseDependencyRow;
+ else if ( rEntry.Name == "MinValue" )
+ rEntry.Value >>= nMinValue;
+ else if ( rEntry.Name == "MaxValue" )
+ rEntry.Value >>= nMaxValue;
+ else if ( rEntry.Name == "HelpId" )
+ {
+ uno::Sequence<OUString> aHelpIds;
+ lcl_extractHelpTextsOrIds(rEntry, aHelpIds);
+ Help* const pHelp = Application::GetHelp();
+ if (pHelp)
+ {
+ const int nLen = aHelpIds.getLength();
+ aHelpTexts.realloc(nLen);
+ std::transform(aHelpIds.begin(), aHelpIds.end(), aHelpTexts.begin(),
+ [&pHelp](const OUString& rHelpId) { return pHelp->GetHelpText(rHelpId, static_cast<weld::Widget*>(nullptr)); });
+ }
+ else // fallback
+ aHelpTexts = aHelpIds;
+ }
+ else if ( rEntry.Name == "HelpText" )
+ lcl_extractHelpTextsOrIds(rEntry, aHelpTexts);
+ else if ( rEntry.Name == "InternalUIOnly" )
+ rEntry.Value >>= bIgnore;
+ else if ( rEntry.Name == "Enabled" )
+ {
+ // Ignore this. We use UIControlOptions::isUIOptionEnabled
+ // to check whether a control should be enabled.
+ }
+ else if ( rEntry.Name == "GroupingHint" )
+ {
+ // Ignore this. We cannot add/modify controls to/on existing
+ // tabs of the Gtk print dialog.
+ }
+ else
+ {
+ SAL_INFO("vcl.gtk", "unhandled UI option entry: " << rEntry.Name);
+ }
+ }
+
+ if ( aPropertyName == "PrintContent" )
+ bGtkInternal = true;
+
+ if (aCtrlType == "Group" || !pCurParent)
+ {
+ pCurTabPage = gtk_vbox_new(FALSE, 12);
+ gtk_container_set_border_width(GTK_CONTAINER(pCurTabPage), 6);
+ lcl_setHelpText(pCurTabPage, aHelpTexts, 0);
+
+ pCurParent = pCurTabPage;
+ aCustomTabs.emplace_back(pCurTabPage, aText);
+ }
+ else if (aCtrlType == "Subgroup")
+ {
+ bIgnoreSubgroup = bIgnore;
+ if (bIgnore)
+ continue;
+ pCurParent = gtk_vbox_new(FALSE, 12);
+ gtk_container_set_border_width(GTK_CONTAINER(pCurParent), 0);
+
+ pCurSubGroup = lcl_makeFrame(pCurParent, aText, aHelpTexts, nullptr);
+ gtk_box_pack_start(GTK_BOX(pCurTabPage), pCurSubGroup, FALSE, FALSE, 0);
+ }
+ // special case: we need to map these to controls of the gtk print dialog
+ else if (bGtkInternal)
+ {
+ if ( aPropertyName == "PrintContent" )
+ {
+ // What to print? And, more importantly, is there a selection?
+ impl_initPrintContent(aChoicesDisabled);
+ }
+ }
+ else if (bIgnoreSubgroup || bIgnore)
+ continue;
+ else
+ {
+ // change handlers for all the controls set up in this block
+ // should be set _after_ the control has been made (in)active,
+ // because:
+ // 1. value of the property is _known_--we are using it to
+ // _set_ the control, right?--no need to change it back .-)
+ // 2. it may cause warning because the widget may not
+ // have been placed in m_aControlToPropertyMap yet
+
+ GtkWidget* pWidget = nullptr;
+ beans::PropertyValue* pVal = nullptr;
+ if (aCtrlType == "Bool" && pCurParent)
+ {
+ pWidget = gtk_check_button_new_with_mnemonic(
+ OUStringToOString(aText, RTL_TEXTENCODING_UTF8).getStr());
+ lcl_setHelpText(pWidget, aHelpTexts, 0);
+ m_aControlToPropertyMap[pWidget] = aPropertyName;
+
+ bool bVal = false;
+ pVal = m_rController.getValue(aPropertyName);
+ if (pVal)
+ pVal->Value >>= bVal;
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pWidget), bVal);
+ gtk_widget_set_sensitive(pWidget,
+ m_rController.isUIOptionEnabled(aPropertyName) && pVal != nullptr);
+ g_signal_connect(pWidget, "toggled", G_CALLBACK(GtkPrintDialog::UIOption_CheckHdl), this);
+ }
+ else if (aCtrlType == "Radio" && pCurParent)
+ {
+ GtkWidget* const pVbox = gtk_vbox_new(FALSE, 12);
+ gtk_container_set_border_width(GTK_CONTAINER(pVbox), 0);
+
+ if (!aText.isEmpty())
+ pGroup = lcl_makeFrame(pVbox, aText, aHelpTexts, &nCurHelpText);
+
+ sal_Int32 nSelectVal = 0;
+ pVal = m_rController.getValue(aPropertyName);
+ if (pVal && pVal->Value.hasValue())
+ pVal->Value >>= nSelectVal;
+
+ for (sal_Int32 m = 0; m != aChoices.getLength(); m++)
+ {
+ pWidget = gtk_radio_button_new_with_mnemonic_from_widget(
+ GTK_RADIO_BUTTON(m == 0 ? nullptr : pWidget),
+ OUStringToOString(aChoices[m].replace('~', '_'), RTL_TEXTENCODING_UTF8).getStr());
+ lcl_setHelpText(pWidget, aHelpTexts, nCurHelpText++);
+ m_aControlToPropertyMap[pWidget] = aPropertyName;
+ m_aControlToNumValMap[pWidget] = m;
+ GtkWidget* const pRow = gtk_hbox_new(FALSE, 12);
+ gtk_box_pack_start(GTK_BOX(pVbox), pRow, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(pRow), pWidget, FALSE, FALSE, 0);
+ aPropertyToDependencyRowMap[aPropertyName + OUString::number(m)] = pRow;
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pWidget), m == nSelectVal);
+ gtk_widget_set_sensitive(pWidget,
+ m_rController.isUIOptionEnabled(aPropertyName) && pVal != nullptr);
+ g_signal_connect(pWidget, "toggled",
+ G_CALLBACK(GtkPrintDialog::UIOption_RadioHdl), this);
+ }
+
+ if (pGroup)
+ pWidget = pGroup;
+ else
+ pWidget = pVbox;
+ }
+ else if ((aCtrlType == "List" ||
+ aCtrlType == "Range" ||
+ aCtrlType == "Edit"
+ ) && pCurParent)
+ {
+ GtkWidget* const pHbox = gtk_hbox_new(FALSE, 12);
+ gtk_container_set_border_width(GTK_CONTAINER(pHbox), 0);
+
+ if ( aCtrlType == "List" )
+ {
+ pWidget = lcl_combo_box_text_new();
+
+ for (const auto& rChoice : std::as_const(aChoices))
+ {
+ lcl_combo_box_text_append(pWidget,
+ OUStringToOString(rChoice, RTL_TEXTENCODING_UTF8).getStr());
+ }
+
+ sal_Int32 nSelectVal = 0;
+ pVal = m_rController.getValue(aPropertyName);
+ if (pVal && pVal->Value.hasValue())
+ pVal->Value >>= nSelectVal;
+ gtk_combo_box_set_active(GTK_COMBO_BOX(pWidget), nSelectVal);
+ g_signal_connect(pWidget, "changed", G_CALLBACK(GtkPrintDialog::UIOption_SelectHdl), this);
+ }
+ else if (aCtrlType == "Edit" && pCurParent)
+ {
+ pWidget = gtk_entry_new();
+
+ OUString aCurVal;
+ pVal = m_rController.getValue(aPropertyName);
+ if (pVal && pVal->Value.hasValue())
+ pVal->Value >>= aCurVal;
+ gtk_entry_set_text(GTK_ENTRY(pWidget),
+ OUStringToOString(aCurVal, RTL_TEXTENCODING_UTF8).getStr());
+ }
+ else if (aCtrlType == "Range" && pCurParent)
+ {
+ pWidget = gtk_spin_button_new_with_range(nMinValue, nMaxValue, 1.0);
+
+ sal_Int64 nCurVal = 0;
+ pVal = m_rController.getValue(aPropertyName);
+ if (pVal && pVal->Value.hasValue())
+ pVal->Value >>= nCurVal;
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(pWidget), nCurVal);
+ }
+
+ lcl_setHelpText(pWidget, aHelpTexts, 0);
+ m_aControlToPropertyMap[pWidget] = aPropertyName;
+
+ gtk_widget_set_sensitive(pWidget,
+ m_rController.isUIOptionEnabled(aPropertyName) && pVal != nullptr);
+
+ if (!aText.isEmpty())
+ {
+ GtkWidget* const pLabel = gtk_label_new_with_mnemonic(
+ OUStringToOString(aText, RTL_TEXTENCODING_UTF8).getStr());
+ gtk_label_set_mnemonic_widget(GTK_LABEL(pLabel), pWidget);
+ gtk_box_pack_start(GTK_BOX(pHbox), pLabel, FALSE, FALSE, 0);
+ }
+
+ gtk_box_pack_start(GTK_BOX(pHbox), pWidget, FALSE, FALSE, 0);
+
+ pWidget = pHbox;
+
+ }
+ else
+ SAL_INFO("vcl.gtk", "unhandled option type: " << aCtrlType);
+
+ GtkWidget* pRow = nullptr;
+ if (pWidget)
+ {
+ if (bUseDependencyRow && !aDependsOnName.isEmpty())
+ {
+ pRow = aPropertyToDependencyRowMap[aDependsOnName + OUString::number(nDependsOnValue)];
+ if (!pRow)
+ {
+ gtk_widget_destroy(pWidget);
+ pWidget = nullptr;
+ }
+ }
+ }
+ if (pWidget)
+ {
+ if (!pRow)
+ {
+ pRow = gtk_hbox_new(FALSE, 12);
+ gtk_box_pack_start(GTK_BOX(pCurParent), pRow, FALSE, FALSE, 0);
+ }
+ if (!pGroup)
+ aPropertyToDependencyRowMap[aPropertyName + OUString::number(0)] = pRow;
+ gtk_box_pack_start(GTK_BOX(pRow), pWidget, FALSE, FALSE, 0);
+ }
+ }
+ }
+
+ CustomTabs_t::const_reverse_iterator aEnd = aCustomTabs.rend();
+ for (CustomTabs_t::const_reverse_iterator aI = aCustomTabs.rbegin(); aI != aEnd; ++aI)
+ {
+ gtk_widget_show_all(aI->first);
+ m_xWrapper->print_unix_dialog_add_custom_tab(GTK_PRINT_UNIX_DIALOG(m_pDialog), aI->first,
+ gtk_label_new(OUStringToOString(aI->second, RTL_TEXTENCODING_UTF8).getStr()));
+ }
+}
+
+void
+GtkPrintDialog::impl_initPrintContent(uno::Sequence<sal_Bool> const& i_rDisabled)
+{
+ SAL_WARN_IF(i_rDisabled.getLength() != 3, "vcl.gtk", "there is more choices than we expected");
+ if (i_rDisabled.getLength() != 3)
+ return;
+
+ GtkPrintUnixDialog* const pDialog(GTK_PRINT_UNIX_DIALOG(m_pDialog));
+
+ // XXX: This is a hack that depends on the number and the ordering of
+ // the controls in the rDisabled sequence (cf. the initialization of
+ // the "PrintContent" UI option in SwPrintUIOptions::SwPrintUIOptions,
+ // sw/source/core/view/printdata.cxx)
+ if (m_xWrapper->supportsPrintSelection() && !i_rDisabled[2])
+ {
+ m_xWrapper->print_unix_dialog_set_support_selection(pDialog, true);
+ m_xWrapper->print_unix_dialog_set_has_selection(pDialog, true);
+ }
+
+ beans::PropertyValue* const pPrintContent(
+ m_rController.getValue(OUString("PrintContent")));
+
+ if (pPrintContent)
+ {
+ sal_Int32 nSelectionType(0);
+ pPrintContent->Value >>= nSelectionType;
+ GtkPrintSettings* const pSettings(getSettings());
+ GtkPrintPages ePrintPages(GTK_PRINT_PAGES_ALL);
+ switch (nSelectionType)
+ {
+ case 0:
+ ePrintPages = GTK_PRINT_PAGES_ALL;
+ break;
+ case 1:
+ ePrintPages = GTK_PRINT_PAGES_RANGES;
+ break;
+ case 2:
+ if (m_xWrapper->supportsPrintSelection())
+ ePrintPages = GTK_PRINT_PAGES_SELECTION;
+ else
+ SAL_INFO("vcl.gtk", "the application wants to print a selection, but the present gtk version does not support it");
+ break;
+ default:
+ SAL_WARN("vcl.gtk", "unexpected selection type: " << nSelectionType);
+ }
+ m_xWrapper->print_settings_set_print_pages(pSettings, ePrintPages);
+ m_xWrapper->print_unix_dialog_set_settings(pDialog, pSettings);
+ g_object_unref(G_OBJECT(pSettings));
+ }
+}
+
+void
+GtkPrintDialog::impl_checkOptionalControlDependencies()
+{
+ for (auto& rEntry : m_aControlToPropertyMap)
+ {
+ gtk_widget_set_sensitive(rEntry.first, m_rController.isUIOptionEnabled(rEntry.second));
+ }
+}
+
+beans::PropertyValue*
+GtkPrintDialog::impl_queryPropertyValue(GtkWidget* const i_pWidget) const
+{
+ beans::PropertyValue* pVal(nullptr);
+ std::map<GtkWidget*, OUString>::const_iterator aIt(m_aControlToPropertyMap.find(i_pWidget));
+ if (aIt != m_aControlToPropertyMap.end())
+ {
+ pVal = m_rController.getValue(aIt->second);
+ SAL_WARN_IF(!pVal, "vcl.gtk", "property value not found");
+ }
+ else
+ {
+ SAL_WARN("vcl.gtk", "changed control not in property map");
+ }
+ return pVal;
+}
+
+void
+GtkPrintDialog::impl_UIOption_CheckHdl(GtkWidget* const i_pWidget)
+{
+ beans::PropertyValue* const pVal = impl_queryPropertyValue(i_pWidget);
+ if (pVal)
+ {
+ const bool bVal = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(i_pWidget));
+ pVal->Value <<= bVal;
+
+ impl_checkOptionalControlDependencies();
+ }
+}
+
+void
+GtkPrintDialog::impl_UIOption_RadioHdl(GtkWidget* const i_pWidget)
+{
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(i_pWidget)))
+ {
+ beans::PropertyValue* const pVal = impl_queryPropertyValue(i_pWidget);
+ std::map<GtkWidget*, sal_Int32>::const_iterator it = m_aControlToNumValMap.find(i_pWidget);
+ if (pVal && it != m_aControlToNumValMap.end())
+ {
+
+ const sal_Int32 nVal = it->second;
+ pVal->Value <<= nVal;
+
+ impl_checkOptionalControlDependencies();
+ }
+ }
+}
+
+void
+GtkPrintDialog::impl_UIOption_SelectHdl(GtkWidget* const i_pWidget)
+{
+ beans::PropertyValue* const pVal = impl_queryPropertyValue(i_pWidget);
+ if (pVal)
+ {
+ const sal_Int32 nVal(gtk_combo_box_get_active(GTK_COMBO_BOX(i_pWidget)));
+ pVal->Value <<= nVal;
+
+ impl_checkOptionalControlDependencies();
+ }
+}
+
+bool
+GtkPrintDialog::run()
+{
+ bool bDoJob = false;
+ bool bContinue = true;
+ while (bContinue)
+ {
+ bContinue = false;
+ const gint nStatus = gtk_dialog_run(GTK_DIALOG(m_pDialog));
+ switch (nStatus)
+ {
+ case GTK_RESPONSE_HELP:
+ SAL_WARN("vcl.gtk3", "To-Do: Help ?");
+ bContinue = true;
+ break;
+ case GTK_RESPONSE_OK:
+ bDoJob = true;
+ break;
+ default:
+ break;
+ }
+ }
+ gtk_widget_hide(m_pDialog);
+ impl_storeToSettings();
+ return bDoJob;
+}
+
+void
+GtkPrintDialog::updateControllerPrintRange()
+{
+ GtkPrintSettings* const pSettings(getSettings());
+ // TODO: use get_print_pages
+ if (const gchar* const pStr = m_xWrapper->print_settings_get(pSettings, GTK_PRINT_SETTINGS_PRINT_PAGES))
+ {
+ beans::PropertyValue* pVal = m_rController.getValue(OUString("PrintRange"));
+ if (!pVal)
+ pVal = m_rController.getValue(OUString("PrintContent"));
+ SAL_WARN_IF(!pVal, "vcl.gtk", "Nothing to map standard print options to!");
+ if (pVal)
+ {
+ sal_Int32 nVal = 0;
+ if (!strcmp(pStr, "all"))
+ nVal = 0;
+ else if (!strcmp(pStr, "ranges"))
+ nVal = 1;
+ else if (!strcmp(pStr, "selection"))
+ nVal = 2;
+ pVal->Value <<= nVal;
+
+ if (nVal == 1)
+ {
+ pVal = m_rController.getValue(OUString("PageRange"));
+ SAL_WARN_IF(!pVal, "vcl.gtk", "PageRange doesn't exist!");
+ if (pVal)
+ {
+ OUStringBuffer sBuf;
+ gint num_ranges;
+ const GtkPageRange* const pRanges = m_xWrapper->print_settings_get_page_ranges(pSettings, &num_ranges);
+ for (gint i = 0; i != num_ranges && pRanges; ++i)
+ {
+ sBuf.append(sal_Int32(pRanges[i].start+1));
+ if (pRanges[i].start != pRanges[i].end)
+ {
+ sBuf.append('-');
+ sBuf.append(sal_Int32(pRanges[i].end+1));
+ }
+
+ if (i != num_ranges-1)
+ sBuf.append(',');
+ }
+ pVal->Value <<= sBuf.makeStringAndClear();
+ }
+ }
+ }
+ }
+ g_object_unref(G_OBJECT(pSettings));
+}
+
+GtkPrintDialog::~GtkPrintDialog()
+{
+ gtk_widget_destroy(m_pDialog);
+}
+
+void
+GtkPrintDialog::impl_readFromSettings()
+{
+ vcl::SettingsConfigItem* const pItem(vcl::SettingsConfigItem::get());
+ GtkPrintSettings* const pSettings(getSettings());
+
+ const OUString aPrintDialogStr("PrintDialog");
+ const OUString aCopyCount(pItem->getValue(aPrintDialogStr,
+ "CopyCount"));
+ const OUString aCollate(pItem->getValue(aPrintDialogStr,
+ "Collate"));
+
+ const gint nOldCopyCount(m_xWrapper->print_settings_get_n_copies(pSettings));
+ const sal_Int32 nCopyCount(aCopyCount.toInt32());
+ if (nCopyCount > 0 && nOldCopyCount != nCopyCount)
+ {
+ m_xWrapper->print_settings_set_n_copies(pSettings, sal::static_int_cast<gint>(nCopyCount));
+ }
+
+ const bool bOldCollate(m_xWrapper->print_settings_get_collate(pSettings));
+ const bool bCollate(aCollate.equalsIgnoreAsciiCase("true"));
+ if (bOldCollate != bCollate)
+ {
+ m_xWrapper->print_settings_set_collate(pSettings, bCollate);
+ }
+
+ m_xWrapper->print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(m_pDialog), pSettings);
+ g_object_unref(G_OBJECT(pSettings));
+}
+
+void
+GtkPrintDialog::impl_storeToSettings()
+const
+{
+ vcl::SettingsConfigItem* const pItem(vcl::SettingsConfigItem::get());
+ GtkPrintSettings* const pSettings(getSettings());
+
+ const OUString aPrintDialogStr("PrintDialog");
+ pItem->setValue(aPrintDialogStr,
+ "CopyCount",
+ OUString::number(m_xWrapper->print_settings_get_n_copies(pSettings)));
+ pItem->setValue(aPrintDialogStr,
+ "Collate",
+ m_xWrapper->print_settings_get_collate(pSettings)
+ ? OUString("true")
+ : OUString("false"))
+ ;
+ // pItem->setValue(aPrintDialog, OUString("ToFile"), );
+ g_object_unref(G_OBJECT(pSettings));
+ pItem->Commit();
+}
+
+sal_uInt32
+GtkSalInfoPrinter::GetCapabilities(
+ const ImplJobSetup* const i_pSetupData,
+ const PrinterCapType i_nType)
+{
+ if (i_nType == PrinterCapType::ExternalDialog && lcl_useSystemPrintDialog())
+ return 1;
+ return PspSalInfoPrinter::GetCapabilities(i_pSetupData, i_nType);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3/gtkprintwrapper.hxx b/vcl/unx/gtk3/gtkprintwrapper.hxx
new file mode 100644
index 000000000..9a0c8e3ee
--- /dev/null
+++ b/vcl/unx/gtk3/gtkprintwrapper.hxx
@@ -0,0 +1,17 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_VCL_UNX_GTK3_INC_GTKPRINTWRAPPER_HXX
+#define INCLUDED_VCL_UNX_GTK3_INC_GTKPRINTWRAPPER_HXX
+
+#include "gtk/gtkprintwrapper.hxx"
+
+#endif // INCLUDED_VCL_UNX_GTK3_INC_GTKPRINTWRAPPER_HXX
+
+/* 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..b05929b90
--- /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/gtk3atkaction.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..d31e5e429
--- /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/gtk3atkbridge.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..63f44a3a1
--- /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/gtk3atkcomponent.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..064e72d98
--- /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/gtk3atkeditabletext.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..86d9ac43a
--- /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/gtk3atkfactory.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..d2ce059aa
--- /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/gtk3atkhypertext.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..3a1234e07
--- /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/gtk3atkimage.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..51d53bf91
--- /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/gtk3atklistener.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..f5e791720
--- /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/gtk3atkregistry.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..ab39c0e13
--- /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/gtk3atkselection.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..194791b68
--- /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/gtk3atktable.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..8d668e6e6
--- /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/gtk3atktext.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..c767a95d0
--- /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/gtk3atktextattributes.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..39eb5aeeb
--- /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/gtk3atkutil.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..5a526e1cf
--- /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/gtk3atkvalue.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..b0029f273
--- /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/gtk3atkwrapper.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..fc271f160
--- /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/cairo_gtk3_cairo.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
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..eec532d40
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker.cxx
@@ -0,0 +1,456 @@
+/* -*- 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 <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)
+{
+ const char* resId = nullptr;
+
+ switch (controlId)
+ {
+ case CHECKBOX_AUTOEXTENSION:
+ resId = STR_FPICKER_AUTO_EXTENSION;
+ break;
+ case CHECKBOX_PASSWORD:
+ resId = STR_FPICKER_PASSWORD;
+ break;
+ case CHECKBOX_FILTEROPTIONS:
+ resId = STR_FPICKER_FILTER_OPTIONS;
+ break;
+ case CHECKBOX_READONLY:
+ resId = STR_FPICKER_READONLY;
+ break;
+ case CHECKBOX_LINK:
+ resId = STR_FPICKER_INSERT_AS_LINK;
+ break;
+ case CHECKBOX_PREVIEW:
+ resId = STR_FPICKER_SHOW_PREVIEW;
+ break;
+ case CHECKBOX_SELECTION:
+ resId = STR_FPICKER_SELECTION;
+ break;
+ case CHECKBOX_GPGENCRYPTION:
+ resId = STR_FPICKER_GPGENCRYPT;
+ break;
+ case PUSHBUTTON_PLAY:
+ resId = STR_FPICKER_PLAY;
+ break;
+ case LISTBOX_VERSION:
+ resId = STR_FPICKER_VERSION;
+ break;
+ case LISTBOX_TEMPLATE:
+ resId = STR_FPICKER_TEMPLATES;
+ break;
+ case LISTBOX_IMAGE_TEMPLATE:
+ resId = STR_FPICKER_IMAGE_TEMPLATE;
+ break;
+ case LISTBOX_IMAGE_ANCHOR:
+ resId = STR_FPICKER_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_FPICKER_SAVE : STR_FPICKER_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..80c15938b
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.cxx
@@ -0,0 +1,275 @@
+/* -*- 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_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/syswin.hxx>
+
+#include <osl/file.h>
+#include <osl/process.h>
+
+#include <gtk/gtk.h>
+
+#include <boost/filesystem/path.hpp>
+
+#include <svdata.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(OString(ret.c_str(), strlen(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(OString(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);
+ for (size_t i = 0; i < numFiles; ++i)
+ {
+ readIpcArg(stream, seq[i]);
+ }
+}
+
+void sendIpcArg(std::ostream& stream, const OUString& string)
+{
+ const auto utf8 = string.toUtf8();
+ sendIpcStringArg(stream, utf8.getLength(), utf8.getStr());
+}
+
+OUString getResString(const char* pResId)
+{
+ if (pResId == nullptr)
+ return {};
+
+ return VclResId(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()
+{
+ vcl::Window* pParentWin = Application::GetDefDialogParent();
+ if (!pParentWin)
+ return {};
+
+ const SystemEnvData* pSysData = static_cast<SystemWindow*>(pParentWin)->GetSystemData();
+ if (!pSysData)
+ return {};
+
+ sendCommand(Commands::SetWinId, pSysData->aWindow);
+
+ auto* pMainWindow = static_cast<GtkWidget*>(pSysData->pWidget);
+ if (!pMainWindow)
+ return {};
+
+ 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, pSysData->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..e24fb92de
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_filepicker_ipc.hxx
@@ -0,0 +1,134 @@
+/* -*- 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 "filepicker_ipc_commands.hxx"
+
+#include <functional>
+#include <mutex>
+#include <thread>
+#include <sstream>
+
+OUString getResString(const char* 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..fa0b562cc
--- /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 <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_FPICKER_FOLDER_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..fa71136f9
--- /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/gtk3gloactiongroup.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..de79c14c1
--- /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/gtk3glomenu.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..200a74131
--- /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/gtk3gtkdata.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..0b2eb38dc
--- /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/gtk3gtkframe.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..e25ec8b0e
--- /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/gtk3gtkinst.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..a2eb6b9e1
--- /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/gtk3gtkobject.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..9eb1c7975
--- /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/gtk3gtksalmenu.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..8f6b38843
--- /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/gtk3gtksys.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..eb1592389
--- /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/gtk3hudawareness.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_printwrapper.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_printwrapper.cxx
new file mode 100644
index 000000000..33768f8ca
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_printwrapper.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/gtk3gtkprintwrapper.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..108e41d47
--- /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/gtk3salnativewidgets-gtk.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/gtk3_kde5/gtk3_kde5_salprn-gtk.cxx b/vcl/unx/gtk3_kde5/gtk3_kde5_salprn-gtk.cxx
new file mode 100644
index 000000000..a9f8c076f
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/gtk3_kde5_salprn-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/gtk3salprn-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..65953e4f2
--- /dev/null
+++ b/vcl/unx/gtk3_kde5/kde5_filepicker.cxx
@@ -0,0 +1,292 @@
+/* -*- 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)
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ _dialog->setConfirmOverwrite(true);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ _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: */