include/vcl/builderbase.hxx       |    2 
 vcl/Library_vclplug_qt5.mk        |    2 
 vcl/Library_vclplug_qt6.mk        |    2 
 vcl/inc/qt5/QtBuilder.hxx         |   82 +++++++++++++
 vcl/inc/qt5/QtInstanceBuilder.hxx |    8 -
 vcl/inc/qt6/QtBuilder.hxx         |   12 +
 vcl/qt5/QtBuilder.cxx             |  233 ++++++++++++++++++++++++++++++++++++++
 vcl/qt5/QtInstanceBuilder.cxx     |   26 ++--
 vcl/qt6/QtBuilder.cxx             |   12 +
 9 files changed, 367 insertions(+), 12 deletions(-)

New commits:
commit 7282421451f4a85c06c34430e37a8e98a84f0919
Author:     OmkarAcharekar <omkarachareka...@gmail.com>
AuthorDate: Wed Sep 25 09:48:09 2024 +0200
Commit:     Michael Weghorn <m.wegh...@posteo.de>
CommitDate: Wed Sep 25 21:09:26 2024 +0200

    tdf#130857 qt weld: Implement QtBuilder + use it for first msg dialog
    
    This implements an initial QtBuilder, which is used by
    QtInstanceBuilder to create weld::Widget instances
    using native Qt widgets.
    
    This tries to be close to the VCL implementation (VclBuilder).
    The selected approach is based on Caolán's suggestion in [1] to
    rework VclBuilder to have overridable methods for widgets creation.
    
    This way, we can have common code for the UI parser but the
    function for widget creation is different.
    
    Qt equivalents for Gtk's widget classes in the .ui files:
    
    * GtkMessageDialog -> QMessageBox
    * GtkBox -> QLayout (can be QVBoxLayout/QHBoxLayout based on
      property in .ui file)
    * GtkButtonBox -> QDialogButtonBox
    * GtkButton -> QPushButton
    
    As QMessageBox already comes with a layout and
    a QDialogButtonBox, don't create new ones, but
    return the existing ones in QtBuilder::makeObject.
    
    This commit implements initial support for the
    above-mentioned widget types and adds the first
    message dialog to the list of supported .ui files
    in QtInstanceBuilder::IsUIFileSupported,
    so a native QMessageBox is used for it now, unless
    environment variable SAL_VCL_QT_NO_WELDED_WIDGETS
    is set when starting LibreOffice.
    
    The dialog ("modules/swriter/ui/inforeadonlydialog.ui")
    gets shown when taking the following steps:
    
    * start Writer
    * type "hello world"
    * select text
    * "Insert" -> "Section"
    * tick the "Protect" checkbox in the "Write Protection" section
    * close dialog via "Insert" button
    * try to type in the protected section
    
    The dialog can be dismissed using the default "OK" button.
    (Handling for response codes for buttons is not implemented
    yet, which will be needed when welding dialogs that have multiple
    buttons resulting in different behavior depending on what button
    gets clicked.)
    
    This change was originally submitted as a WIP change as part of
    Omkar Acharekar's Outreachy project "Implement Qt/KDE Frameworks
    theming using native Qt widgets" (see [2]), then further refined
    by Michael Weghorn.
    
    [1] 
https://lists.freedesktop.org/archives/libreoffice/2023-December/091288.html
    [2] 
https://lists.freedesktop.org/archives/libreoffice/2023-December/091281.html
    
    Co-authored-by: Michael Weghorn <m.wegh...@posteo.de>
    Change-Id: I6dd010a2138c245dc1e6d83dd08123898e9d9048
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/161831
    Tested-by: Jenkins
    Reviewed-by: Michael Weghorn <m.wegh...@posteo.de>

diff --git a/include/vcl/builderbase.hxx b/include/vcl/builderbase.hxx
index cee443c26310..a3ca6e4f673c 100644
--- a/include/vcl/builderbase.hxx
+++ b/include/vcl/builderbase.hxx
@@ -33,7 +33,7 @@ struct ComboBoxTextItem
     }
 };
 
-class UNLESS_MERGELIBS(VCL_DLLPUBLIC) BuilderBase
+class VCL_DLLPUBLIC BuilderBase
 {
 public:
     typedef std::map<OUString, OUString> stringmap;
diff --git a/vcl/Library_vclplug_qt5.mk b/vcl/Library_vclplug_qt5.mk
index b153e529c658..441405d851ba 100644
--- a/vcl/Library_vclplug_qt5.mk
+++ b/vcl/Library_vclplug_qt5.mk
@@ -52,6 +52,7 @@ $(eval $(call gb_Library_use_libraries,vclplug_qt5,\
     cppu \
     sal \
     salhelper \
+    xmlreader \
 ))
 
 $(eval $(call gb_Library_use_externals,vclplug_qt5,\
@@ -79,6 +80,7 @@ $(eval $(call gb_Library_add_exception_objects,vclplug_qt5,\
     vcl/qt5/QtAccessibleRegistry \
     vcl/qt5/QtAccessibleWidget \
     vcl/qt5/QtBitmap \
+    vcl/qt5/QtBuilder \
     vcl/qt5/QtClipboard \
     vcl/qt5/QtData \
     vcl/qt5/QtDragAndDrop \
diff --git a/vcl/Library_vclplug_qt6.mk b/vcl/Library_vclplug_qt6.mk
index a67231825a2e..67720df8c119 100644
--- a/vcl/Library_vclplug_qt6.mk
+++ b/vcl/Library_vclplug_qt6.mk
@@ -51,6 +51,7 @@ $(eval $(call gb_Library_use_libraries,vclplug_qt6,\
     cppu \
     sal \
     salhelper \
+    xmlreader \
 ))
 
 $(eval $(call gb_Library_use_externals,vclplug_qt6,\
@@ -78,6 +79,7 @@ $(eval $(call gb_Library_add_exception_objects,vclplug_qt6,\
     vcl/qt6/QtAccessibleRegistry \
     vcl/qt6/QtAccessibleWidget \
     vcl/qt6/QtBitmap \
+    vcl/qt6/QtBuilder \
     vcl/qt6/QtClipboard \
     vcl/qt6/QtData \
     vcl/qt6/QtDragAndDrop \
diff --git a/vcl/inc/qt5/QtBuilder.hxx b/vcl/inc/qt5/QtBuilder.hxx
new file mode 100644
index 000000000000..3617f45f57fd
--- /dev/null
+++ b/vcl/inc/qt5/QtBuilder.hxx
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include <QtCore/QObject>
+#include <QtWidgets/QDialog>
+#include <QtWidgets/QDialogButtonBox>
+#include <QtWidgets/QMessageBox>
+#include <QtWidgets/QPushButton>
+
+#include <rtl/ustring.hxx>
+#include <unotools/resmgr.hxx>
+#include <vcl/builder.hxx>
+
+class QtBuilder : public WidgetBuilder<QObject, QObject*>
+{
+private:
+    QObject* get_by_name(std::u16string_view sID);
+    struct WinAndId
+    {
+        OUString m_sID;
+        QObject* m_pWindow;
+        WinAndId(OUString sId, QObject* pWindow)
+            : m_sID(std::move(sId))
+            , m_pWindow(pWindow)
+        {
+        }
+    };
+
+    std::vector<WinAndId> m_aChildren;
+
+public:
+    QtBuilder(QObject* pParent, std::u16string_view sUIRoot, const OUString& 
rUIFile);
+    virtual ~QtBuilder();
+
+    template <typename T = QObject> T* get(std::u16string_view sID);
+
+    QObject* makeObject(QObject* pParent, std::u16string_view sName, const 
OUString& sID,
+                        stringmap& rMap);
+
+    virtual void applyAtkProperties(QObject* pObject, const stringmap& 
rProperties,
+                                    bool bToolbarItem) override;
+    virtual void applyPackingProperties(QObject* pCurrentChild, QObject* 
pParent,
+                                        const stringmap& rPackingProperties) 
override;
+    virtual void insertComboBoxOrListBoxItems(QObject* pObject, stringmap& 
rMap,
+                                              const 
std::vector<ComboBoxTextItem>& rItems) override;
+
+    virtual QObject* insertObject(QObject* pParent, const OUString& rClass, 
const OUString& rID,
+                                  stringmap& rProps, stringmap& 
rPangoAttributes,
+                                  stringmap& rAtkProps) override;
+
+    void tweakInsertedChild(QObject* pParent, QObject* pCurrentChild, 
std::string_view sType,
+                            std::string_view sInternalChild) override;
+
+    virtual void setPriority(QObject* pObject, int nPriority) override;
+    virtual void setContext(QObject* pObject,
+                            std::vector<vcl::EnumContext::Context>&& aContext) 
override;
+
+    virtual void set_response(std::u16string_view sID, short nResponse) 
override;
+
+private:
+    static void setProperties(QObject* obj, stringmap& rProps);
+    static QWidget* windowForObject(QObject* pObject);
+    static QDialogButtonBox* findButtonBox(QMessageBox* pMessageBox);
+};
+
+template <typename T> inline T* QtBuilder::get(std::u16string_view sID)
+{
+    QObject* w = get_by_name(sID);
+    return static_cast<T*>(w);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/qt5/QtInstanceBuilder.hxx 
b/vcl/inc/qt5/QtInstanceBuilder.hxx
index 747582d6f69c..3c9fec8b1189 100644
--- a/vcl/inc/qt5/QtInstanceBuilder.hxx
+++ b/vcl/inc/qt5/QtInstanceBuilder.hxx
@@ -9,6 +9,8 @@
 
 #pragma once
 
+#include "QtBuilder.hxx"
+
 #include <string_view>
 
 #include <QtWidgets/QWidget>
@@ -18,9 +20,11 @@
 
 class QtInstanceBuilder : public weld::Builder
 {
+private:
+    std::unique_ptr<QtBuilder> m_xBuilder;
+
 public:
-    QtInstanceBuilder(QWidget* pParent, std::u16string_view sUIRoot,
-                      const std::u16string_view sUIFile);
+    QtInstanceBuilder(QWidget* pParent, std::u16string_view sUIRoot, const 
OUString& rUIFile);
     ~QtInstanceBuilder();
 
     static bool IsUIFileSupported(const OUString& rUIFile);
diff --git a/vcl/inc/qt6/QtBuilder.hxx b/vcl/inc/qt6/QtBuilder.hxx
new file mode 100644
index 000000000000..99895979bb14
--- /dev/null
+++ b/vcl/inc/qt6/QtBuilder.hxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "../qt5/QtBuilder.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtBuilder.cxx b/vcl/qt5/QtBuilder.cxx
new file mode 100644
index 000000000000..b79c83959242
--- /dev/null
+++ b/vcl/qt5/QtBuilder.cxx
@@ -0,0 +1,233 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <QtBuilder.hxx>
+
+#include <QtTools.hxx>
+
+#include <rtl/ustrbuf.hxx>
+
+#include <QtWidgets/QDialog>
+#include <QtWidgets/QDialogButtonBox>
+#include <QtWidgets/QLayout>
+#include <QtWidgets/QPushButton>
+
+namespace
+{
+QString convertAccelerator(const OUString& rText)
+{
+    // preserve literal '&'s and use '&' instead of '_' for the accelerator
+    return toQString(rText.replaceAll("&", "&&").replace('_', '&'));
+}
+}
+
+QtBuilder::QtBuilder(QObject* pParent, std::u16string_view sUIRoot, const 
OUString& rUIFile)
+    : WidgetBuilder(sUIRoot, rUIFile, false)
+{
+    processUIFile(pParent);
+}
+
+QtBuilder::~QtBuilder() {}
+
+QObject* QtBuilder::get_by_name(std::u16string_view sID)
+{
+    for (auto const& child : m_aChildren)
+    {
+        if (child.m_sID == sID)
+            return child.m_pWindow;
+    }
+
+    return nullptr;
+}
+
+void QtBuilder::insertComboBoxOrListBoxItems(QObject*, stringmap&,
+                                             const 
std::vector<ComboBoxTextItem>&)
+{
+    assert(false && "comboboxes and list boxes are not supported yet");
+}
+
+QObject* QtBuilder::insertObject(QObject* pParent, const OUString& rClass, 
const OUString& rID,
+                                 stringmap& rProps, stringmap&, stringmap&)
+{
+    QObject* pCurrentChild = nullptr;
+
+    pCurrentChild = makeObject(pParent, rClass, rID, rProps);
+
+    setProperties(pCurrentChild, rProps);
+
+    rProps.clear();
+
+    return pCurrentChild;
+}
+
+QObject* QtBuilder::makeObject(QObject* pParent, std::u16string_view sName, 
const OUString& sID,
+                               stringmap& rMap)
+{
+    QWidget* pParentWidget = qobject_cast<QWidget*>(pParent);
+
+    QObject* pObject = nullptr;
+
+    if (sName == u"GtkMessageDialog")
+    {
+        pObject = new QMessageBox(pParentWidget);
+    }
+    else if (sName == u"GtkBox")
+    {
+        // for a QMessageBox, return the existing layout instead of creating a 
new one
+        if (QMessageBox* pMessageBox = qobject_cast<QMessageBox*>(pParent))
+        {
+            pObject = pMessageBox->layout();
+            assert(pObject && "QMessageBox has no layout");
+        }
+        else
+        {
+            const bool bVertical = hasOrientationVertical(rMap);
+            if (bVertical)
+                pObject = new QVBoxLayout();
+            else
+                pObject = new QHBoxLayout();
+        }
+    }
+    else if (sName == u"GtkButtonBox")
+    {
+        QWidget* pTopLevel = windowForObject(pParent);
+        if (QMessageBox* pMessageBox = qobject_cast<QMessageBox*>(pTopLevel))
+        {
+            // for a QMessageBox, return the existing button box instead of 
creating a new one
+            QDialogButtonBox* pButtonBox = findButtonBox(pMessageBox);
+            assert(pButtonBox && "Could not find QMessageBox's button box");
+            pObject = pButtonBox;
+        }
+        else
+        {
+            pObject = new QDialogButtonBox(pParentWidget);
+        }
+    }
+    else if (sName == u"GtkButton")
+    {
+        if (QDialogButtonBox* pButtonBox = 
qobject_cast<QDialogButtonBox*>(pParentWidget))
+        {
+            pObject = pButtonBox->addButton("", QDialogButtonBox::NoRole);
+
+            // for message boxes, avoid implicit standard buttons in addition 
to those explicitly added
+            if (QMessageBox* pMessageBox = 
qobject_cast<QMessageBox*>(pParentWidget->window()))
+                pMessageBox->setStandardButtons(QMessageBox::NoButton);
+        }
+        else
+        {
+            pObject = new QPushButton(pParentWidget);
+        }
+    }
+
+    m_aChildren.emplace_back(sID, pObject);
+
+    return pObject;
+}
+
+void QtBuilder::tweakInsertedChild(QObject*, QObject*, std::string_view, 
std::string_view) {}
+
+void QtBuilder::setPriority(QObject*, int) { SAL_WARN("vcl.qt", "Ignoring 
priority"); }
+
+void QtBuilder::setContext(QObject*, std::vector<vcl::EnumContext::Context>&&)
+{
+    SAL_WARN("vcl.qt", "Ignoring context");
+}
+
+void QtBuilder::applyAtkProperties(QObject*, const stringmap&, bool)
+{
+    SAL_WARN("vcl.qt", "QtBuilder::applyAtkProperties not implemented yet");
+}
+
+void QtBuilder::applyPackingProperties(QObject*, QObject*, const stringmap&)
+{
+    SAL_WARN("vcl.qt", "QtBuilder::applyPackingProperties not implemented 
yet");
+}
+
+void QtBuilder::set_response(std::u16string_view, short)
+{
+    SAL_WARN("vcl.qt", "QtBuilder::set_response not implemented yet");
+}
+
+void QtBuilder::setProperties(QObject* obj, stringmap& rProps)
+{
+    if (QMessageBox* pMessageBox = qobject_cast<QMessageBox*>(obj))
+    {
+        for (auto const & [ Key, rValue ] : rProps)
+        {
+            OUString rKey = Key;
+
+            if (rKey == u"text")
+            {
+                pMessageBox->setText(toQString(rValue));
+            }
+            else if (rKey == u"title")
+            {
+                pMessageBox->setWindowTitle(toQString(rValue));
+            }
+            else if (rKey == u"secondary-text")
+            {
+                pMessageBox->setInformativeText(toQString(rValue));
+            }
+            else if (rKey == u"message-type")
+            {
+                if (rValue == u"error")
+                    pMessageBox->setIcon(QMessageBox::Critical);
+                else if (rValue == u"info")
+                    pMessageBox->setIcon(QMessageBox::Information);
+                else if (rValue == u"question")
+                    pMessageBox->setIcon(QMessageBox::Question);
+                else if (rValue == u"warning")
+                    pMessageBox->setIcon(QMessageBox::Warning);
+                else
+                    assert(false && "Unhandled message-type");
+            }
+        }
+    }
+    else if (QPushButton* pButton = qobject_cast<QPushButton*>(obj))
+    {
+        for (auto const & [ rKey, rValue ] : rProps)
+        {
+            if (rKey == u"label")
+                pButton->setText(convertAccelerator(rValue));
+        }
+    }
+}
+
+QWidget* QtBuilder::windowForObject(QObject* pObject)
+{
+    if (QWidget* pWidget = qobject_cast<QWidget*>(pObject))
+        return pWidget->window();
+
+    if (QLayout* pLayout = qobject_cast<QLayout*>(pObject))
+    {
+        if (QWidget* pParentWidget = pLayout->parentWidget())
+            return pParentWidget->window();
+    }
+
+    return nullptr;
+}
+
+QDialogButtonBox* QtBuilder::findButtonBox(QMessageBox* pMessageBox)
+{
+    assert(pMessageBox);
+    QLayout* pLayout = pMessageBox->layout();
+    assert(pLayout && "QMessageBox has no layout");
+    for (int i = 0; i < pLayout->count(); i++)
+    {
+        QLayoutItem* pItem = pLayout->itemAt(i);
+        if (QWidget* pItemWidget = pItem->widget())
+        {
+            if (QDialogButtonBox* pButtonBox = 
qobject_cast<QDialogButtonBox*>(pItemWidget))
+                return pButtonBox;
+        }
+    }
+    return nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/qt5/QtInstanceBuilder.cxx b/vcl/qt5/QtInstanceBuilder.cxx
index 938be55ecfbc..68386ec6d3d2 100644
--- a/vcl/qt5/QtInstanceBuilder.cxx
+++ b/vcl/qt5/QtInstanceBuilder.cxx
@@ -11,31 +11,39 @@
 
 #include <unordered_set>
 
-QtInstanceBuilder::QtInstanceBuilder(QWidget*, std::u16string_view, 
std::u16string_view) {}
+#include <QtBuilder.hxx>
+#include <QtInstanceMessageDialog.hxx>
+
+QtInstanceBuilder::QtInstanceBuilder(QWidget* pParent, std::u16string_view 
sUIRoot,
+                                     const OUString& rUIFile)
+    : m_xBuilder(std::make_unique<QtBuilder>(pParent, sUIRoot, rUIFile))
+{
+}
 
 QtInstanceBuilder::~QtInstanceBuilder() {}
 
 bool QtInstanceBuilder::IsUIFileSupported(const OUString& rUIFile)
 {
-    // set of supported UI files - none yet
+    // set of supported UI files
     //
     // The idea is to implement functionality needed for a specific UI 
file/dialog
     // in QtInstanceBuilder, then add it to the set of supported UI files here.
     // This allows looking at one .ui file at a time and only having to 
implement
     // what is relevant for that particular one, without having to implement 
the full
     // weld API at once.
-    //
-    // see demo/WIP change: https://gerrit.libreoffice.org/c/core/+/161831
-    // for an example
-    static std::unordered_set<OUString> aSupportedUIFiles = {};
+    static std::unordered_set<OUString> aSupportedUIFiles = {
+        u"modules/swriter/ui/inforeadonlydialog.ui"_ustr,
+    };
 
     return aSupportedUIFiles.contains(rUIFile);
 }
 
-std::unique_ptr<weld::MessageDialog> 
QtInstanceBuilder::weld_message_dialog(const OUString&)
+std::unique_ptr<weld::MessageDialog> 
QtInstanceBuilder::weld_message_dialog(const OUString& id)
 {
-    assert(false && "Not implemented yet");
-    return nullptr;
+    QMessageBox* pMessageBox = m_xBuilder->get<QMessageBox>(id);
+    std::unique_ptr<weld::MessageDialog> xRet(
+        pMessageBox ? std::make_unique<QtInstanceMessageDialog>(pMessageBox) : 
nullptr);
+    return xRet;
 }
 
 std::unique_ptr<weld::Dialog> QtInstanceBuilder::weld_dialog(const OUString&)
diff --git a/vcl/qt6/QtBuilder.cxx b/vcl/qt6/QtBuilder.cxx
new file mode 100644
index 000000000000..6af0fcabcfaa
--- /dev/null
+++ b/vcl/qt6/QtBuilder.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 "../qt5/QtBuilder.cxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to