tdf#133160 sc: fix width of Autofilter popup window

Autofilter popup window will be automatically resized horizontally
to make sure its list items are fit into the window, if possible.

The window width is capped at 1024 pixel. If it is not enough,
horizontal scrollbar will appear for the TreeList.

Co-authored-by: Tibor Nagy (NISZ)

Change-Id: I49ce9ec2c715010d604357e4955eee86d276d713
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/100978
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/sc/qa/uitest/autofilter/autofilter.py b/sc/qa/uitest/autofilter/autofilter.py
index 57504e6..a0264f0 100644
--- a/sc/qa/uitest/autofilter/autofilter.py
+++ b/sc/qa/uitest/autofilter/autofilter.py
@@ -6,6 +6,7 @@
#

from uitest.framework import UITestCase
from uitest.uihelper.common import get_state_as_dict
from uitest.path import get_srcdir_url

from libreoffice.uno.propertyvalue import mkPropertyValues
@@ -70,4 +71,48 @@ class AutofilterTest(UITestCase):
        self.assertTrue(is_row_hidden(doc, 3))
        self.assertFalse(is_row_hidden(doc, 4))

    def test_tdf133160(self):
        doc = self.ui_test.load_file(get_url_for_data_file("tdf133160.ods"))

        xGridWin = self.xUITest.getTopFocusWindow().getChild("grid_window")
        xGridWin.executeAction("LAUNCH", mkPropertyValues({"AUTOFILTER": "", "COL": "1", "ROW": "3"}))
        xFloatWindow = self.xUITest.getFloatWindow()
        xCheckListMenu = xFloatWindow.getChild("check_list_menu")
        xTreeList = xCheckListMenu.getChild("check_list_box")
        size1 = int(get_state_as_dict(xTreeList)["Size"].split('x')[0])
        xOkBtn = xFloatWindow.getChild("cancel")
        xOkBtn.executeAction("CLICK", tuple())

        xGridWin.executeAction("LAUNCH", mkPropertyValues({"AUTOFILTER": "", "COL": "2", "ROW": "3"}))
        xFloatWindow = self.xUITest.getFloatWindow()
        xCheckListMenu = xFloatWindow.getChild("check_list_menu")
        xTreeList = xCheckListMenu.getChild("check_list_box")
        size2 = int(get_state_as_dict(xTreeList)["Size"].split('x')[0])
        xOkBtn = xFloatWindow.getChild("cancel")
        xOkBtn.executeAction("CLICK", tuple())

        xGridWin.executeAction("LAUNCH", mkPropertyValues({"AUTOFILTER": "", "COL": "3", "ROW": "3"}))
        xFloatWindow = self.xUITest.getFloatWindow()
        xCheckListMenu = xFloatWindow.getChild("check_list_menu")
        xTreeList = xCheckListMenu.getChild("check_list_box")
        size3 = int(get_state_as_dict(xTreeList)["Size"].split('x')[0])
        xOkBtn = xFloatWindow.getChild("cancel")
        xOkBtn.executeAction("CLICK", tuple())

        xGridWin.executeAction("LAUNCH", mkPropertyValues({"AUTOFILTER": "", "COL": "4", "ROW": "3"}))
        xFloatWindow = self.xUITest.getFloatWindow()
        xCheckListMenu = xFloatWindow.getChild("check_list_menu")
        xTreeList = xCheckListMenu.getChild("check_list_box")
        size4 = int(get_state_as_dict(xTreeList)["Size"].split('x')[0])

        xOkBtn = xFloatWindow.getChild("cancel")
        xOkBtn.executeAction("CLICK", tuple())

        self.assertTrue(size1 < size2) # for me they were size1=176 size2=212 size3=459 size4=1012
        self.assertTrue(size2 < size3) # size1 is the minimum window width,  size2 based on its column width
        self.assertTrue(size3 < size4) # size3 is a long text width
        self.assertTrue(size4 < 1500)  # size4 is the maximum window width with a really long text

        self.ui_test.close_doc()

# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/sc/qa/uitest/autofilter/data/tdf133160.ods b/sc/qa/uitest/autofilter/data/tdf133160.ods
new file mode 100644
index 0000000..40f83e6
--- /dev/null
+++ b/sc/qa/uitest/autofilter/data/tdf133160.ods
Binary files differ
diff --git a/sc/source/ui/cctrl/checklistmenu.cxx b/sc/source/ui/cctrl/checklistmenu.cxx
index 7422a1a..9a9259f 100644
--- a/sc/source/ui/cctrl/checklistmenu.cxx
+++ b/sc/source/ui/cctrl/checklistmenu.cxx
@@ -1159,14 +1159,17 @@ namespace
    }
}

size_t ScCheckListMenuControl::initMembers()
size_t ScCheckListMenuControl::initMembers(int nMaxMemberWidth)
{
    size_t n = maMembers.size();
    size_t nVisMemCount = 0;

    if (nMaxMemberWidth == -1)
        nMaxMemberWidth = mnCheckWidthReq;

    if (!mxChecks->n_children() && !mbHasDates)
    {
        std::vector<int> aFixedWidths { mnCheckWidthReq };
        std::vector<int> aFixedWidths { nMaxMemberWidth };
        // tdf#134038 insert in the fastest order, this might be backwards so only do it for
        // the !mbHasDates case where no entry depends on another to exist before getting
        // inserted. We cannot retain pre-existing treeview content, only clear and fill it.
@@ -1348,4 +1351,22 @@ IMPL_LINK_NOARG(ScCheckListMenuControl, PopupModeEndHdl, FloatingWindow*, void)
        mxPopupEndAction->execute();
}

int ScCheckListMenuControl::GetTextWidth(const OUString& rsName) const
{
    return mxDropDown->GetTextWidth(rsName);
}

int ScCheckListMenuControl::IncreaseWindowWidthToFitText(int nMaxTextWidth)
{
    int nBorder = mxFrame->get_border_width() * 2 + 4;
    int nNewWidth = nMaxTextWidth - nBorder;
    if (nNewWidth > mnCheckWidthReq)
    {
        mnCheckWidthReq = nNewWidth;
        int nChecksHeight = mxChecks->get_height_rows(9);
        mxChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
    }
    return mnCheckWidthReq + nBorder;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/ui/inc/checklistmenu.hxx b/sc/source/ui/inc/checklistmenu.hxx
index 6a6cbf4..f407bc9 100644
--- a/sc/source/ui/inc/checklistmenu.hxx
+++ b/sc/source/ui/inc/checklistmenu.hxx
@@ -130,7 +130,7 @@ public:
    void setMemberSize(size_t n);
    void addDateMember(const OUString& rName, double nVal, bool bVisible);
    void addMember(const OUString& rName, bool bVisible);
    size_t initMembers();
    size_t initMembers(int nMaxMemberWidth = -1);
    void setConfig(const Config& rConfig);

    bool isAllSelected() const;
@@ -164,6 +164,8 @@ public:
    void setPopupEndAction(Action* p);

    void setHasDates(bool bHasDates);
    int GetTextWidth(const OUString& rsName) const;
    int IncreaseWindowWidthToFitText(int nMaxTextWidth);

private:

diff --git a/sc/source/ui/view/gridwin.cxx b/sc/source/ui/view/gridwin.cxx
index 296873a..9b30f35 100644
--- a/sc/source/ui/view/gridwin.cxx
+++ b/sc/source/ui/view/gridwin.cxx
@@ -656,6 +656,42 @@ void ScGridWindow::LaunchAutoFilterMenu(SCCOL nCol, SCROW nRow)
    mpAutoFilterPopup.reset(VclPtr<ScCheckListMenuWindow>::Create(this, pDoc, false, nColWidth));
    ScCheckListMenuControl& rControl = mpAutoFilterPopup->get_widget();

    // Estimate the width (in pixels) of the longest text in the list
    ScFilterEntries aFilterEntries;
    pDoc->GetFilterEntries(nCol, nRow, nTab, aFilterEntries);
    int nMaxTextWidth = 0;
    if (aFilterEntries.size() <= 10)
    {
        // do pixel calculation for all elements of short lists
        for (const auto& rEntry : aFilterEntries)
        {
            const OUString& aText = rEntry.GetString();
            nMaxTextWidth = std::max<int>(nMaxTextWidth, rControl.GetTextWidth(aText) + aText.getLength() * 2);
        }
    }
    else
    {
        // find the longest string, probably it will be the longest rendered text, too
        // (performance optimization for long lists)
        auto itMax = aFilterEntries.begin();
        for (auto it = itMax; it != aFilterEntries.end(); ++it)
        {
            int nTextWidth = it->GetString().getLength();
            if (nMaxTextWidth < nTextWidth)
            {
                nMaxTextWidth = nTextWidth;
                itMax = it;
            }
        }
        nMaxTextWidth = rControl.GetTextWidth(itMax->GetString()) + nMaxTextWidth * 2;
    }

    // window should be at least as wide as the column, or the longest text + checkbox, scrollbar ... (it is estimated with 70 pixel now)
    // window should be maximum 1024 pixel wide.
    int nWindowWidth = std::min<int>(1024, nMaxTextWidth + 70);
    nWindowWidth = rControl.IncreaseWindowWidthToFitText(nWindowWidth);
    nMaxTextWidth = std::max<int>(nMaxTextWidth, nWindowWidth - 70);

    if (bLOKActive)
        mpAutoFilterPopup->SetLOKNotifier(SfxViewShell::Current());
    rControl.setOKAction(new AutoFilterAction(this, AutoFilterMode::Normal));
@@ -703,8 +739,6 @@ void ScGridWindow::LaunchAutoFilterMenu(SCCOL nCol, SCROW nRow)
    }

    // Populate the check box list.
    ScFilterEntries aFilterEntries;
    pDoc->GetFilterEntries(nCol, nRow, nTab, aFilterEntries);

    rControl.setHasDates(aFilterEntries.mbHasDates);
    rControl.setMemberSize(aFilterEntries.size());
@@ -738,7 +772,7 @@ void ScGridWindow::LaunchAutoFilterMenu(SCCOL nCol, SCROW nRow)
    rControl.addMenuItem(
        ScResId(SCSTR_STDFILTER), new AutoFilterAction(this, AutoFilterMode::Custom));

    rControl.initMembers();
    rControl.initMembers(nMaxTextWidth + 20); // 20 pixel estimated for the checkbox

    ScCheckListMenuControl::Config aConfig;
    aConfig.mbAllowEmptySet = false;
diff --git a/sc/uiconfig/scalc/ui/filterdropdown.ui b/sc/uiconfig/scalc/ui/filterdropdown.ui
index b6a3e4f..c84131fd 100644
--- a/sc/uiconfig/scalc/ui/filterdropdown.ui
+++ b/sc/uiconfig/scalc/ui/filterdropdown.ui
@@ -198,7 +198,8 @@
                <property name="can_focus">True</property>
                <property name="hexpand">True</property>
                <property name="vexpand">True</property>
                <property name="hscrollbar_policy">external</property>
                <property name="hscrollbar_policy">automatic</property>
                <property name="vscrollbar_policy">automatic</property>
                <property name="shadow_type">in</property>
                <child>
                  <object class="GtkTreeView" id="check_list_box">