svx/source/tbxctrls/linectrl.cxx   |  153 +++++++++++++++++++++++--------------
 svx/uiconfig/ui/floatinglineend.ui |   38 ++++++---
 2 files changed, 122 insertions(+), 69 deletions(-)

New commits:
commit 3d80ea1e93b73e9a93d0debee0d82cbb17f856db
Author:     Parth Raiyani <[email protected]>
AuthorDate: Tue Aug 19 16:37:57 2025 +0530
Commit:     Caolán McNamara <[email protected]>
CommitDate: Wed Feb 25 09:18:09 2026 +0100

    Replaces ValueSet with IconView for line end selection
    
    - Updates the UI layout to use GtkIconView with better configuration 
options.
    - Simplifies logic for filling and updating the selection view.
    - Adds support for tooltips and single-click activation.
    - Removes obsolete code related to ValueSet, including custom sizing logic.
    
    Change-Id: I2bb486cc4e062fcfaec5c478958337b4af59c47b
    Signed-off-by: Parth Raiyani <[email protected]>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/189880
    Reviewed-by: Szymon Kłos <[email protected]>
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/200217
    Reviewed-by: Caolán McNamara <[email protected]>

diff --git a/svx/source/tbxctrls/linectrl.cxx b/svx/source/tbxctrls/linectrl.cxx
index ef901fcfb24e..b2941241529f 100644
--- a/svx/source/tbxctrls/linectrl.cxx
+++ b/svx/source/tbxctrls/linectrl.cxx
@@ -247,18 +247,16 @@ class SvxLineEndWindow final : public WeldToolbarPopup
 private:
     XLineEndListRef mpLineEndList;
     rtl::Reference<SvxLineEndToolBoxControl> mxControl;
-    std::unique_ptr<ValueSet> mxLineEndSet;
-    std::unique_ptr<weld::CustomWeld> mxLineEndSetWin;
-    sal_uInt16 mnLines;
+    std::unique_ptr<weld::IconView> mxLineEndIV;
     Size maBmpSize;
 
-    DECL_LINK( SelectHdl, ValueSet*, void );
-    void FillValueSet();
-    void SetSize();
+    DECL_LINK( ItemActivatedHdl, weld::IconView&, bool );
+    DECL_LINK(QueryTooltipHdl, const weld::TreeIter&, OUString);
+    void FillIconView();
 
     virtual void GrabFocus() override
     {
-        mxLineEndSet->GrabFocus();
+        mxLineEndIV->grab_focus();
     }
 
 public:
@@ -268,17 +266,12 @@ public:
 
 }
 
-constexpr sal_uInt16 gnCols = 2;
-
 SvxLineEndWindow::SvxLineEndWindow(SvxLineEndToolBoxControl* pControl, 
weld::Widget* pParent)
     : WeldToolbarPopup(pControl->getFrameInterface(), pParent, 
u"svx/ui/floatinglineend.ui"_ustr, u"FloatingLineEnd"_ustr)
     , mxControl(pControl)
-    , mxLineEndSet(new 
ValueSet(m_xBuilder->weld_scrolled_window(u"valuesetwin"_ustr, true)))
-    , mxLineEndSetWin(new weld::CustomWeld(*m_xBuilder, u"valueset"_ustr, 
*mxLineEndSet))
-    , mnLines(12)
+    , 
mxLineEndIV(m_xBuilder->weld_icon_view(u"floating_line_end_iconview"_ustr))
 {
-    mxLineEndSet->SetStyle(mxLineEndSet->GetStyle() | WB_ITEMBORDER | 
WB_3DLOOK | WB_NO_DIRECTSELECT);
-    mxLineEndSet->SetHelpId(HID_POPUP_LINEEND_CTRL);
+    mxLineEndIV->set_help_id(HID_POPUP_LINEEND_CTRL);
     m_xTopLevel->set_help_id(HID_POPUP_LINEEND);
 
     SfxObjectShell* pDocSh = SfxObjectShell::Current();
@@ -290,20 +283,32 @@ 
SvxLineEndWindow::SvxLineEndWindow(SvxLineEndToolBoxControl* pControl, weld::Wid
     }
     DBG_ASSERT( mpLineEndList.is(), "LineEndList not found" );
 
-    mxLineEndSet->SetSelectHdl( LINK( this, SvxLineEndWindow, SelectHdl ) );
-    mxLineEndSet->SetColCount( gnCols );
+    mxLineEndIV->connect_item_activated( LINK( this, SvxLineEndWindow, 
ItemActivatedHdl ) );
 
-    // ValueSet fill with entries of LineEndList
-    FillValueSet();
+    // Avoid LibreOffice Kit crash: tooltip handlers cause segfault during 
JSDialog
+    // serialization when popup widgets are destroyed/recreated during 
character formatting resets.
+    // Tooltip event binding is not needed for LibreOffice Kit
+    if (!comphelper::LibreOfficeKit::isActive())
+    {
+        mxLineEndIV->connect_query_tooltip(LINK(this, SvxLineEndWindow, 
QueryTooltipHdl));
+    }
+
+    // IconView fill with entries of LineEndList
+    FillIconView();
 
     AddStatusListener( u".uno:LineEndListState"_ustr);
 }
 
-IMPL_LINK_NOARG(SvxLineEndWindow, SelectHdl, ValueSet*, void)
+IMPL_LINK_NOARG(SvxLineEndWindow, ItemActivatedHdl, weld::IconView&, bool)
 {
     std::unique_ptr<XLineEndItem> pLineEndItem;
     std::unique_ptr<XLineStartItem> pLineStartItem;
-    sal_uInt16 nId = mxLineEndSet->GetSelectedItemId();
+
+    OUString sId = mxLineEndIV->get_selected_id();
+    if (sId.isEmpty())
+        return false;
+
+    sal_uInt32 nId = sId.toUInt32();
 
     if( nId == 1 )
     {
@@ -342,14 +347,54 @@ IMPL_LINK_NOARG(SvxLineEndWindow, SelectHdl, ValueSet*, 
void)
     /*  #i33380# DR 2004-09-03 Moved the following line above the Dispatch() 
call.
         This instance may be deleted in the meantime (i.e. when a dialog is 
opened
         while in Dispatch()), accessing members will crash in this case. */
-    mxLineEndSet->SetNoSelection();
+    mxLineEndIV->unselect_all();
 
     mxControl->dispatchCommand(mxControl->getCommandURL(), aArgs);
 
     mxControl->EndPopupMode();
+
+    return true;
+}
+
+IMPL_LINK(SvxLineEndWindow, QueryTooltipHdl, const weld::TreeIter&, rIter, 
OUString)
+{
+    OUString sId = mxLineEndIV->get_id(rIter);
+    if (sId.isEmpty())
+        return OUString();
+
+    sal_uInt16 nId = sId.toUInt32();
+
+    if (nId == 1 || nId == 2)
+    {
+        return SvxResId(RID_SVXSTR_NONE);
+    }
+
+    if (!mpLineEndList.is())
+        return OUString();
+
+    tools::Long nEntryIndex;
+    if (nId % 2) // beginning of line
+    {
+        nEntryIndex = (nId - 1) / 2 - 1;
+    }
+    else // end of line
+    {
+        nEntryIndex = nId / 2 - 2;
+    }
+
+    if (nEntryIndex >= 0 && nEntryIndex < mpLineEndList->Count())
+    {
+        const XLineEndEntry* pEntry = mpLineEndList->GetLineEnd(nEntryIndex);
+        if (pEntry)
+        {
+            return pEntry->GetName();
+        }
+    }
+
+    return OUString();
 }
 
-void SvxLineEndWindow::FillValueSet()
+void SvxLineEndWindow::FillIconView()
 {
     if( !mpLineEndList.is() )
         return;
@@ -375,8 +420,20 @@ void SvxLineEndWindow::FillValueSet()
     Point aPt1( maBmpSize.Width(), 0 );
 
     pVD->DrawBitmap( Point(), aBmp );
-    mxLineEndSet->InsertItem(1, Image(pVD->GetBitmap(aPt0, maBmpSize)), 
pEntry->GetName());
-    mxLineEndSet->InsertItem(2, Image(pVD->GetBitmap(aPt1, maBmpSize)), 
pEntry->GetName());
+
+    // First half (left side)
+    ScopedVclPtrInstance< VirtualDevice > pVD1;
+    pVD1->SetOutputSizePixel( maBmpSize, false );
+    pVD1->DrawBitmap( Point(), pVD->GetBitmap(aPt0, maBmpSize) );
+    Bitmap aBmp1 = pVD1->GetBitmap(Point(), maBmpSize);
+    mxLineEndIV->append(u"1"_ustr, pEntry->GetName(), &aBmp1);
+
+    // Second half (right side)
+    ScopedVclPtrInstance< VirtualDevice > pVD2;
+    pVD2->SetOutputSizePixel( maBmpSize, false );
+    pVD2->DrawBitmap( Point(), pVD->GetBitmap(aPt1, maBmpSize) );
+    Bitmap aBmp2 = pVD2->GetBitmap(Point(), maBmpSize);
+    mxLineEndIV->append(u"2"_ustr, pEntry->GetName(), &aBmp2);
 
     mpLineEndList->Remove(nCount);
 
@@ -388,15 +445,21 @@ void SvxLineEndWindow::FillValueSet()
         OSL_ENSURE( !aBmp.IsEmpty(), "UI bitmap was not created" );
 
         pVD->DrawBitmap( aPt0, aBmp );
-        mxLineEndSet->InsertItem(static_cast<sal_uInt16>((i+1)*2L+1),
-                Image(pVD->GetBitmap(aPt0, maBmpSize)), pEntry->GetName());
-        mxLineEndSet->InsertItem(static_cast<sal_uInt16>((i+2)*2L),
-                Image(pVD->GetBitmap(aPt1, maBmpSize)), pEntry->GetName());
-    }
-    mnLines = std::min( static_cast<sal_uInt16>(nCount + 1), 
sal_uInt16(MAX_LINES) );
-    mxLineEndSet->SetLineCount( mnLines );
 
-    SetSize();
+        // Left half for line start
+        ScopedVclPtrInstance< VirtualDevice > pVDStart;
+        pVDStart->SetOutputSizePixel( maBmpSize, false );
+        pVDStart->DrawBitmap( Point(), pVD->GetBitmap(aPt0, maBmpSize) );
+        Bitmap aBmpStart = pVDStart->GetBitmap(Point(), maBmpSize);
+        mxLineEndIV->append(OUString::number((i+1)*2L+1), pEntry->GetName(), 
&aBmpStart);
+
+        // Right half for line end
+        ScopedVclPtrInstance< VirtualDevice > pVDEnd;
+        pVDEnd->SetOutputSizePixel( maBmpSize, false );
+        pVDEnd->DrawBitmap( Point(), pVD->GetBitmap(aPt1, maBmpSize) );
+        Bitmap aBmpEnd = pVDEnd->GetBitmap(Point(), maBmpSize);
+        mxLineEndIV->append(OUString::number((i+2)*2L), pEntry->GetName(), 
&aBmpEnd);
+    }
 }
 
 void SvxLineEndWindow::statusChanged( const css::frame::FeatureStateEvent& 
rEvent )
@@ -411,33 +474,11 @@ void SvxLineEndWindow::statusChanged( const 
css::frame::FeatureStateEvent& rEven
         mpLineEndList.set( static_cast< XLineEndList* >( xWeak.get() ) );
         DBG_ASSERT( mpLineEndList.is(), "LineEndList not found" );
 
-        mxLineEndSet->Clear();
-        FillValueSet();
+        mxLineEndIV->clear();
+        FillIconView();
     }
 }
 
-void SvxLineEndWindow::SetSize()
-{
-    sal_uInt16 nItemCount = mxLineEndSet->GetItemCount();
-    sal_uInt16 nMaxLines  = nItemCount / gnCols;
-
-    WinBits nBits = mxLineEndSet->GetStyle();
-    if ( mnLines == nMaxLines )
-        nBits &= ~WB_VSCROLL;
-    else
-        nBits |= WB_VSCROLL;
-    mxLineEndSet->SetStyle( nBits );
-
-    Size aSize( maBmpSize );
-    aSize.AdjustWidth(6 );
-    aSize.AdjustHeight(6 );
-    aSize = mxLineEndSet->CalcWindowSizePixel( aSize );
-    if (nBits & WB_VSCROLL)
-        aSize.AdjustWidth(mxLineEndSet->GetScrollWidth());
-    mxLineEndSet->GetDrawingArea()->set_size_request(aSize.Width(), 
aSize.Height());
-    mxLineEndSet->SetOutputSizePixel(aSize);
-}
-
 SvxLineEndToolBoxControl::SvxLineEndToolBoxControl( const 
css::uno::Reference<css::uno::XComponentContext>& rContext )
     : svt::PopupWindowController( rContext, nullptr, OUString() )
 {
diff --git a/svx/uiconfig/ui/floatinglineend.ui 
b/svx/uiconfig/ui/floatinglineend.ui
index 237b71c6d483..bc09457f55cc 100644
--- a/svx/uiconfig/ui/floatinglineend.ui
+++ b/svx/uiconfig/ui/floatinglineend.ui
@@ -2,6 +2,14 @@
 <!-- Generated with glade 3.22.1 -->
 <interface domain="svx">
   <requires lib="gtk+" version="3.24"/>
+  <object class="GtkTreeStore" id="liststore1">
+    <columns>
+      <!-- column-name pixbuf -->
+      <column type="GdkPixbuf"/>
+      <!-- column-name id -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
   <object class="GtkPopover" id="FloatingLineEnd">
     <property name="can_focus">False</property>
     <property name="no_show_all">True</property>
@@ -14,26 +22,30 @@
         <property name="orientation">vertical</property>
         <property name="spacing">6</property>
         <child>
-          <object class="GtkScrolledWindow" id="valuesetwin">
+          <object class="GtkScrolledWindow">
             <property name="visible">True</property>
             <property name="can_focus">True</property>
             <property name="hexpand">True</property>
             <property name="vexpand">True</property>
-            <property name="hscrollbar_policy">never</property>
+            <property name="hscrollbar-policy">never</property>
+            <property name="vscrollbar-policy">always</property>
             <property name="shadow_type">in</property>
+            <property name="height-request">264</property>
             <child>
-              <object class="GtkViewport">
+              <object class="GtkIconView" id="floating_line_end_iconview">
                 <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <child>
-                  <object class="GtkDrawingArea" id="valueset">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="events">GDK_BUTTON_PRESS_MASK | 
GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | 
GDK_STRUCTURE_MASK</property>
-                    <property name="hexpand">True</property>
-                    <property name="vexpand">True</property>
-                  </object>
-                </child>
+                <property name="can-focus">True</property>
+                <property name="hexpand">True</property>
+                <property name="vexpand">True</property>
+                <property name="model">liststore1</property>
+                <property name="pixbuf-column">0</property>
+                <property name="columns">2</property>
+                <property name="selection-mode">single</property>
+                <property name="activate-on-single-click">True</property>
+                <property name="item-padding">2</property>
+                <property name="row-spacing">2</property>
+                <property name="column-spacing">2</property>
+                <property name="margin">2</property>
               </object>
             </child>
           </object>

Reply via email to