include/sfx2/tabdlg.hxx              |   16 +++++++++++--
 include/vcl/tabs.hrc                 |   38 ++++++++++++++++++++++++++++++
 include/vcl/weld.hxx                 |    9 ++++---
 sfx2/source/dialog/tabdlg.cxx        |   25 +++++++++++++++++---
 vcl/inc/jsdialog/jsdialogbuilder.hxx |    6 +++-
 vcl/inc/qt5/QtInstanceNotebook.hxx   |    3 +-
 vcl/inc/salvtables.hxx               |    6 +++-
 vcl/jsdialog/jsdialogbuilder.cxx     |   10 ++++----
 vcl/qt5/QtInstanceNotebook.cxx       |    3 +-
 vcl/source/app/salvtables.cxx        |    8 ++++--
 vcl/unx/gtk3/gtkinst.cxx             |   43 +++++++++++++++++++++++++++++++----
 11 files changed, 141 insertions(+), 26 deletions(-)

New commits:
commit 05919359717b8b7b2fff6ad73088d34ee397e297
Author:     Heiko Tietze <tietze.he...@gmail.com>
AuthorDate: Thu Jul 3 12:36:28 2025 +0200
Commit:     Heiko Tietze <heiko.tie...@documentfoundation.org>
CommitDate: Wed Jul 9 10:50:14 2025 +0200

    Show icons on tabs
    
    Simplifies the setup of SfxTabDialogs as tabs can be added
    per code; and vertical tabs should be accompanied by icons.
    
    One definition of tab label/icon makes the code more consistent
    and minimizes the effort for localization.
    
    Allows to switch between horizontal and vertical tabs via code.
    
    Change-Id: Ib631bcbd76a8e24ab15ff824a37e346b82756b82
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187307
    Reviewed-by: Heiko Tietze <heiko.tie...@documentfoundation.org>
    Tested-by: Jenkins

diff --git a/include/sfx2/tabdlg.hxx b/include/sfx2/tabdlg.hxx
index 17d0dd035f78..71bad7aa6d59 100644
--- a/include/sfx2/tabdlg.hxx
+++ b/include/sfx2/tabdlg.hxx
@@ -129,11 +129,23 @@ public:
 
     void                AddTabPage(const OUString& rName,          // Name of 
the label for the new page to create
                                    const OUString& rLabel,         // UI Label 
for the new page to create
-                                   CreateTabPage pCreateFunc);     // != 0
+                                   CreateTabPage pCreateFunc,      // != 0
+                                   const OUString* pIconName = nullptr);
+
+    void                AddTabPage(const OUString& rName,
+                                   const OUString& rLabel,
+                                   CreateTabPage pCreateFunc,
+                                   const OUString& rIconName);
 
     void                AddTabPage(const OUString& rName,          // Name of 
the label for the new page to create
                                    const OUString& rLabel,         // UI Label 
for the new page to create
-                                   sal_uInt16 nPageCreateId);      // 
Identifier of the Factory Method to create the page
+                                   sal_uInt16 nPageCreateId,       // 
Identifier of the Factory Method to create the page
+                                   const OUString* pIconName = nullptr);
+
+    void                AddTabPage(const OUString& rName,
+                                   const OUString& rLabel,
+                                   sal_uInt16 nPageCreateId,
+                                   const OUString& rIconName);
 
     void                RemoveTabPage( const OUString& rName ); // Name of the 
label for the page in the notebook .ui
 
diff --git a/include/vcl/tabs.hrc b/include/vcl/tabs.hrc
new file mode 100644
index 000000000000..a0bb3f7c185a
--- /dev/null
+++ b/include/vcl/tabs.hrc
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+#pragma once
+
+#define NC_(Context, String) TranslateId(Context, u8##String)
+
+#include <rtl/ustring.hxx>
+#include <unotools/resmgr.hxx>
+
+struct TabData
+{
+    TranslateId aLabel;
+    OUString sIconName;
+};
+
+/*
+ * Tabs are used on dialogs with a few items, usually <4, where it should be
+ * accompanied with larger 32px icons and dialogs with more content using 24px 
icons;
+ * the effective icon is loaded per RID_M + RID_TAB*.sIconName
+*/
+
+static constexpr OUString RID_M = u"cmd/lc_"_ustr;
+static constexpr OUString RID_L = u"cmd/32/"_ustr;
+
+
+static OUString TabResId(TranslateId aId)
+{
+    return Translate::get(aId, Translate::Create("vcl"));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/include/vcl/weld.hxx b/include/vcl/weld.hxx
index 2fa451f32bc4..564004be1905 100644
--- a/include/vcl/weld.hxx
+++ b/include/vcl/weld.hxx
@@ -525,10 +525,13 @@ public:
     virtual void set_current_page(int nPage) = 0;
     virtual void set_current_page(const OUString& rIdent) = 0;
     virtual void remove_page(const OUString& rIdent) = 0;
-    virtual void insert_page(const OUString& rIdent, const OUString& rLabel, 
int nPos) = 0;
-    void append_page(const OUString& rIdent, const OUString& rLabel)
+    virtual void insert_page(const OUString& rIdent, const OUString& rLabel, 
int nPos,
+                             const OUString* pIconName = nullptr)
+        = 0;
+    void append_page(const OUString& rIdent, const OUString& rLabel,
+                     const OUString* pIconName = nullptr)
     {
-        insert_page(rIdent, rLabel, -1);
+        insert_page(rIdent, rLabel, -1, pIconName);
     }
     virtual void set_tab_label_text(const OUString& rIdent, const OUString& 
rLabel) = 0;
     virtual OUString get_tab_label_text(const OUString& rIdent) const = 0;
diff --git a/sfx2/source/dialog/tabdlg.cxx b/sfx2/source/dialog/tabdlg.cxx
index 9320146c9fe6..9bdc433dab5f 100644
--- a/sfx2/source/dialog/tabdlg.cxx
+++ b/sfx2/source/dialog/tabdlg.cxx
@@ -880,21 +880,38 @@ void SfxTabDialogController::AddTabPage(const OUString 
&rName /* Page ID */,
 
 void SfxTabDialogController::AddTabPage(const OUString &rName, /* Page ID */
                                         const OUString& rRiderText,
-                                        CreateTabPage pCreateFunc  /* Pointer 
to the Factory Method */)
+                                        CreateTabPage pCreateFunc, /* Pointer 
to the Factory Method */
+                                        const OUString* pIconName)
 {
     assert(!m_xTabCtrl->get_page(rName) && "Double Page-Ids in the Tabpage");
-    m_xTabCtrl->append_page(rName, rRiderText);
+    m_xTabCtrl->append_page(rName, rRiderText, pIconName);
     AddTabPage(rName, pCreateFunc, nullptr);
 }
 
+void SfxTabDialogController::AddTabPage(const OUString &rName,
+                                        const OUString& rRiderText,
+                                        CreateTabPage pCreateFunc,
+                                        const OUString& rIconName)
+{
+    AddTabPage(rName, rRiderText, pCreateFunc, &rIconName);
+}
+
 void SfxTabDialogController::AddTabPage(const OUString &rName, const OUString& 
rRiderText,
-                                        sal_uInt16 nPageCreateId /* Identifier 
of the Factory Method to create the page */)
+                                        sal_uInt16 nPageCreateId, /* 
Identifier of the Factory Method to create the page */
+                                        const OUString* pIconName)
 {
     assert(!m_xTabCtrl->get_page(rName) && "Double Page-Ids in the Tabpage");
-    m_xTabCtrl->append_page(rName, rRiderText);
+    m_xTabCtrl->append_page(rName, rRiderText, pIconName);
     AddTabPage(rName, nPageCreateId);
 }
 
+void SfxTabDialogController::AddTabPage(const OUString &rName, const OUString& 
rRiderText,
+                                        sal_uInt16 nPageCreateId,
+                                        const OUString& rIconName)
+{
+    AddTabPage(rName, rRiderText, nPageCreateId, &rIconName);
+}
+
 /*  [Description]
 
     Default implementation of the virtual Method.
diff --git a/vcl/inc/jsdialog/jsdialogbuilder.hxx 
b/vcl/inc/jsdialog/jsdialogbuilder.hxx
index bc2817dd317c..be76e9c54164 100644
--- a/vcl/inc/jsdialog/jsdialogbuilder.hxx
+++ b/vcl/inc/jsdialog/jsdialogbuilder.hxx
@@ -557,7 +557,8 @@ public:
                bool bTakeOwnership);
 
     virtual void remove_page(const OUString& rIdent) override;
-    virtual void insert_page(const OUString& rIdent, const OUString& rLabel, 
int nPos) override;
+    virtual void insert_page(const OUString& rIdent, const OUString& rLabel, 
int nPos,
+                             const OUString* pIconName = nullptr) override;
 };
 
 class JSVerticalNotebook final : public JSWidget<SalInstanceVerticalNotebook, 
::VerticalTabControl>
@@ -567,7 +568,8 @@ public:
                        SalInstanceBuilder* pBuilder, bool bTakeOwnership);
 
     virtual void remove_page(const OUString& rIdent) override;
-    virtual void insert_page(const OUString& rIdent, const OUString& rLabel, 
int nPos) override;
+    virtual void insert_page(const OUString& rIdent, const OUString& rLabel, 
int nPos,
+                             const OUString* pIconName = nullptr) override;
 };
 
 class JSSpinButton final : public JSWidget<SalInstanceSpinButton, 
::FormattedField>
diff --git a/vcl/inc/qt5/QtInstanceNotebook.hxx 
b/vcl/inc/qt5/QtInstanceNotebook.hxx
index 2a18a65784a0..0e6e2280dd55 100644
--- a/vcl/inc/qt5/QtInstanceNotebook.hxx
+++ b/vcl/inc/qt5/QtInstanceNotebook.hxx
@@ -36,7 +36,8 @@ public:
     virtual void set_current_page(int nPage) override;
     virtual void set_current_page(const OUString& rIdent) override;
     virtual void remove_page(const OUString& rIdent) override;
-    virtual void insert_page(const OUString& rIdent, const OUString& rLabel, 
int nPos) override;
+    virtual void insert_page(const OUString& rIdent, const OUString& rLabel, 
int nPos,
+                             const OUString* pIconName = nullptr) override;
     virtual void set_tab_label_text(const OUString& rIdent, const OUString& 
rLabel) override;
     virtual OUString get_tab_label_text(const OUString& rIdent) const override;
     virtual void set_show_tabs(bool bShow) override;
diff --git a/vcl/inc/salvtables.hxx b/vcl/inc/salvtables.hxx
index 49ebd5bb0f23..0934d4d06309 100644
--- a/vcl/inc/salvtables.hxx
+++ b/vcl/inc/salvtables.hxx
@@ -1178,7 +1178,8 @@ public:
 
     virtual void remove_page(const OUString& rIdent) override;
 
-    virtual void insert_page(const OUString& rIdent, const OUString& rLabel, 
int nPos) override;
+    virtual void insert_page(const OUString& rIdent, const OUString& rLabel, 
int nPos,
+                             const OUString* pIconName = nullptr) override;
 
     virtual int get_n_pages() const override;
 
@@ -2333,7 +2334,8 @@ public:
 
     virtual void remove_page(const OUString& rIdent) override;
 
-    virtual void insert_page(const OUString& rIdent, const OUString& rLabel, 
int nPos) override;
+    virtual void insert_page(const OUString& rIdent, const OUString& rLabel, 
int nPos,
+                             const OUString* pIconName = nullptr) override;
 
     virtual int get_n_pages() const override;
 
diff --git a/vcl/jsdialog/jsdialogbuilder.cxx b/vcl/jsdialog/jsdialogbuilder.cxx
index 7f9693a9d4cf..c5414e70a615 100644
--- a/vcl/jsdialog/jsdialogbuilder.cxx
+++ b/vcl/jsdialog/jsdialogbuilder.cxx
@@ -1359,9 +1359,10 @@ void JSNotebook::remove_page(const OUString& rIdent)
     sendFullUpdate();
 }
 
-void JSNotebook::insert_page(const OUString& rIdent, const OUString& rLabel, 
int nPos)
+void JSNotebook::insert_page(const OUString& rIdent, const OUString& rLabel, 
int nPos,
+                             const OUString* pIconName)
 {
-    SalInstanceNotebook::insert_page(rIdent, rLabel, nPos);
+    SalInstanceNotebook::insert_page(rIdent, rLabel, nPos, pIconName);
     sendFullUpdate();
 }
 
@@ -1378,9 +1379,10 @@ void JSVerticalNotebook::remove_page(const OUString& 
rIdent)
     sendFullUpdate();
 }
 
-void JSVerticalNotebook::insert_page(const OUString& rIdent, const OUString& 
rLabel, int nPos)
+void JSVerticalNotebook::insert_page(const OUString& rIdent, const OUString& 
rLabel, int nPos,
+                                     const OUString* pIconName)
 {
-    SalInstanceVerticalNotebook::insert_page(rIdent, rLabel, nPos);
+    SalInstanceVerticalNotebook::insert_page(rIdent, rLabel, nPos, pIconName);
     sendFullUpdate();
 }
 
diff --git a/vcl/qt5/QtInstanceNotebook.cxx b/vcl/qt5/QtInstanceNotebook.cxx
index 706ee6aa583d..8ff95b59f8bf 100644
--- a/vcl/qt5/QtInstanceNotebook.cxx
+++ b/vcl/qt5/QtInstanceNotebook.cxx
@@ -100,7 +100,8 @@ void QtInstanceNotebook::remove_page(const OUString& rIdent)
     GetQtInstance().RunInMainThread([&] { 
m_pTabWidget->removeTab(get_page_index(rIdent)); });
 }
 
-void QtInstanceNotebook::insert_page(const OUString& rIdent, const OUString& 
rLabel, int nPos)
+void QtInstanceNotebook::insert_page(const OUString& rIdent, const OUString& 
rLabel, int nPos,
+                                     const OUString* /* pIconName */)
 {
     SolarMutexGuard g;
     GetQtInstance().RunInMainThread([&] {
diff --git a/vcl/source/app/salvtables.cxx b/vcl/source/app/salvtables.cxx
index ebfce424463f..4035f772c5bd 100644
--- a/vcl/source/app/salvtables.cxx
+++ b/vcl/source/app/salvtables.cxx
@@ -2723,7 +2723,8 @@ void SalInstanceNotebook::remove_page(const OUString& 
rIdent)
     }
 }
 
-void SalInstanceNotebook::insert_page(const OUString& rIdent, const OUString& 
rLabel, int nPos)
+void SalInstanceNotebook::insert_page(const OUString& rIdent, const OUString& 
rLabel, int nPos,
+                                      const OUString* /* pIconName */)
 {
     sal_uInt16 nPageCount = m_xNotebook->GetPageCount();
     sal_uInt16 nLastPageId = nPageCount ? m_xNotebook->GetPageId(nPageCount - 
1) : 0;
@@ -2847,12 +2848,13 @@ void SalInstanceVerticalNotebook::remove_page(const 
OUString& rIdent)
 }
 
 void SalInstanceVerticalNotebook::insert_page(const OUString& rIdent, const 
OUString& rLabel,
-                                              int nPos)
+                                              int nPos, const OUString* 
pIconName)
 {
     VclPtrInstance<VclGrid> xGrid(m_xNotebook->GetPageParent());
     xGrid->set_hexpand(true);
     xGrid->set_vexpand(true);
-    m_xNotebook->InsertPage(rIdent, rLabel, Image(), u""_ustr, xGrid, nPos);
+    Image aImage = pIconName ? Image(StockImage::Yes, *pIconName) : Image();
+    m_xNotebook->InsertPage(rIdent, rLabel, aImage, u""_ustr, xGrid, nPos);
 }
 
 int SalInstanceVerticalNotebook::get_n_pages() const { return 
m_xNotebook->GetPageCount(); }
diff --git a/vcl/unx/gtk3/gtkinst.cxx b/vcl/unx/gtk3/gtkinst.cxx
index 4924183de978..72b4a7125d1d 100644
--- a/vcl/unx/gtk3/gtkinst.cxx
+++ b/vcl/unx/gtk3/gtkinst.cxx
@@ -9048,12 +9048,47 @@ private:
         enable_notify_events();
     }
 
-    void insert_page(GtkNotebook *pNotebook, const OUString& rIdent, const 
OUString& rLabel, GtkWidget *pChild, int nPos)
+    void insert_page(GtkNotebook* pNotebook, const OUString& rIdent, const 
OUString& rLabel,
+                     GtkWidget* pChild, int nPos, const OUString* pIconName = 
nullptr)
     {
         disable_notify_events();
 
-        GtkWidget *pTabWidget = 
gtk_label_new_with_mnemonic(MapToGtkAccelerator(rLabel).getStr());
+        GtkWidget* pLabel = 
gtk_label_new_with_mnemonic(MapToGtkAccelerator(rLabel).getStr());
+        GtkWidget* pTabWidget = nullptr;
+
+        GtkWidget* pImage = nullptr;
+        if (pIconName)
+            pImage = image_new_from_icon_name(*pIconName);
+
+        if (pImage)
+        {
+            // image/label should be stacked vertically for only a few tabs 
with large icons
+            bool bLarge = false;
+#if !GTK_CHECK_VERSION(4, 0, 0)
+            GdkPixbuf* pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(pImage));
+            bLarge = gdk_pixbuf_get_height(pixbuf) > 24;
+#endif
+            GtkBox* pBox = GTK_BOX(gtk_box_new(bLarge ? 
GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, 6));
+            gtk_label_set_xalign(GTK_LABEL(pLabel), bLarge ? 0.5 : 0.0);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+            gtk_box_pack_start(pBox, pImage, false, true, 0);
+            gtk_box_pack_start(pBox, pLabel, true, true, 0);
+#else
+            gtk_box_insert_child_after(GTK_BOX(pBox), pImage, nullptr);
+            gtk_box_insert_child_after(GTK_BOX(pBox), pLabel, pImage);
+#endif
+            pTabWidget = GTK_WIDGET(pBox);
+#if !GTK_CHECK_VERSION(4, 0, 0)
+            gtk_widget_show_all(pTabWidget);
+#endif
+        }
+        else
+        {
+            pTabWidget = pLabel;
+        }
+
         ::set_buildable_id(GTK_BUILDABLE(pTabWidget), rIdent);
+
         gtk_notebook_insert_page(pNotebook, pChild, pTabWidget, nPos);
         gtk_widget_set_visible(pChild, true);
         gtk_widget_set_visible(pTabWidget, true);
@@ -9598,7 +9633,7 @@ public:
             m_aPages.erase(m_aPages.begin() + nPageIndex);
     }
 
-    virtual void insert_page(const OUString& rIdent, const OUString& rLabel, 
int nPos) override
+    virtual void insert_page(const OUString& rIdent, const OUString& rLabel, 
int nPos, const OUString* pIconName = nullptr) override
     {
         if (m_bOverFlowBoxActive)
         {
@@ -9610,7 +9645,7 @@ public:
         gtk_widget_set_visible(GTK_WIDGET(m_pOverFlowNotebook), false);
         m_bOverFlowBoxActive = false;
 
-        insert_page(m_pNotebook, rIdent, rLabel, gtk_grid_new(), nPos);
+        insert_page(m_pNotebook, rIdent, rLabel, gtk_grid_new(), nPos, 
pIconName);
     }
 
     virtual ~GtkInstanceNotebook() override

Reply via email to