sw/Library_sw.mk | 2 sw/UIConfig_swriter.mk | 1 sw/inc/formatcontentcontrol.hxx | 2 sw/inc/viscrs.hxx | 2 sw/source/core/crsr/contentcontrolbutton.cxx | 151 +++++++++++++++++++ sw/source/core/crsr/dropdowncontentcontrolbutton.cxx | 93 +++++++++++ sw/source/core/crsr/viscrs.cxx | 27 +++ sw/source/core/inc/contentcontrolbutton.hxx | 47 +++++ sw/source/core/inc/dropdowncontentcontrolbutton.hxx | 38 ++++ sw/uiconfig/swriter/ui/contentcontroldropdown.ui | 68 ++++++++ 10 files changed, 431 insertions(+)
New commits: commit f120944d0d5239b40c676e4e848332ee7177bcd9 Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Thu May 5 08:52:11 2022 +0200 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Mon May 9 08:27:16 2022 +0200 sw content controls, drop-down: show list items on click - add a generic SwContentControlButton, to be used for dropdowns, but meant to be useful for other types in the future (e.g. date picker) - add a contentcontroldropdown .ui file for the welded list items - add a SwDropDownContentControlButton, which contains the drop-down-specific logic - invoke content control button from HighlightContentControl(), so list items are shown on button click (cherry picked from commit 1c0dbc2c5c782c118ce44825d92f697a6265fc75) Change-Id: Ib3c6765020c5b3b1dd343a40a2b1862c73feaac8 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133946 Tested-by: Miklos Vajna <vmik...@collabora.com> Reviewed-by: Miklos Vajna <vmik...@collabora.com> diff --git a/sw/Library_sw.mk b/sw/Library_sw.mk index 28db1cc7a94b..4f80a864376a 100644 --- a/sw/Library_sw.mk +++ b/sw/Library_sw.mk @@ -149,6 +149,7 @@ $(eval $(call gb_Library_add_exception_objects,sw,\ sw/source/core/crsr/BlockCursor \ sw/source/core/crsr/bookmark \ sw/source/core/crsr/callnk \ + sw/source/core/crsr/contentcontrolbutton \ sw/source/core/crsr/crbm \ sw/source/core/crsr/crossrefbookmark \ sw/source/core/crsr/crsrsh \ @@ -156,6 +157,7 @@ $(eval $(call gb_Library_add_exception_objects,sw,\ sw/source/core/crsr/crstrvl1 \ sw/source/core/crsr/DateFormFieldButton \ sw/source/core/crsr/DropDownFormFieldButton \ + sw/source/core/crsr/dropdowncontentcontrolbutton \ sw/source/core/crsr/findattr \ sw/source/core/crsr/findcoll \ sw/source/core/crsr/findfmt \ diff --git a/sw/UIConfig_swriter.mk b/sw/UIConfig_swriter.mk index 8e0901cec231..f38c894695dc 100644 --- a/sw/UIConfig_swriter.mk +++ b/sw/UIConfig_swriter.mk @@ -121,6 +121,7 @@ $(eval $(call gb_UIConfig_add_uifiles,modules/swriter,\ sw/uiconfig/swriter/ui/combobox \ sw/uiconfig/swriter/ui/comboboxfragment \ sw/uiconfig/swriter/ui/conditionpage \ + sw/uiconfig/swriter/ui/contentcontroldropdown \ sw/uiconfig/swriter/ui/converttexttable \ sw/uiconfig/swriter/ui/createaddresslist \ sw/uiconfig/swriter/ui/createauthorentry \ diff --git a/sw/inc/formatcontentcontrol.hxx b/sw/inc/formatcontentcontrol.hxx index f646e28b16f5..f7299028436a 100644 --- a/sw/inc/formatcontentcontrol.hxx +++ b/sw/inc/formatcontentcontrol.hxx @@ -168,6 +168,8 @@ public: std::vector<SwContentControlListItem> GetListItems() const { return m_aListItems; } + bool HasListItems() const { return !m_aListItems.empty(); } + void SetListItems(const std::vector<SwContentControlListItem>& rListItems) { m_aListItems = rListItems; diff --git a/sw/inc/viscrs.hxx b/sw/inc/viscrs.hxx index 2eb2f787fbc7..c5d97fc023c1 100644 --- a/sw/inc/viscrs.hxx +++ b/sw/inc/viscrs.hxx @@ -30,6 +30,7 @@ namespace sdr::overlay { class OverlayObject; } class SwCursorShell; class SfxViewShell; +class SwContentControlButton; // From here classes/methods for non-text cursor. @@ -86,6 +87,7 @@ class SwSelPaintRects : public SwRects std::unique_ptr<sw::overlay::OverlayRangesOutline> m_pTextInputFieldOverlay; bool m_bShowContentControlOverlay; std::unique_ptr<sw::overlay::OverlayRangesOutline> m_pContentControlOverlay; + VclPtr<SwContentControlButton> m_pContentControlButton; void HighlightInputField(); void HighlightContentControl(); diff --git a/sw/source/core/crsr/contentcontrolbutton.cxx b/sw/source/core/crsr/contentcontrolbutton.cxx new file mode 100644 index 000000000000..c135e25bd188 --- /dev/null +++ b/sw/source/core/crsr/contentcontrolbutton.cxx @@ -0,0 +1,151 @@ +/* -*- 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/. + */ + +#include <contentcontrolbutton.hxx> + +#include <vcl/weldutils.hxx> +#include <vcl/event.hxx> + +#include <edtwin.hxx> + +SwContentControlButton::SwContentControlButton(SwEditWin* pEditWin, + const SwContentControl& rContentControl) + : Control(pEditWin, WB_DIALOGCONTROL) + , m_rContentControl(rContentControl) +{ + assert(GetParent()); + assert(dynamic_cast<SwEditWin*>(GetParent())); + + SetBackground(); + EnableChildTransparentMode(); + SetParentClipMode(ParentClipMode::NoClip); + SetPaintTransparent(true); +} + +SwContentControlButton::~SwContentControlButton() { disposeOnce(); } + +void SwContentControlButton::LaunchPopup() +{ + m_xPopup->connect_closed(LINK(this, SwContentControlButton, PopupModeEndHdl)); + + tools::Rectangle aRect(Point(0, 0), GetSizePixel()); + weld::Window* pParent = weld::GetPopupParent(*this, aRect); + m_xPopup->popup_at_rect(pParent, aRect); +} + +void SwContentControlButton::DestroyPopup() +{ + m_xPopup.reset(); + m_xPopupBuilder.reset(); +} + +void SwContentControlButton::dispose() +{ + DestroyPopup(); + Control::dispose(); +} + +void SwContentControlButton::CalcPosAndSize(const SwRect& rPortionPaintArea) +{ + assert(GetParent()); + + Point aBoxPos = GetParent()->LogicToPixel(rPortionPaintArea.Pos()); + Size aBoxSize = GetParent()->LogicToPixel(rPortionPaintArea.SSize()); + + // First calculate the size of the frame around the content control's last portion + int nPadding = aBoxSize.Height() / 4; + aBoxPos.AdjustX(-nPadding / 2); + aBoxPos.AdjustY(-1); + aBoxSize.AdjustWidth(nPadding); + aBoxSize.AdjustHeight(2); + + m_aFramePixel = tools::Rectangle(aBoxPos, aBoxSize); + + // Then extend the size with the button area + aBoxSize.AdjustWidth(GetParent()->LogicToPixel(rPortionPaintArea.SSize()).Height()); + + if (aBoxPos != GetPosPixel() || aBoxSize != GetSizePixel()) + { + SetPosSizePixel(aBoxPos, aBoxSize); + Invalidate(); + } +} + +void SwContentControlButton::MouseButtonDown(const MouseEvent&) +{ + LaunchPopup(); + Invalidate(); +} + +IMPL_LINK_NOARG(SwContentControlButton, PopupModeEndHdl, weld::Popover&, void) +{ + DestroyPopup(); + Show(false); + Invalidate(); +} + +void SwContentControlButton::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + SetMapMode(MapMode(MapUnit::MapPixel)); + + Color aLineColor = COL_BLACK; + Color aFillColor = aLineColor; + aFillColor.IncreaseLuminance(255 * (m_xPopup ? 0.5 : 0.75)); + + // Calc the frame around the content control's last portion + int nPadding = 1; + Point aPos(nPadding, nPadding); + Size aSize(m_aFramePixel.GetSize().Width() - nPadding, + m_aFramePixel.GetSize().Height() - nPadding); + const tools::Rectangle aFrameRect(tools::Rectangle(aPos, aSize)); + + // Draw the button next to the frame + Point aButtonPos(aFrameRect.TopLeft()); + aButtonPos.AdjustX(aFrameRect.GetSize().getWidth() - 1); + Size aButtonSize(aFrameRect.GetSize()); + aButtonSize.setWidth(GetSizePixel().getWidth() - aFrameRect.getWidth() - nPadding); + const tools::Rectangle aButtonRect(tools::Rectangle(aButtonPos, aButtonSize)); + + // Background & border + rRenderContext.SetLineColor(aLineColor); + rRenderContext.SetFillColor(aFillColor); + rRenderContext.DrawRect(aButtonRect); + + // the arrowhead + rRenderContext.SetLineColor(aLineColor); + rRenderContext.SetFillColor(aLineColor); + + Point aCenter(aButtonPos.X() + (aButtonSize.Width() / 2), + aButtonPos.Y() + (aButtonSize.Height() / 2)); + tools::Long nMinWidth = 4; + tools::Long nMinHeight = 2; + Size aArrowSize(std::max(aButtonSize.Width() / 4, nMinWidth), + std::max(aButtonSize.Height() / 10, nMinHeight)); + + tools::Polygon aPoly(3); + aPoly.SetPoint(Point(aCenter.X() - aArrowSize.Width(), aCenter.Y() - aArrowSize.Height()), 0); + aPoly.SetPoint(Point(aCenter.X() + aArrowSize.Width(), aCenter.Y() - aArrowSize.Height()), 1); + aPoly.SetPoint(Point(aCenter.X(), aCenter.Y() + aArrowSize.Height()), 2); + rRenderContext.DrawPolygon(aPoly); +} + +WindowHitTest SwContentControlButton::ImplHitTest(const Point& rFramePos) +{ + // We need to check whether the position hits the button (the frame should be mouse transparent) + WindowHitTest aResult = Control::ImplHitTest(rFramePos); + if (aResult != WindowHitTest::Inside) + return aResult; + else + { + return rFramePos.X() >= m_aFramePixel.Right() ? WindowHitTest::Inside + : WindowHitTest::Transparent; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/dropdowncontentcontrolbutton.cxx b/sw/source/core/crsr/dropdowncontentcontrolbutton.cxx new file mode 100644 index 000000000000..75517cd972c4 --- /dev/null +++ b/sw/source/core/crsr/dropdowncontentcontrolbutton.cxx @@ -0,0 +1,93 @@ +/* -*- 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/. + */ + +#include <dropdowncontentcontrolbutton.hxx> + +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> + +#include <edtwin.hxx> +#include <view.hxx> +#include <docsh.hxx> +#include <strings.hrc> +#include <formatcontentcontrol.hxx> + +void SwDropDownContentControlButton::InitDropdown() +{ + std::vector<SwContentControlListItem> aListItems = m_rContentControl.GetListItems(); + + for (const auto& rListItem : aListItems) + m_xTreeView->append_text(rListItem.m_aDisplayText); + + if (aListItems.empty()) + { + m_xTreeView->append_text(SwResId(STR_DROP_DOWN_EMPTY_LIST)); + } + + int nHeight = m_xTreeView->get_height_rows( + std::min<int>(Application::GetSettings().GetStyleSettings().GetListBoxMaximumLineCount(), + m_xTreeView->n_children())); + m_xTreeView->set_size_request(-1, nHeight); + Size aSize(m_xTreeView->get_preferred_size()); + aSize.AdjustWidth(4); + aSize.AdjustHeight(4); + tools::Long nMinListWidth = GetSizePixel().Width(); + aSize.setWidth(std::max(aSize.Width(), nMinListWidth)); + m_xTreeView->set_size_request(aSize.Width(), aSize.Height()); +} + +IMPL_LINK(SwDropDownContentControlButton, ListBoxHandler, weld::TreeView&, rBox, bool) +{ + OUString sSelection = rBox.get_selected_text(); + if (sSelection == SwResId(STR_DROP_DOWN_EMPTY_LIST)) + { + m_xPopup->popdown(); + return true; + } + + sal_Int32 nSelection = rBox.get_selected_index(); + if (nSelection >= 0) + { + // TODO update the doc model + SwView& rView = static_cast<SwEditWin*>(GetParent())->GetView(); + rView.GetDocShell()->SetModified(); + } + + m_xPopup->popdown(); + + return true; +} + +SwDropDownContentControlButton::SwDropDownContentControlButton( + SwEditWin* pEditWin, const SwContentControl& rContentControl) + : SwContentControlButton(pEditWin, rContentControl) +{ +} + +SwDropDownContentControlButton::~SwDropDownContentControlButton() { disposeOnce(); } + +void SwDropDownContentControlButton::LaunchPopup() +{ + m_xPopupBuilder = Application::CreateBuilder(GetFrameWeld(), + "modules/swriter/ui/contentcontroldropdown.ui"); + m_xPopup = m_xPopupBuilder->weld_popover("ContentControlDropDown"); + m_xTreeView = m_xPopupBuilder->weld_tree_view("list"); + InitDropdown(); + m_xTreeView->connect_row_activated(LINK(this, SwDropDownContentControlButton, ListBoxHandler)); + SwContentControlButton::LaunchPopup(); + m_xTreeView->grab_focus(); +} + +void SwDropDownContentControlButton::DestroyPopup() +{ + m_xTreeView.reset(); + SwContentControlButton::DestroyPopup(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/crsr/viscrs.cxx b/sw/source/core/crsr/viscrs.cxx index ef504ad5a6fb..7cfffcd5ebae 100644 --- a/sw/source/core/crsr/viscrs.cxx +++ b/sw/source/core/crsr/viscrs.cxx @@ -61,6 +61,7 @@ #include <cellfrm.hxx> #include <wrtsh.hxx> #include <textcontentcontrol.hxx> +#include <dropdowncontentcontrolbutton.hxx> // Here static members are defined. They will get changed on alteration of the // MapMode. This is done so that on ShowCursor the same size does not have to be @@ -360,6 +361,7 @@ SwSelPaintRects::SwSelPaintRects( const SwCursorShell& rCSh ) SwSelPaintRects::~SwSelPaintRects() { Hide(); + m_pContentControlButton.disposeAndClear(); } void SwSelPaintRects::swapContent(SwSelPaintRects& rSwap) @@ -636,6 +638,8 @@ void SwSelPaintRects::HighlightContentControl() { std::vector<basegfx::B2DRange> aContentControlRanges; std::vector<OString> aLOKRectangles; + SwRect aLastPortionPaintArea; + const SwContentControl* pContentControl = nullptr; if (m_bShowContentControlOverlay) { @@ -675,6 +679,12 @@ void SwSelPaintRects::HighlightContentControl() aLOKRectangles.push_back(aRect.toString()); } } + + if (!pRects->empty()) + { + aLastPortionPaintArea = (*pRects)[pRects->size() - 1]; + } + pContentControl = pCurContentControlAtCursor->GetContentControl().GetContentControl(); } } @@ -711,6 +721,18 @@ void SwSelPaintRects::HighlightContentControl() xTargetOverlay->add(*m_pContentControlOverlay); } } + + if (!m_pContentControlButton && pContentControl && pContentControl->HasListItems()) + { + auto pWrtShell = dynamic_cast<const SwWrtShell*>(GetShell()); + if (pWrtShell) + { + auto& rEditWin = const_cast<SwEditWin&>(pWrtShell->GetView().GetEditWin()); + m_pContentControlButton = VclPtr<SwDropDownContentControlButton>::Create(&rEditWin, *pContentControl); + m_pContentControlButton->CalcPosAndSize(aLastPortionPaintArea); + m_pContentControlButton->Show(); + } + } } else { @@ -722,6 +744,11 @@ void SwSelPaintRects::HighlightContentControl() GetShell()->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_CONTENT_CONTROL, pJson.get()); } m_pContentControlOverlay.reset(); + + if (m_pContentControlButton) + { + m_pContentControlButton.disposeAndClear(); + } } } diff --git a/sw/source/core/inc/contentcontrolbutton.hxx b/sw/source/core/inc/contentcontrolbutton.hxx new file mode 100644 index 000000000000..591b8ce6114c --- /dev/null +++ b/sw/source/core/inc/contentcontrolbutton.hxx @@ -0,0 +1,47 @@ +/* -*- 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 + +#include <vcl/ctrl.hxx> +#include <vcl/weld.hxx> +#include <swrect.hxx> + +class SwEditWin; +class SwContentControl; + +/// This button is shown when the cursor is inside a content control with drop-down capability. +class SwContentControlButton : public Control +{ +public: + SwContentControlButton(SwEditWin* pEditWin, const SwContentControl& rContentControl); + virtual ~SwContentControlButton() override; + virtual void dispose() override; + + void CalcPosAndSize(const SwRect& rPortionPaintArea); + + virtual void MouseButtonDown(const MouseEvent& rMEvt) override; + DECL_LINK(PopupModeEndHdl, weld::Popover&, void); + + virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override; + virtual WindowHitTest ImplHitTest(const Point& rFramePos) override; + + virtual void LaunchPopup(); + virtual void DestroyPopup(); + +private: + tools::Rectangle m_aFramePixel; + +protected: + const SwContentControl& m_rContentControl; + std::unique_ptr<weld::Builder> m_xPopupBuilder; + std::unique_ptr<weld::Popover> m_xPopup; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/dropdowncontentcontrolbutton.hxx b/sw/source/core/inc/dropdowncontentcontrolbutton.hxx new file mode 100644 index 000000000000..5be95ae5adf6 --- /dev/null +++ b/sw/source/core/inc/dropdowncontentcontrolbutton.hxx @@ -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 + +#include "contentcontrolbutton.hxx" + +class SwEditWin; +class SwContentControl; + +/** + * This button is shown when the cursor is inside a drop-down content control. + * The user can select a list item using this button while filling in a form. + */ +class SwDropDownContentControlButton final : public SwContentControlButton +{ +private: + std::unique_ptr<weld::TreeView> m_xTreeView; + + DECL_LINK(ListBoxHandler, weld::TreeView&, bool); + + void InitDropdown(); + +public: + SwDropDownContentControlButton(SwEditWin* pEditWin, const SwContentControl& rContentControl); + virtual ~SwDropDownContentControlButton() override; + + virtual void LaunchPopup() override; + virtual void DestroyPopup() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/uiconfig/swriter/ui/contentcontroldropdown.ui b/sw/uiconfig/swriter/ui/contentcontroldropdown.ui new file mode 100644 index 000000000000..2f4b003746bb --- /dev/null +++ b/sw/uiconfig/swriter/ui/contentcontroldropdown.ui @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.38.2 --> +<interface domain="sw"> + <requires lib="gtk+" version="3.20"/> + <object class="GtkTreeStore" id="liststore1"> + <columns> + <!-- column-name text --> + <column type="gchararray"/> + <!-- column-name id --> + <column type="gchararray"/> + </columns> + </object> + <object class="GtkPopover" id="ContentControlDropDown"> + <property name="can-focus">False</property> + <property name="position">bottom</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <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> + <child> + <object class="GtkTreeView" id="list"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="vexpand">True</property> + <property name="model">liststore1</property> + <property name="headers-visible">False</property> + <property name="headers-clickable">False</property> + <property name="search-column">0</property> + <property name="hover-selection">True</property> + <property name="show-expanders">False</property> + <property name="activate-on-single-click">True</property> + <child internal-child="selection"> + <object class="GtkTreeSelection"/> + </child> + <child> + <object class="GtkTreeViewColumn" id="treeviewcolumn1"> + <child> + <object class="GtkCellRendererText" id="cellrenderertext1"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + </object> +</interface>