weld ScFilterListBox

use the same positioning and sizing logic for
DataSelectMenu and ScenarioMenu

Change-Id: I34d183f72b6719528ce357b1fd40a1fdd1a4a29a
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/94774
Tested-by: Jenkins
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
diff --git a/sc/UIConfig_scalc.mk b/sc/UIConfig_scalc.mk
index 3035aa2..8e3de96 100644
--- a/sc/UIConfig_scalc.mk
+++ b/sc/UIConfig_scalc.mk
@@ -126,6 +126,7 @@
	sc/uiconfig/scalc/ui/externaldata \
	sc/uiconfig/scalc/ui/exponentialsmoothingdialog \
	sc/uiconfig/scalc/ui/filldlg \
	sc/uiconfig/scalc/ui/filterlist \
	sc/uiconfig/scalc/ui/footerdialog \
	sc/uiconfig/scalc/ui/formatcellsdialog \
	sc/uiconfig/scalc/ui/formulacalculationoptions \
diff --git a/sc/source/ui/inc/gridwin.hxx b/sc/source/ui/inc/gridwin.hxx
index 5f0844a..e04fc42 100644
--- a/sc/source/ui/inc/gridwin.hxx
+++ b/sc/source/ui/inc/gridwin.hxx
@@ -373,6 +373,8 @@

    void            UpdateFormulas(SCCOL nX1 = -1, SCROW nY1 = -1, SCCOL nX2 = -1, SCROW nY2 = -1);

    void            ShowFilterMenu(const tools::Rectangle& rCellRect, bool bLayoutRTL);

    void            LaunchDataSelectMenu( SCCOL nCol, SCROW nRow );
    void            DoScenarioMenu( const ScRange& rScenRange );

diff --git a/sc/source/ui/view/gridwin.cxx b/sc/source/ui/view/gridwin.cxx
index 0e2c817..bb55a74 100644
--- a/sc/source/ui/view/gridwin.cxx
+++ b/sc/source/ui/view/gridwin.cxx
@@ -37,11 +37,11 @@
#include <sfx2/ipclient.hxx>
#include <svl/stritem.hxx>
#include <svl/sharedstringpool.hxx>
#include <vcl/InterimItemWindow.hxx>
#include <vcl/canvastools.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/cursor.hxx>
#include <vcl/inputctx.hxx>
#include <vcl/lstbox.hxx>
#include <vcl/settings.hxx>
#include <sot/formats.hxx>
#include <comphelper/classids.hxx>
@@ -187,29 +187,30 @@
    return bChanged;
}

class ScFilterListBox : public ListBox
class ScFilterListBox final : public InterimItemWindow
{
private:
    std::unique_ptr<weld::TreeView> xTreeView;
    VclPtr<ScGridWindow>   pGridWin;
    SCCOL           nCol;
    SCROW           nRow;
    bool            bInit;
    bool            bCancelled;
    bool            bInSelect;
    sal_uLong       nSel;
    ScFilterBoxMode eMode;
    ImplSVEvent* nAsyncSelectHdl;

protected:
    void            SelectHdl();
    DECL_LINK(SelectHdl, weld::TreeView&, bool);
    DECL_LINK(KeyInputHdl, const KeyEvent&, bool);
    DECL_LINK(AsyncSelectHdl, void*, void);

public:
                ScFilterListBox( vcl::Window* pParent, ScGridWindow* pGrid,
                                 SCCOL nNewCol, SCROW nNewRow, ScFilterBoxMode eNewMode );
                virtual ~ScFilterListBox() override;
    ScFilterListBox( vcl::Window* pParent, ScGridWindow* pGrid,
                     SCCOL nNewCol, SCROW nNewRow, ScFilterBoxMode eNewMode );
    virtual ~ScFilterListBox() override;
    virtual void dispose() override;

    virtual bool    PreNotify( NotifyEvent& rNEvt ) override;
    virtual void    Select() override;
    weld::TreeView& get_widget() { return *xTreeView; }

    SCCOL           GetCol() const          { return nCol; }
    SCROW           GetRow() const          { return nRow; }
@@ -217,22 +218,24 @@
    void            EndInit();
    bool            IsInInit() const        { return bInit; }
    void            SetCancelled()          { bCancelled = true; }
    bool            IsInSelect() const      { return bInSelect; }
};

//  ListBox in a FloatingWindow (pParent)
ScFilterListBox::ScFilterListBox( vcl::Window* pParent, ScGridWindow* pGrid,
                                  SCCOL nNewCol, SCROW nNewRow, ScFilterBoxMode eNewMode ) :
    ListBox( pParent, WB_AUTOHSCROLL | WB_LISTBOX_POPUP ),
    InterimItemWindow(pParent, "modules/scalc/ui/filterlist.ui", "FilterList"),
    xTreeView(m_xBuilder->weld_tree_view("list")),
    pGridWin( pGrid ),
    nCol( nNewCol ),
    nRow( nNewRow ),
    bInit( true ),
    bCancelled( false ),
    bInSelect( false ),
    nSel( 0 ),
    eMode( eNewMode )
    eMode( eNewMode ),
    nAsyncSelectHdl(nullptr)
{
    xTreeView->connect_row_activated(LINK(this, ScFilterListBox, SelectHdl));
    xTreeView->connect_key_press(LINK(this, ScFilterListBox, KeyInputHdl));
}

ScFilterListBox::~ScFilterListBox()
@@ -242,16 +245,20 @@

void ScFilterListBox::dispose()
{
    if (IsMouseCaptured())
        ReleaseMouse();
    if (nAsyncSelectHdl)
    {
        Application::RemoveUserEvent(nAsyncSelectHdl);
        nAsyncSelectHdl = nullptr;
    }
    pGridWin.clear();
    ListBox::dispose();
    xTreeView.reset();
    InterimItemWindow::dispose();
}

void ScFilterListBox::EndInit()
{
    sal_Int32 nPos = GetSelectedEntryPos();
    if ( LISTBOX_ENTRY_NOTFOUND == nPos )
    sal_Int32 nPos = xTreeView->get_selected_index();
    if (nPos == -1)
        nSel = 0;
    else
        nSel = nPos;
@@ -259,52 +266,41 @@
    bInit = false;
}

bool ScFilterListBox::PreNotify( NotifyEvent& rNEvt )
IMPL_LINK(ScFilterListBox, KeyInputHdl, const KeyEvent&, rKeyEvent, bool)
{
    bool bDone = false;
    if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT )

    vcl::KeyCode aCode = rKeyEvent.GetKeyCode();
    // esc with no modifiers
    if (!aCode.GetModifier() && aCode.GetCode() == KEY_ESCAPE)
    {
        KeyEvent aKeyEvt = *rNEvt.GetKeyEvent();
        vcl::KeyCode aCode = aKeyEvt.GetKeyCode();
        if ( !aCode.GetModifier() ) // no modifiers
        {
            sal_uInt16 nKey = aCode.GetCode();
            if ( nKey == KEY_RETURN )
            {
                SelectHdl(); // select
                bDone = true;
            }
            else if ( nKey == KEY_ESCAPE )
            {
                pGridWin->ClickExtern();  // clears the listbox
                bDone = true;
            }
        }
        pGridWin->ClickExtern();  // clears the listbox
        bDone = true;
    }

    return bDone || ListBox::PreNotify( rNEvt );
    return bDone;
}

void ScFilterListBox::Select()
IMPL_LINK_NOARG(ScFilterListBox, SelectHdl, weld::TreeView&, bool)
{
    ListBox::Select();
    SelectHdl();
}

void ScFilterListBox::SelectHdl()
{
    if ( !IsTravelSelect() && !bInit && !bCancelled )
    if (!bInit && !bCancelled && !nAsyncSelectHdl)
    {
        sal_Int32 nPos = GetSelectedEntryPos();
        if ( LISTBOX_ENTRY_NOTFOUND != nPos )
        int nPos = xTreeView->get_selected_index();
        if (nPos != -1)
        {
            nSel = nPos;
            // #i81298# set bInSelect flag, so the box isn't deleted from modifications within FilterSelect
            bInSelect = true;
            pGridWin->FilterSelect( nSel );
            bInSelect = false;
            // #i81298# launch async so the box isn't deleted from modifications within FilterSelect
            nAsyncSelectHdl = Application::PostUserEvent(LINK(this, ScFilterListBox, AsyncSelectHdl));
        }
    }
    return true;
}

IMPL_LINK_NOARG(ScFilterListBox, AsyncSelectHdl, void*, void)
{
    pGridWin->FilterSelect( nSel );
    nAsyncSelectHdl = nullptr;
    pGridWin->ClickExtern();
}

namespace {
@@ -516,13 +512,9 @@
{
    do
    {
        // #i81298# don't delete the filter box when called from its select handler
        // (possible through row header size update)
        // #i84277# when initializing the filter box, a Basic error can deactivate the view
        if (mpFilterBox && (mpFilterBox->IsInSelect() || mpFilterBox->IsInInit()))
        {
        if (mpFilterBox && mpFilterBox->IsInInit())
            break;
        }
        mpFilterBox.disposeAndClear();
        mpFilterFloat.disposeAndClear();
    }
@@ -923,6 +915,52 @@
    DPLaunchFieldPopupMenu(OutputToScreenPixel(aScrPos), aScrSize, ScAddress(nCol, nRow, nTab), pDPObj);
}

void ScGridWindow::ShowFilterMenu(const tools::Rectangle& rCellRect, bool bLayoutRTL)
{
    auto nSizeX = rCellRect.GetWidth();

    // minimum width in pixel
    if (comphelper::LibreOfficeKit::isActive())
    {
        const long nMinLOKWinWidth = static_cast<long>(1.3 * STD_COL_WIDTH / TWIPS_PER_PIXEL);
        if (nSizeX < nMinLOKWinWidth)
            nSizeX = nMinLOKWinWidth;
    }

    weld::TreeView& rFilterBox = mpFilterBox->get_widget();
    int nEntryCount = rFilterBox.n_children();
    if (nEntryCount > SC_FILTERLISTBOX_LINES)
        nEntryCount = SC_FILTERLISTBOX_LINES;
    auto nHeight = rFilterBox.get_height_rows(nEntryCount);
    rFilterBox.set_size_request(-1, nHeight);
    Size aSize(rFilterBox.get_preferred_size());
    if (aSize.Width() < nSizeX)
        aSize.setWidth(nSizeX);
    if (aSize.Width() > 300)
        aSize.setWidth(300);     // do not over do it (Pixel)

    aSize.AdjustWidth(4); // add a little margin
    nSizeX += 4;
    aSize.AdjustHeight(4);

    tools::Rectangle aCellRect(rCellRect);
    aCellRect.AdjustLeft(-2); // offset the little margin above

    if (!bLayoutRTL && aSize.Width() > nSizeX)
    {
        //  move popup position
        long nDiff = aSize.Width() - nSizeX;
        long nNewX = aCellRect.Left() - nDiff;
        if ( nNewX < 0 )
            nNewX = 0;
        aCellRect.SetLeft( nNewX );
    }

    mpFilterBox->SetSizePixel(aSize);
    mpFilterFloat->SetOutputSizePixel(aSize);
    mpFilterFloat->StartPopupMode(aCellRect, FloatWinPopupFlags::Down|FloatWinPopupFlags::GrabFocus);
}

void ScGridWindow::DoScenarioMenu( const ScRange& rScenRange )
{
    bool bMenuAtTop = true;
@@ -945,7 +983,6 @@

    long nSizeX  = 0;
    long nSizeY  = 0;
    long nHeight = 0;
    pViewData->GetMergeSizePixel( nCol, nRow, nSizeX, nSizeY );
    // The button height should not use the merged cell height, should still use single row height
    nSizeY = ScViewData::ToPixel(pDoc->GetRowHeight(nRow, nTab), pViewData->GetPPTY());
@@ -967,37 +1004,14 @@
    mpFilterFloat.reset(VclPtr<ScFilterFloatingWindow>::Create(this, WinBits(WB_BORDER)));
    mpFilterFloat->SetPopupModeEndHdl( LINK( this, ScGridWindow, PopupModeEndHdl ) );
    mpFilterBox.reset(VclPtr<ScFilterListBox>::Create(mpFilterFloat.get(), this, nCol, nRow, ScFilterBoxMode::Scenario));
    if (bLayoutRTL)
        mpFilterBox->EnableMirroring();
    weld::TreeView& rFilterBox = mpFilterBox->get_widget();
    rFilterBox.set_direction(bLayoutRTL); // Fix for bug fdo#44925 use sheet direction for widget RTL/LTR

    nSizeX += 1;

    {
        vcl::Font aOldFont = GetFont();
        SetFont(mpFilterBox->GetFont());
        MapMode aOldMode = GetMapMode();
        SetMapMode(MapMode(MapUnit::MapPixel));

        nHeight  = GetTextHeight();
        nHeight *= SC_FILTERLISTBOX_LINES;

        SetMapMode( aOldMode );
        SetFont( aOldFont );
    }

    //  SetSize later

    //  ParentSize missing check
    Size aSize( nSizeX, nHeight );
    mpFilterBox->SetSizePixel( aSize );
    mpFilterBox->Show();                 // Show has to be before SetUpdateMode !!!
    mpFilterBox->SetUpdateMode(false);

    //  SetOutputSizePixel/StartPopupMode first below, when the size is set

    //  Listbox fill

    long nMaxText = 0;
    rFilterBox.freeze();
    OUString aCurrent;
    OUString aTabName;
    SCTAB nTabCount = pDoc->GetTableCount();
@@ -1007,56 +1021,31 @@
        if (pDoc->HasScenarioRange( i, rScenRange ))
            if (pDoc->GetName( i, aTabName ))
            {
                mpFilterBox->InsertEntry(aTabName);
                rFilterBox.append_text(aTabName);
                if (pDoc->IsActiveScenario(i))
                    aCurrent = aTabName;
                long nTextWidth = mpFilterBox->GetTextWidth(aTabName);
                if ( nTextWidth > nMaxText )
                    nMaxText = nTextWidth;
                ++nEntryCount;
            }
    }
    if (nEntryCount > SC_FILTERLISTBOX_LINES)
        nMaxText += GetSettings().GetStyleSettings().GetScrollBarSize();
    nMaxText += 4;          // for margin
    if ( nMaxText > 300 )
        nMaxText = 300;     // do not over do it (Pixel)
    rFilterBox.thaw();

    if (nMaxText > nSizeX)  // Adjust size to what is needed
    {
        long nDiff = nMaxText - nSizeX;
        aSize = Size( nMaxText, nHeight );
        mpFilterBox->SetSizePixel(aSize);
        mpFilterFloat->SetOutputSizePixel(aSize);
    ShowFilterMenu(aCellRect, bLayoutRTL);

        if ( !bLayoutRTL )
        {
            //  also move popup position
            long nNewX = aCellRect.Left() - nDiff;
            if ( nNewX < 0 )
                nNewX = 0;
            aCellRect.SetLeft( nNewX );
        }
    }
    rFilterBox.grab_focus();

    mpFilterFloat->SetOutputSizePixel( aSize );
    mpFilterFloat->StartPopupMode( aCellRect, FloatWinPopupFlags::Down|FloatWinPopupFlags::GrabFocus );

    mpFilterBox->SetUpdateMode(true);
    mpFilterBox->GrabFocus();

    sal_Int32 nPos = LISTBOX_ENTRY_NOTFOUND;
    sal_Int32 nPos = -1;
    if (!aCurrent.isEmpty())
    {
        nPos = mpFilterBox->GetEntryPos(aCurrent);
        nPos = rFilterBox.find_text(aCurrent);
    }
    if (LISTBOX_ENTRY_NOTFOUND == nPos && mpFilterBox->GetEntryCount() > 0 )
    if (nPos == -1 && rFilterBox.n_children() > 0 )
    {
        nPos = 0;
    }
    if (LISTBOX_ENTRY_NOTFOUND != nPos )
    if (nPos != -1)
    {
        mpFilterBox->SelectEntryPos(nPos);
        rFilterBox.set_cursor(nPos);
        rFilterBox.select(nPos);
    }
    mpFilterBox->EndInit();

@@ -1077,7 +1066,6 @@

    long nSizeX  = 0;
    long nSizeY  = 0;
    long nHeight = 0;
    pViewData->GetMergeSizePixel( nCol, nRow, nSizeX, nSizeY );
    Point aPos = pViewData->GetScrPos( nCol, nRow, eWhich );
    bool bLOKActive = comphelper::LibreOfficeKit::isActive();
@@ -1112,24 +1100,8 @@
    }
    mpFilterFloat->SetPopupModeEndHdl(LINK( this, ScGridWindow, PopupModeEndHdl));
    mpFilterBox.reset(VclPtr<ScFilterListBox>::Create(mpFilterFloat.get(), this, nCol, nRow, ScFilterBoxMode::DataSelect));
    // Fix for bug fdo#44925
    if (AllSettings::GetLayoutRTL() != bLayoutRTL)
        mpFilterBox->EnableMirroring();

    nSizeX += 1;

    {
        vcl::Font aOldFont = GetFont();
        SetFont(mpFilterBox->GetFont());
        MapMode aOldMode = GetMapMode();
        SetMapMode(MapMode(MapUnit::MapPixel));

        nHeight  = GetTextHeight();
        nHeight *= SC_FILTERLISTBOX_LINES;

        SetMapMode( aOldMode );
        SetFont( aOldFont );
    }
    weld::TreeView& rFilterBox = mpFilterBox->get_widget();
    rFilterBox.set_direction(bLayoutRTL); // Fix for bug fdo#44925 use sheet direction for widget RTL/LTR

    // SetSize later

@@ -1142,31 +1114,8 @@

    if (!bEmpty)
    {
        //  Adjust position and size to Window
        //! Check first if the entries fit (width)

        // minimum width in pixel
        const long nMinLOKWinWidth = static_cast<long>(1.3 * STD_COL_WIDTH / TWIPS_PER_PIXEL);
        if (bLOKActive && nSizeX < nMinLOKWinWidth)
            nSizeX = nMinLOKWinWidth;

        if (bLOKActive && aStrings.size() < SC_FILTERLISTBOX_LINES)
            nHeight = nHeight * (aStrings.size() + 1) / SC_FILTERLISTBOX_LINES;

        Size aParentSize = GetParent()->GetOutputSizePixel();
        Size aSize( nSizeX, nHeight );

        if ( aSize.Height() > aParentSize.Height() )
            aSize.setHeight( aParentSize.Height() );
        if ( aPos.Y() + aSize.Height() > aParentSize.Height() )
            aPos.setY( aParentSize.Height() - aSize.Height() );

        mpFilterBox->SetSizePixel(aSize);
        mpFilterBox->Show();                 // Show has to be before SetUpdateMode !!!
        mpFilterBox->SetUpdateMode(false);

        mpFilterFloat->SetOutputSizePixel(aSize);
        mpFilterFloat->StartPopupMode(aCellRect, FloatWinPopupFlags::Down | FloatWinPopupFlags::GrabFocus);
        mpFilterBox->Show();                 // Show has to be before freeze !!!
        rFilterBox.freeze();

        // Fill Listbox
        bool bWait = aStrings.size() > 100;
@@ -1175,15 +1124,17 @@
            EnterWait();

        for (const auto& rString : aStrings)
            mpFilterBox->InsertEntry(rString.GetString());
            rFilterBox.append_text(rString.GetString());

        if (bWait)
            LeaveWait();

        mpFilterBox->SetUpdateMode(true);
        rFilterBox.thaw();

        ShowFilterMenu(aCellRect, bLayoutRTL);
    }

    sal_Int32 nSelPos = LISTBOX_ENTRY_NOTFOUND;
    sal_Int32 nSelPos = -1;

    sal_uLong nIndex = pDoc->GetAttr( nCol, nRow, nTab, ATTR_VALIDDATA )->GetValue();
    if ( nIndex )
@@ -1225,15 +1176,16 @@
    }
    else
    {
        mpFilterBox->GrabFocus();
        rFilterBox.grab_focus();

        // Select only after GrabFocus, so that the focus rectangle gets correct
        if ( LISTBOX_ENTRY_NOTFOUND != nSelPos )
            mpFilterBox->SelectEntryPos(nSelPos);
        else
        if (nSelPos != -1)
        {
            mpFilterBox->SetNoSelection();
            rFilterBox.set_cursor(nSelPos);
            rFilterBox.select(nSelPos);
        }
        else
            rFilterBox.unselect_all();

        mpFilterBox->EndInit();
    }
@@ -1241,7 +1193,8 @@

void ScGridWindow::FilterSelect( sal_uLong nSel )
{
    OUString aString = mpFilterBox->GetEntry(static_cast<sal_Int32>(nSel));
    weld::TreeView& rFilterBox = mpFilterBox->get_widget();
    OUString aString = rFilterBox.get_text(static_cast<sal_Int32>(nSel));

    SCCOL nCol = mpFilterBox->GetCol();
    SCROW nRow = mpFilterBox->GetRow();
diff --git a/sc/uiconfig/scalc/ui/filterlist.ui b/sc/uiconfig/scalc/ui/filterlist.ui
new file mode 100644
index 0000000..0456fab
--- /dev/null
+++ b/sc/uiconfig/scalc/ui/filterlist.ui
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2 -->
<interface domain="sc">
  <requires lib="gtk+" version="3.18"/>
  <object class="GtkTreeStore" id="liststore1">
    <columns>
      <!-- column-name text -->
      <column type="gchararray"/>
      <!-- column-name id -->
      <column type="gchararray"/>
    </columns>
  </object>
  <object class="GtkBox" id="FilterList">
    <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="show_expanders">False</property>
            <property name="activate_on_single_click">True</property>
            <child internal-child="selection">
              <object class="GtkTreeSelection" id="treeview-selection1"/>
            </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>
</interface>