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);