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>