include/vcl/jsdialog/executor.hxx      |    5 
 include/vcl/weld.hxx                   |    2 
 sd/source/ui/sidebar/LayoutMenu.cxx    |  322 +++++++++++++++------------------
 sd/source/ui/sidebar/LayoutMenu.hxx    |   46 +++-
 sd/uiconfig/simpress/ui/layoutpanel.ui |   45 +++-
 vcl/jsdialog/enabled.cxx               |    3 
 vcl/jsdialog/executor.cxx              |   14 +
 vcl/jsdialog/jsdialogbuilder.cxx       |   12 -
 8 files changed, 258 insertions(+), 191 deletions(-)

New commits:
commit 39acd67994d33596a29ca68df451dba6d46085f4
Author:     Parth Raiyani <parth.raiy...@collabora.com>
AuthorDate: Wed Jun 11 17:30:21 2025 +0530
Commit:     Szymon Kłos <szymon.k...@collabora.com>
CommitDate: Thu Jun 12 07:47:48 2025 +0200

    Switch to IconView in LayoutMenu for improved UI handling
    
    - Replaces ValueSet with IconView widget in the LayoutMenu.
    - Cleans up redundant code related to ValueSet and updates the layout panel 
UI file to support IconView.
    - Introduces methods for handling mouse events and context menus in IconView
    
    Change-Id: I137462eaa94dec2f25b9cf1a96d24e4f43b1becc
    Signed-off-by: Parth Raiyani <parth.raiy...@collabora.com>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/185516
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Szymon Kłos <szymon.k...@collabora.com>

diff --git a/include/vcl/jsdialog/executor.hxx 
b/include/vcl/jsdialog/executor.hxx
index fb6b0d818e27..be24997d4409 100644
--- a/include/vcl/jsdialog/executor.hxx
+++ b/include/vcl/jsdialog/executor.hxx
@@ -91,6 +91,11 @@ public:
         rDrawingArea.mouse_move(rPos);
     }
 
+    static void trigger_mouse_press(weld::IconView& rIconView, const 
MouseEvent& rEvent)
+    {
+        rIconView.signal_mouse_press(rEvent);
+    }
+
     static void trigger_selected(weld::MenuButton& rButton, const OUString& 
rIdent)
     {
         rButton.signal_selected(rIdent);
diff --git a/include/vcl/weld.hxx b/include/vcl/weld.hxx
index 585238cd77b1..333ef8ea5557 100644
--- a/include/vcl/weld.hxx
+++ b/include/vcl/weld.hxx
@@ -270,6 +270,8 @@ public:
         m_aMousePressHdl = rLink;
     }
 
+    void signal_mouse_press(const MouseEvent& rEvent) { 
m_aMousePressHdl.Call(rEvent); }
+
     virtual void connect_mouse_move(const Link<const MouseEvent&, bool>& rLink)
     {
         assert(!m_aMouseMotionHdl.IsSet() || !rLink.IsSet());
diff --git a/sd/source/ui/sidebar/LayoutMenu.cxx 
b/sd/source/ui/sidebar/LayoutMenu.cxx
index 4f0545fbf83e..4ba9c4ff6918 100644
--- a/sd/source/ui/sidebar/LayoutMenu.cxx
+++ b/sd/source/ui/sidebar/LayoutMenu.cxx
@@ -53,6 +53,7 @@
 #include <vcl/commandevent.hxx>
 #include <vcl/image.hxx>
 #include <xmloff/autolayout.hxx>
+#include <comphelper/lok.hxx>
 
 #include <com/sun/star/drawing/framework/XControllerManager.hpp>
 #include <com/sun/star/drawing/framework/XView.hpp>
@@ -126,49 +127,22 @@ constexpr snew_slide_value_info standard[] =
     {BMP_LAYOUT_HEAD02A, STR_AL_TITLE_VERT_OUTLINE_CLIPART,   
WritingMode_TB_RL, AUTOLAYOUT_TITLE_2VTEXT},
 };
 
-class LayoutValueSet : public ValueSet
-{
-private:
-    LayoutMenu& mrMenu;
-
-    /** Calculate the number of displayed rows.  This depends on the given
-        item size, the given number of columns, and the size of the
-        control.  Note that this is not the number of rows managed by the
-        valueset.  This number may be larger.  In that case a vertical
-        scroll bar is displayed.
-    */
-    int CalculateRowCount(const Size& rItemSize, int nColumnCount);
-
-public:
-    LayoutValueSet(LayoutMenu& rMenu)
-        : ValueSet(nullptr)
-        , mrMenu(rMenu)
-    {
-    }
-
-    virtual void Resize() override;
-
-    virtual bool Command(const CommandEvent& rEvent) override;
-};
-
 LayoutMenu::LayoutMenu (
     weld::Widget* pParent,
     ViewShellBase& rViewShellBase,
     css::uno::Reference<css::ui::XSidebar> xSidebar)
     : PanelLayout( pParent, u"LayoutPanel"_ustr, 
u"modules/simpress/ui/layoutpanel.ui"_ustr ),
       mrBase(rViewShellBase),
-      mxLayoutValueSet(new LayoutValueSet(*this)),
-      mxLayoutValueSetWin(new weld::CustomWeld(*m_xBuilder, 
u"layoutvalueset"_ustr, *mxLayoutValueSet)),
+      mxLayoutIconView(m_xBuilder->weld_icon_view(u"layoutpanel_icons"_ustr)),
       mbIsMainViewChangePending(false),
       mxSidebar(std::move(xSidebar)),
-      mbIsDisposed(false)
+      mbIsDisposed(false),
+      maPreviewSize(0, 0),
+      bInContextMenuOperation(false)
 {
     implConstruct( *mrBase.GetDocument()->GetDocSh() );
     SAL_INFO("sd.ui", "created LayoutMenu at " << this);
 
-    mxLayoutValueSet->SetStyle(mxLayoutValueSet->GetStyle() | WB_ITEMBORDER | 
WB_FLATVALUESET | WB_TABSTOP);
-
-    
mxLayoutValueSet->SetColor(sfx2::sidebar::Theme::GetColor(sfx2::sidebar::Theme::Color_PanelBackground));
 }
 
 void LayoutMenu::implConstruct( DrawDocShell& rDocumentShell )
@@ -178,21 +152,16 @@ void LayoutMenu::implConstruct( DrawDocShell& 
rDocumentShell )
     // if this fires, then my assumption that the rDocumentShell parameter to 
our first ctor is superfluous ...
     (void) rDocumentShell;
 
-    mxLayoutValueSet->SetStyle (
-        ( mxLayoutValueSet->GetStyle()  & ~(WB_ITEMBORDER) )
-        | WB_TABSTOP
-        | WB_MENUSTYLEVALUESET
-        | WB_NO_DIRECTSELECT
-        );
-    mxLayoutValueSet->SetExtraSpacing(2);
-    mxLayoutValueSet->SetSelectHdl (LINK(this, LayoutMenu, ClickHandler));
+    mxLayoutIconView->connect_item_activated(LINK(this, LayoutMenu, 
LayoutSelected));
+    mxLayoutIconView->connect_mouse_press(LINK(this, LayoutMenu, 
MousePressHdl));
+    mxLayoutIconView->connect_query_tooltip(LINK(this, LayoutMenu, 
QueryTooltipHdl));
     InvalidateContent();
 
     Link<::sd::tools::EventMultiplexerEvent&,void> aEventListenerLink 
(LINK(this,LayoutMenu,EventMultiplexerListener));
     mrBase.GetEventMultiplexer()->AddEventListener(aEventListenerLink);
 
-    mxLayoutValueSet->SetHelpId(HID_SD_TASK_PANE_PREVIEW_LAYOUTS);
-    
mxLayoutValueSet->SetAccessibleName(SdResId(STR_TASKPANEL_LAYOUT_MENU_TITLE));
+    mxLayoutIconView->set_help_id(HID_SD_TASK_PANE_PREVIEW_LAYOUTS);
+    
mxLayoutIconView->set_accessible_name(SdResId(STR_TASKPANEL_LAYOUT_MENU_TITLE));
 
     Link<const OUString&,void> aStateChangeLink 
(LINK(this,LayoutMenu,StateChangeHandler));
     mxListener = new ::sd::tools::SlotStateListener(
@@ -205,8 +174,7 @@ LayoutMenu::~LayoutMenu()
 {
     SAL_INFO("sd.ui", "destroying LayoutMenu at " << this);
     Dispose();
-    mxLayoutValueSetWin.reset();
-    mxLayoutValueSet.reset();
+    mxLayoutIconView.reset();
 }
 
 void LayoutMenu::Dispose()
@@ -228,86 +196,80 @@ void LayoutMenu::Dispose()
 
 AutoLayout LayoutMenu::GetSelectedAutoLayout() const
 {
-    AutoLayout aResult = AUTOLAYOUT_NONE;
+    OUString sId = mxLayoutIconView->get_selected_id();
 
-    if (!mxLayoutValueSet->IsNoSelection() && 
mxLayoutValueSet->GetSelectedItemId()!=0)
-    {
-        AutoLayout* pLayout = 
static_cast<AutoLayout*>(mxLayoutValueSet->GetItemData(mxLayoutValueSet->GetSelectedItemId()));
-        if (pLayout != nullptr)
-            aResult = *pLayout;
-    }
+    if (!sId.isEmpty())
+        return static_cast<AutoLayout>(sId.toInt32());
 
-    return aResult;
+    return AUTOLAYOUT_NONE; // default layout
 }
 
 ui::LayoutSize LayoutMenu::GetHeightForWidth (const sal_Int32 nWidth)
 {
-    sal_Int32 nPreferredHeight = 200;
-    if (mxLayoutValueSet->GetItemCount()>0)
+    // there is no way to get margin of item programmatically, we use value 
provided in ui file.
+    const int nMargin = 6;
+    Size aPreviewSize = maPreviewSize;
+    if (aPreviewSize.Width() == 0 && mxLayoutIconView->n_children() > 0)
     {
-        Image aImage = 
mxLayoutValueSet->GetItemImage(mxLayoutValueSet->GetItemId(0));
-        Size aItemSize = 
mxLayoutValueSet->CalcItemSizePixel(aImage.GetSizePixel());
-        if (nWidth>0 && aItemSize.Width()>0)
-        {
-            aItemSize.AdjustWidth(8 );
-            aItemSize.AdjustHeight(8 );
-            int nColumnCount = nWidth / aItemSize.Width();
-            if (nColumnCount <= 0)
-                nColumnCount = 1;
-            else if (nColumnCount > 4)
-                nColumnCount = 4;
-            int nRowCount = (mxLayoutValueSet->GetItemCount() + 
nColumnCount-1) / nColumnCount;
-            nPreferredHeight = nRowCount * aItemSize.Height();
-        }
+        aPreviewSize.setWidth(mxLayoutIconView->get_item_width());
+        
aPreviewSize.setHeight(mxLayoutIconView->get_preferred_size().getHeight());
     }
-    return ui::LayoutSize(nPreferredHeight,nPreferredHeight,nPreferredHeight);
+
+    sal_Int32 nColumnCount = nWidth / (aPreviewSize.Width() + (2 * nMargin));
+    if (nColumnCount < 1)
+        nColumnCount = 1;
+
+    sal_Int32 nTotalItems = mxLayoutIconView->n_children();
+    sal_Int32 nRowCount = (nTotalItems + nColumnCount - 1) / nColumnCount;
+    if (nRowCount < 1)
+        nRowCount = 1;
+
+    sal_Int32 nPreferredHeight = nRowCount * (aPreviewSize.Height() + (2 * 
nMargin));
+    return css::ui::LayoutSize(nPreferredHeight, nPreferredHeight, 
nPreferredHeight);
 }
 
-void LayoutValueSet::Resize()
+IMPL_LINK(LayoutMenu, MousePressHdl, const MouseEvent&, rMEvet, bool)
 {
-    Size aWindowSize = GetOutputSizePixel();
-    if (IsVisible() && aWindowSize.Width() > 0)
+    if (!rMEvet.IsRight())
+        return false;
+
+    const Point& pPos = rMEvet.GetPosPixel();
+    for (int i = 0; i < mxLayoutIconView->n_children(); i++)
     {
-        // Calculate the number of rows and columns.
-        if (GetItemCount() > 0)
+        const ::tools::Rectangle aRect = mxLayoutIconView->get_rect(i);
+        if (aRect.Contains(pPos))
         {
-            Image aImage = GetItemImage(GetItemId(0));
-            Size aItemSize = CalcItemSizePixel (
-                aImage.GetSizePixel());
-            aItemSize.AdjustWidth(8 );
-            aItemSize.AdjustHeight(8 );
-            int nColumnCount = aWindowSize.Width() / aItemSize.Width();
-            if (nColumnCount < 1)
-                nColumnCount = 1;
-            else if (nColumnCount > 4)
-                nColumnCount = 4;
-
-            int nRowCount = CalculateRowCount (aItemSize, nColumnCount);
-
-            SetColCount(nColumnCount);
-            SetLineCount(nRowCount);
+            bInContextMenuOperation = true;
+            mxLayoutIconView->select(i);
+            ShowContextMenu(pPos);
+            bInContextMenuOperation = false;
+            break;
         }
     }
-
-    ValueSet::Resize();
+    return false;
 }
 
-bool LayoutValueSet::Command(const CommandEvent& rEvent)
+IMPL_LINK(LayoutMenu, QueryTooltipHdl, const weld::TreeIter&, iter, OUString)
 {
-    if (rEvent.GetCommand() != CommandEventId::ContextMenu)
-        return false;
+    const OUString sId = mxLayoutIconView->get_id(iter);
 
-    // As a preparation for the context menu the item under the mouse is
-    // selected.
-    if (rEvent.IsMouseEvent())
+    if (!sId.isEmpty())
     {
-        sal_uInt16 nIndex = GetItemId(rEvent.GetMousePosPixel());
-        if (nIndex > 0)
-            SelectItem(nIndex);
+        AutoLayout aLayout = static_cast<AutoLayout>(sId.toInt32());
+        auto aResId = GetStringResourceIdForLayout(aLayout);
+        return aResId ? SdResId(aResId) : OUString();
     }
 
-    mrMenu.ShowContextMenu(rEvent.IsMouseEvent() ? &rEvent.GetMousePosPixel() 
: nullptr);
-    return true;
+    return OUString();
+}
+
+TranslateId LayoutMenu::GetStringResourceIdForLayout(AutoLayout aLayout) const
+{
+    auto it = maLayoutToStringMap.find(aLayout);
+    if (it != maLayoutToStringMap.end())
+        return it->second;
+
+    return TranslateId();
 }
 
 void LayoutMenu::InsertPageWithLayout (AutoLayout aLayout)
@@ -347,23 +309,11 @@ void LayoutMenu::InvalidateContent()
     UpdateSelection();
 }
 
-int LayoutValueSet::CalculateRowCount (const Size&, int nColumnCount)
-{
-    int nRowCount = 0;
 
-    if (GetItemCount() > 0 && nColumnCount > 0)
-    {
-        nRowCount = (GetItemCount() + nColumnCount - 1) / nColumnCount;
-        if (nRowCount < 1)
-            nRowCount = 1;
-    }
-
-    return nRowCount;
-}
-
-IMPL_LINK_NOARG(LayoutMenu, ClickHandler, ValueSet*, void)
+IMPL_LINK_NOARG(LayoutMenu, LayoutSelected, weld::IconView&, bool)
 {
     AssignLayoutToSelectedSlides( GetSelectedAutoLayout() );
+    return true;
 }
 
 /** The specified layout is assigned to the current page of the view shell
@@ -488,6 +438,20 @@ SfxRequest LayoutMenu::CreateRequest (
     return aRequest;
 }
 
+VclPtr<VirtualDevice> LayoutMenu::GetVirtualDevice(Image pImage)
+{
+    BitmapEx aPreviewBitmap = pImage.GetBitmapEx();
+    VclPtr<VirtualDevice> pVDev = VclPtr<VirtualDevice>::Create();
+    const Point aNull(0, 0);
+    if (pVDev->GetDPIScaleFactor() > 1)
+        aPreviewBitmap.Scale(pVDev->GetDPIScaleFactor(), 
pVDev->GetDPIScaleFactor());
+    const Size aSize(aPreviewBitmap.GetSizePixel());
+    pVDev->SetOutputSizePixel(aSize);
+    pVDev->DrawBitmapEx(aNull, aPreviewBitmap);
+
+    return pVDev;
+}
+
 void LayoutMenu::Fill()
 {
     bool bVertical = SvtCJKOptions::IsVerticalTextEnabled();
@@ -528,12 +492,14 @@ void LayoutMenu::Fill()
     }
 
     Clear();
-    sal_uInt16 id = 1;
+    sal_uInt16 id = 0;
+
+    mxLayoutIconView->freeze();
     for (const auto& elem : pInfo)
     {
         if ((WritingMode_TB_RL != elem.meWritingMode) || bVertical)
         {
-            Image aImg(OUString::Concat("private:graphicrepository/") + 
elem.msBmpResId);
+            Image aImg(StockImage::Yes, elem.msBmpResId);
 
             if (bRightToLeft && (WritingMode_TB_RL != elem.meWritingMode))
             { // FIXME: avoid interpolating RTL layouts.
@@ -542,48 +508,54 @@ void LayoutMenu::Fill()
                 aImg = Image(aRTL);
             }
 
-            mxLayoutValueSet->InsertItem(id, aImg, SdResId(elem.mpStrResId));
-            mxLayoutValueSet->SetItemData(id, new 
AutoLayout(elem.maAutoLayout));
+            if (aImg.GetSizePixel().Width() > 0)
+            {
+                OUString sId = 
OUString::number(static_cast<int>(elem.maAutoLayout));
+                VclPtr<VirtualDevice> aVDev = GetVirtualDevice(aImg);
+                OUString sLayoutName = SdResId(elem.mpStrResId);
+                if (!mxLayoutIconView->get_id(id).isEmpty())
+                {
+                    mxLayoutIconView->set_image(id, aVDev);
+                    mxLayoutIconView->set_id(id, sId);
+                    mxLayoutIconView->set_text(id, sLayoutName);
+                }
+                else
+                {
+                    mxLayoutIconView->insert(id, &sLayoutName, &sId, aVDev, 
nullptr);
+                }
+                maLayoutToStringMap[elem.maAutoLayout] = elem.mpStrResId;
+
+                if (maPreviewSize.Width() == 0)
+                    maPreviewSize = aImg.GetSizePixel();
+            }
             ++id;
         }
     }
+
+    mxLayoutIconView->thaw();
 }
 
 void LayoutMenu::Clear()
 {
-    for (size_t nId=1; nId<=mxLayoutValueSet->GetItemCount(); nId++)
-        delete static_cast<AutoLayout*>(mxLayoutValueSet->GetItemData(nId));
-    mxLayoutValueSet->Clear();
+    mxLayoutIconView->clear();
+    maLayoutToStringMap.clear();
+}
+
+IMPL_LINK(LayoutMenu, OnPopupEnd, const OUString&, sCommand, void)
+{
+    MenuSelect(sCommand);
 }
 
-void LayoutMenu::ShowContextMenu(const Point* pPos)
+void LayoutMenu::ShowContextMenu(const Point& pPos)
 {
     if (SdModule::get()->GetWaterCan())
         return;
 
-    // Determine the position where to show the menu.
-    Point aMenuPosition;
-    if (pPos)
-    {
-        auto nItemId = mxLayoutValueSet->GetItemId(*pPos);
-        if (nItemId <= 0)
-            return;
-        mxLayoutValueSet->SelectItem(nItemId);
-        aMenuPosition = *pPos;
-    }
-    else
-    {
-        if (mxLayoutValueSet->GetSelectedItemId() == sal_uInt16(-1))
-            return;
-        ::tools::Rectangle 
aBBox(mxLayoutValueSet->GetItemRect(mxLayoutValueSet->GetSelectedItemId()));
-        aMenuPosition = aBBox.Center();
-    }
-
     // Setup the menu.
-    ::tools::Rectangle aRect(aMenuPosition, Size(1, 1));
-    weld::Widget* pPopupParent = mxLayoutValueSet->GetDrawingArea();
-    std::unique_ptr<weld::Builder> 
xBuilder(Application::CreateBuilder(pPopupParent, 
u"modules/simpress/ui/layoutmenu.ui"_ustr));
-    std::unique_ptr<weld::Menu> xMenu(xBuilder->weld_menu(u"menu"_ustr));
+    ::tools::Rectangle aRect(pPos, Size(1, 1));
+    mxMenu.reset();
+    mxMenuBuilder = Application::CreateBuilder(mxLayoutIconView.get(), 
u"modules/simpress/ui/layoutmenu.ui"_ustr);
+    mxMenu = mxMenuBuilder->weld_menu(u"menu"_ustr);
 
     // Disable the SID_INSERTPAGE_LAYOUT_MENU item when
     // the document is read-only.
@@ -591,32 +563,50 @@ void LayoutMenu::ShowContextMenu(const Point* pPos)
     const SfxItemState aState (
         mrBase.GetViewFrame().GetDispatcher()->QueryState(SID_INSERTPAGE, 
aResult));
     if (aState == SfxItemState::DISABLED)
-        xMenu->set_sensitive(u"insert"_ustr, false);
+        mxMenu->set_sensitive(u"insert"_ustr, false);
+
+    mxMenu->connect_activate(LINK(this, LayoutMenu, OnPopupEnd));
+    mxMenu->popup_at_rect(mxLayoutIconView.get(), aRect);
+}
 
-    // Show the menu.
-    OnMenuItemSelected(xMenu->popup_at_rect(pPopupParent, aRect));
+void LayoutMenu::MenuSelect(const OUString& rIdent)
+{
+    sLastItemIdent = rIdent;
+    if (sLastItemIdent.isEmpty())
+        return;
+
+    if (comphelper::LibreOfficeKit::isActive())
+        HandleMenuSelect(sLastItemIdent);
+    else
+        Application::PostUserEvent(LINK(this, LayoutMenu, MenuSelectAsyncHdl));
 }
 
 IMPL_LINK_NOARG(LayoutMenu, StateChangeHandler, const OUString&, void)
 {
+    if (bInContextMenuOperation)
+        return;
     InvalidateContent();
 }
 
-void LayoutMenu::OnMenuItemSelected(std::u16string_view ident)
+IMPL_LINK_NOARG(LayoutMenu, MenuSelectAsyncHdl, void*, void)
 {
-    if (ident.empty())
-        return;
+    HandleMenuSelect(sLastItemIdent);
+}
 
-    if (ident == u"apply")
+void LayoutMenu::HandleMenuSelect(std::u16string_view rIdent)
+{
+    if (rIdent == u"apply")
     {
         AssignLayoutToSelectedSlides(GetSelectedAutoLayout());
     }
-    else if (ident == u"insert")
+    else if (rIdent == u"insert")
     {
         // Add arguments to this slot and forward it to the main view
         // shell.
         InsertPageWithLayout(GetSelectedAutoLayout());
     }
+    mxMenu.reset();
+    mxMenuBuilder.reset();
 }
 
 // Selects an appropriate layout of the slide inside control.
@@ -645,19 +635,20 @@ void LayoutMenu::UpdateSelection()
             break;
 
         // Find the entry of the menu for to the layout.
-        const sal_uInt16 nItemCount = mxLayoutValueSet->GetItemCount();
-        for (sal_uInt16 nId=1; nId<=nItemCount; nId++)
+        const sal_uInt16 nItemCount = mxLayoutIconView->n_children();
+        for (sal_uInt16 nId=0; nId<nItemCount; nId++)
         {
-            if (*static_cast<AutoLayout*>(mxLayoutValueSet->GetItemData(nId)) 
== aLayout)
+            OUString sItemId = mxLayoutIconView->get_id(nId);
+            if (!sItemId.isEmpty() && 
static_cast<AutoLayout>(sItemId.toInt32()) == aLayout)
             {
-                // do not set selection twice to the same item
-                if (mxLayoutValueSet->GetSelectedItemId() != nId)
+                OUString sCurrentSelectedId = 
mxLayoutIconView->get_selected_id();
+                if (sCurrentSelectedId != sItemId)
                 {
-                    mxLayoutValueSet->SetNoSelection();
-                    mxLayoutValueSet->SelectItem(nId);
+                    mxLayoutIconView->unselect_all();
+                    mxLayoutIconView->select(nId);
                 }
 
-                bItemSelected = true; // no need to call SetNoSelection()
+                bItemSelected = true; // no need to call unselect_all()
                 break;
             }
         }
@@ -665,7 +656,7 @@ void LayoutMenu::UpdateSelection()
     while (false);
 
     if (!bItemSelected)
-        mxLayoutValueSet->SetNoSelection();
+        mxLayoutIconView->unselect_all();
 }
 
 IMPL_LINK(LayoutMenu, EventMultiplexerListener, 
::sd::tools::EventMultiplexerEvent&, rEvent, void)
@@ -680,6 +671,7 @@ IMPL_LINK(LayoutMenu, EventMultiplexerListener, 
::sd::tools::EventMultiplexerEve
         case EventMultiplexerEventId::ShapeRemoved:
         case EventMultiplexerEventId::CurrentPageChanged:
         case EventMultiplexerEventId::SlideSortedSelection:
+        case EventMultiplexerEventId::MainViewRemoved:
             UpdateSelection();
             break;
 
@@ -687,10 +679,6 @@ IMPL_LINK(LayoutMenu, EventMultiplexerListener, 
::sd::tools::EventMultiplexerEve
             mbIsMainViewChangePending = true;
             break;
 
-        case EventMultiplexerEventId::MainViewRemoved:
-            mxLayoutValueSet->Invalidate(); // redraw without focus
-            break;
-
         case EventMultiplexerEventId::ConfigurationUpdated:
             if (mbIsMainViewChangePending)
             {
@@ -708,8 +696,6 @@ void LayoutMenu::DataChanged(const DataChangedEvent& rEvent)
 {
     PanelLayout::DataChanged(rEvent);
     Fill();
-    mxLayoutValueSet->StyleUpdated();
-    
mxLayoutValueSet->SetColor(sfx2::sidebar::Theme::GetColor(sfx2::sidebar::Theme::Color_PanelBackground));
 }
 
 } // end of namespace ::sd::sidebar
diff --git a/sd/source/ui/sidebar/LayoutMenu.hxx 
b/sd/source/ui/sidebar/LayoutMenu.hxx
index 26dd966a485e..d17ee1f6d2b7 100644
--- a/sd/source/ui/sidebar/LayoutMenu.hxx
+++ b/sd/source/ui/sidebar/LayoutMenu.hxx
@@ -22,9 +22,12 @@
 #include <sfx2/sidebar/ILayoutableWindow.hxx>
 #include <sfx2/sidebar/PanelLayout.hxx>
 
-#include <svtools/valueset.hxx>
 #include <sfx2/request.hxx>
 #include <xmloff/autolayout.hxx>
+#include <vcl/image.hxx>
+#include <map>
+
+#include <unotools/resmgr.hxx>
 
 namespace com::sun::star::frame
 {
@@ -49,8 +52,6 @@ class SlotStateListener;
 
 namespace sd::sidebar
 {
-class LayoutValueSet;
-
 class LayoutMenu : public PanelLayout, public sfx2::sidebar::ILayoutableWindow
 {
 public:
@@ -84,7 +85,7 @@ public:
 
     /** The context menu is requested over this ShowContextMenu() method.
     */
-    void ShowContextMenu(const Point* pPos);
+    void ShowContextMenu(const Point& pPos);
 
     /** Call Fill() when switching to or from high contrast mode so that the
         correct set of icons is displayed.
@@ -94,9 +95,7 @@ public:
 private:
     ViewShellBase& mrBase;
 
-    std::unique_ptr<LayoutValueSet> mxLayoutValueSet;
-    std::unique_ptr<weld::CustomWeld> mxLayoutValueSetWin;
-
+    std::unique_ptr<weld::IconView> mxLayoutIconView;
     /** If we are asked for the preferred window size, then use this
         many columns for the calculation.
     */
@@ -104,8 +103,25 @@ private:
     bool mbIsMainViewChangePending;
     css::uno::Reference<css::ui::XSidebar> mxSidebar;
     bool mbIsDisposed;
+    std::map<AutoLayout, TranslateId> maLayoutToStringMap;
+
+    std::unique_ptr<weld::Builder> mxMenuBuilder;
+    std::unique_ptr<weld::Menu> mxMenu;
+
+    // Store the size of preview image
+    Size maPreviewSize;
 
-    /** Fill the value set with the layouts that are applicable to the
+    /**
+     * Prevents StateChangeHandler from rebuilding layouts during context menu 
operations.
+     * Without this flag, the first context menu operation would trigger a 
layout rebuild that
+     * disrupts the selection state, causing the selected item to not appear 
highlighted.
+     * Subsequent operations work correctly.
+     */
+    bool bInContextMenuOperation;
+
+    OUString sLastItemIdent;
+
+    /** Fill the icon view with the layouts that are applicable to the
         current main view shell.
     */
     void Fill();
@@ -145,12 +161,22 @@ private:
     // internal ctor
     void implConstruct(DrawDocShell& rDocumentShell);
 
+    void MenuSelect(const OUString& rIdent);
+
     /** When clicked then set the current page of the view in the center pane.
     */
-    DECL_LINK(ClickHandler, ValueSet*, void);
+    DECL_LINK(LayoutSelected, weld::IconView&, bool);
+    DECL_LINK(MousePressHdl, const MouseEvent&, bool);
+    DECL_LINK(QueryTooltipHdl, const weld::TreeIter&, OUString);
     DECL_LINK(StateChangeHandler, const OUString&, void);
     DECL_LINK(EventMultiplexerListener, ::sd::tools::EventMultiplexerEvent&, 
void);
-    void OnMenuItemSelected(std::u16string_view ident);
+    DECL_LINK(MenuSelectAsyncHdl, void*, void);
+    DECL_LINK(OnPopupEnd, const OUString&, void);
+
+    static VclPtr<VirtualDevice> GetVirtualDevice(Image pPreview);
+    void HandleMenuSelect(std::u16string_view rIdent);
+
+    TranslateId GetStringResourceIdForLayout(AutoLayout aLayout) const;
 };
 
 } // end of namespace ::sd::toolpanel
diff --git a/sd/uiconfig/simpress/ui/layoutpanel.ui 
b/sd/uiconfig/simpress/ui/layoutpanel.ui
index 5eb7122daea6..228dd6be49c2 100644
--- a/sd/uiconfig/simpress/ui/layoutpanel.ui
+++ b/sd/uiconfig/simpress/ui/layoutpanel.ui
@@ -3,22 +3,51 @@
 <interface domain="sd">
   <requires lib="gtk+" version="3.20"/>
   <!-- n-columns=1 n-rows=1 -->
-  <object class="GtkGrid" id="LayoutPanel">
+  <object class="GtkTreeStore" id="liststore1">
+    <columns>
+      <!-- column-name pixbuf -->
+      <column type="GdkPixbuf"/>
+      <!-- column-name id -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkBox" id="LayoutPanel">
     <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="hexpand">True</property>
+    <property name="can-focus">False</property>
     <property name="vexpand">True</property>
+    <property name="orientation">vertical</property>
     <child>
-      <object class="GtkDrawingArea" id="layoutvalueset">
+      <object class="GtkScrolledWindow" id="layoutpanel_scrolled_window">
         <property name="visible">True</property>
-        <property name="can_focus">False</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="can-focus">True</property>
         <property name="hexpand">True</property>
         <property name="vexpand">True</property>
+        <property name="hscrollbar-policy">never</property>
+        <property name="shadow-type">in</property>
+        <child>
+          <object class="GtkIconView" id="layoutpanel_icons">
+            <property name="visible">True</property>
+            <property name="item-padding">2</property>
+            <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="item-width">55</property>
+            <property name="margin">6</property>
+            <property name="activate-on-single-click">True</property>
+            <child internal-child="accessible">
+              <object class="AtkObject" id="layoutpanel_icons-atkobject">
+                <property name="AtkObject::accessible-description" 
translatable="yes" context="layoutpanel|extended_tip|layoutpanel_icons">List of 
available layout templates.</property>
+              </object>
+            </child>
+          </object>
+        </child>
       </object>
       <packing>
-        <property name="left_attach">0</property>
-        <property name="top_attach">0</property>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">0</property>
       </packing>
     </child>
   </object>
diff --git a/vcl/jsdialog/enabled.cxx b/vcl/jsdialog/enabled.cxx
index b7ca603dd6e8..680009c202b4 100644
--- a/vcl/jsdialog/enabled.cxx
+++ b/vcl/jsdialog/enabled.cxx
@@ -381,7 +381,8 @@ constexpr auto PopupList
 
 constexpr auto MenuList
     = frozen::make_unordered_set<std::u16string_view>({
-        { u"sfx/ui/stylecontextmenu.ui" }
+        { u"sfx/ui/stylecontextmenu.ui" },
+        { u"modules/simpress/ui/layoutmenu.ui" }
     });
 
 // ========== SIDEBAR ==================================================== //
diff --git a/vcl/jsdialog/executor.cxx b/vcl/jsdialog/executor.cxx
index 4fd740fb8f56..67afbc73ec82 100644
--- a/vcl/jsdialog/executor.cxx
+++ b/vcl/jsdialog/executor.cxx
@@ -672,6 +672,20 @@ bool ExecuteAction(const OUString& nWindowId, const 
OUString& rWidget, const Str
                     LOKTrigger::trigger_changed(*pIconView);
                     LOKTrigger::trigger_item_activated(*pIconView);
 
+                    return true;
+                }
+                else if (sAction == "contextmenu")
+                {
+                    sal_Int32 nPos = o3tl::toInt32(rData.at(u"data"_ustr));
+
+                    tools::Rectangle aRect = pIconView->get_rect(nPos);
+                    Point aPoint = aRect.Center();
+                    assert(aPoint.getX() >= 0 && aPoint.getY() >= 0);
+
+                    MouseEvent aMouseEvent(aPoint, 1, 
MouseEventModifiers::NONE, MOUSE_RIGHT, 0);
+
+                    LOKTrigger::trigger_mouse_press(*pIconView, aMouseEvent);
+
                     return true;
                 }
             }
diff --git a/vcl/jsdialog/jsdialogbuilder.cxx b/vcl/jsdialog/jsdialogbuilder.cxx
index 20f59f2aa850..3e2981446836 100644
--- a/vcl/jsdialog/jsdialogbuilder.cxx
+++ b/vcl/jsdialog/jsdialogbuilder.cxx
@@ -2007,12 +2007,16 @@ OUString JSMenu::popup_at_rect(weld::Widget* pParent, 
const tools::Rectangle& rR
                                weld::Placement /*ePlace*/)
 {
     // Do not block with SalInstanceMenu::popup_at_rect(pParent, rRect, 
ePlace);
-
-    // we find position based on parent widget id and row text inside TreeView 
for context menu
     OUString sCancelId;
-    weld::TreeView* pTree = dynamic_cast<weld::TreeView*>(pParent);
-    if (pTree)
+    if (weld::IconView* pIconView = dynamic_cast<weld::IconView*>(pParent); 
pIconView)
+    {
+        sCancelId = pIconView->get_selected_text();
+        if (sCancelId.isEmpty())
+            SAL_WARN("vcl", "No entry detected in JSMenu::popup_at_rect");
+    }
+    else if (weld::TreeView* pTree = dynamic_cast<weld::TreeView*>(pParent); 
pTree)
     {
+        // we find position based on parent widget id and row text inside 
TreeView for context menu
         std::unique_ptr<weld::TreeIter> itEntry(pTree->make_iterator());
         if (pTree->get_dest_row_at_pos(rRect.Center(), itEntry.get(), false, 
false))
             sCancelId = pTree->get_text(*itEntry);

Reply via email to