Files
Michael Weghorn e4f1a48967 toolkit: Rename TableControl header/source to match new class name
Following

    Change-Id: I54e578098684a027c9bf2f192a5af3fecb4e65d7
    Author: Michael Weghorn <m.weghorn@posteo.de>
    Date:   Fri Sep 18 15:44:17 2026 +0200

        toolkit: Rename TableControl_Impl to TableControl

, also rename the header and source file to match
with the new class name.
(See that commit's commit message for more background.)

Change-Id: I5d7c4251902689718b9b2f3217671cad5b071201
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211163
Tested-by: Jenkins
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
2026-09-19 16:19:11 +02:00

2630 lines
94 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* 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/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <controls/table/AccessibleGridControl.hxx>
#include <controls/table/TableControl.hxx>
#include <controls/table/TableFunctionSet.hxx>
#include <controls/table/defaultinputhandler.hxx>
#include <controls/table/tablemodel.hxx>
#include "tabledatawindow.hxx"
#include "tablegeometry.hxx"
#include <com/sun/star/accessibility/XAccessible.hpp>
#include <com/sun/star/accessibility/AccessibleStateType.hpp>
#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp>
#include <com/sun/star/accessibility/AccessibleEventId.hpp>
#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp>
#include <comphelper/flagguard.hxx>
#include <tools/mapunit.hxx>
#include <vcl/toolkit/scrbar.hxx>
#include <vcl/seleng.hxx>
#include <vcl/settings.hxx>
#include <vcl/image.hxx>
#include <vcl/vclevent.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <tools/debug.hxx>
#include <cstdlib>
#include <numeric>
#define MIN_COLUMN_WIDTH_PIXEL 4
namespace svt::table
{
using ::com::sun::star::accessibility::AccessibleTableModelChange;
using ::com::sun::star::uno::Any;
using ::com::sun::star::accessibility::XAccessible;
using ::com::sun::star::uno::Reference;
namespace AccessibleEventId = css::accessibility::AccessibleEventId;
namespace AccessibleTableModelChangeType = css::accessibility::AccessibleTableModelChangeType;
//= SuppressCursor
namespace
{
class SuppressCursor
{
private:
TableControl& m_rTable;
public:
explicit SuppressCursor(TableControl& _rTable)
: m_rTable(_rTable)
{
m_rTable.hideCursor();
}
~SuppressCursor() { m_rTable.showCursor(); }
};
//= EmptyTableModel
/** default implementation of an ->ITableModel, used as fallback when no
real model is present
Instances of this class are static in any way, and provide the least
necessary default functionality for a table model.
*/
class EmptyTableModel : public ITableModel
{
public:
EmptyTableModel() {}
// ITableModel overridables
virtual TableSize getColumnCount() const override { return 0; }
virtual TableSize getRowCount() const override { return 0; }
virtual bool hasColumnHeaders() const override { return false; }
virtual bool hasRowHeaders() const override { return false; }
virtual PColumnModel getColumnModel(sal_Int32) override
{
OSL_FAIL("EmptyTableModel::getColumnModel: invalid call!");
return PColumnModel();
}
virtual PTableRenderer getRenderer() const override { return PTableRenderer(); }
virtual PTableInputHandler getInputHandler() const override { return PTableInputHandler(); }
virtual TableMetrics getRowHeight() const override { return 5 * 100; }
virtual TableMetrics getColumnHeaderHeight() const override { return 0; }
virtual TableMetrics getRowHeaderWidth() const override { return 0; }
virtual ScrollbarVisibility getVerticalScrollbarVisibility() const override
{
return ScrollbarShowNever;
}
virtual ScrollbarVisibility getHorizontalScrollbarVisibility() const override
{
return ScrollbarShowNever;
}
virtual void addTableModelListener(const VclPtr<TableControl>&) override {}
virtual void removeTableModelListener(const VclPtr<TableControl>&) override {}
virtual ::std::optional<::Color> getLineColor() const override
{
return ::std::optional<::Color>();
}
virtual ::std::optional<::Color> getHeaderBackgroundColor() const override
{
return ::std::optional<::Color>();
}
virtual ::std::optional<::Color> getHeaderTextColor() const override
{
return ::std::optional<::Color>();
}
virtual ::std::optional<::Color> getActiveSelectionBackColor() const override
{
return ::std::optional<::Color>();
}
virtual ::std::optional<::Color> getInactiveSelectionBackColor() const override
{
return ::std::optional<::Color>();
}
virtual ::std::optional<::Color> getActiveSelectionTextColor() const override
{
return ::std::optional<::Color>();
}
virtual ::std::optional<::Color> getInactiveSelectionTextColor() const override
{
return ::std::optional<::Color>();
}
virtual ::std::optional<::Color> getTextColor() const override
{
return ::std::optional<::Color>();
}
virtual ::std::optional<::Color> getTextLineColor() const override
{
return ::std::optional<::Color>();
}
virtual ::std::optional<::std::vector<::Color>> getRowBackgroundColors() const override
{
return ::std::optional<::std::vector<::Color>>();
}
virtual css::style::VerticalAlignment getVerticalAlign() const override
{
return css::style::VerticalAlignment(0);
}
virtual ITableDataSort* getSortAdapter() override { return nullptr; }
virtual bool isEnabled() const override { return true; }
virtual void getCellContent(sal_Int32 const, sal_Int32 const,
css::uno::Any& o_cellContent) override
{
o_cellContent.clear();
}
virtual void getCellToolTip(sal_Int32 const, sal_Int32 const, css::uno::Any&) override {}
virtual Any getRowHeading(sal_Int32 const) const override { return Any(); }
};
}
TableControl::TableControl(vcl::Window* pParent, WinBits nStyle)
: Control(pParent, nStyle)
, m_pModel(std::make_shared<EmptyTableModel>())
, m_pInputHandler()
, m_nRowHeightPixel(15)
, m_nColHeaderHeightPixel(0)
, m_nRowHeaderWidthPixel(0)
, m_nColumnCount(0)
, m_nRowCount(0)
, m_nCurColumn(COL_INVALID)
, m_nCurRow(ROW_INVALID)
, m_nLeftColumn(0)
, m_nTopRow(0)
, m_nCursorHidden(1)
, m_pDataWindow(VclPtr<TableDataWindow>::Create(*this))
, m_pVScroll(nullptr)
, m_pHScroll(nullptr)
, m_pScrollCorner(nullptr)
, m_aSelectedRows()
, m_pTableFunctionSet(new TableFunctionSet(*this))
, m_nAnchor(-1)
, m_bUpdatingColWidths(false)
{
m_pSelEngine.reset(new SelectionEngine(m_pDataWindow.get(), m_pTableFunctionSet.get()));
m_pSelEngine->SetSelectionMode(SelectionMode::Single);
m_pDataWindow->SetPosPixel(Point(0, 0));
m_pDataWindow->Show();
m_pDataWindow->SetSelectHdl(LINK(this, TableControl, ImplSelectHdl));
// by default, use the background as determined by the style settings
const Color aWindowColor(GetSettings().GetStyleSettings().GetFieldColor());
SetBackground(Wallpaper(aWindowColor));
GetOutDev()->SetFillColor(aWindowColor);
SetCompoundControl(true);
}
TableControl::~TableControl() { disposeOnce(); }
void TableControl::dispose()
{
CallEventListeners(VclEventId::ObjectDying);
SetModel(PTableModel());
disposeAccessible();
m_pVScroll.disposeAndClear();
m_pHScroll.disposeAndClear();
m_pScrollCorner.disposeAndClear();
m_pDataWindow.disposeAndClear();
m_pTableFunctionSet.reset();
m_pSelEngine.reset();
Control::dispose();
}
void TableControl::GetFocus()
{
showCursor();
Control::GetFocus();
}
void TableControl::LoseFocus()
{
hideCursor();
Control::LoseFocus();
}
void TableControl::KeyInput(const KeyEvent& rKEvt)
{
bool bHandled = false;
const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
sal_uInt16 nKeyCode = rKeyCode.GetCode();
struct ActionMapEntry
{
sal_uInt16 nKeyCode;
sal_uInt16 nKeyModifier;
TableControlAction eAction;
} static const aKnownActions[]
= { { KEY_DOWN, 0, TableControlAction::cursorDown },
{ KEY_UP, 0, TableControlAction::cursorUp },
{ KEY_LEFT, 0, TableControlAction::cursorLeft },
{ KEY_RIGHT, 0, TableControlAction::cursorRight },
{ KEY_HOME, 0, TableControlAction::cursorToLineStart },
{ KEY_END, 0, TableControlAction::cursorToLineEnd },
{ KEY_PAGEUP, 0, TableControlAction::cursorPageUp },
{ KEY_PAGEDOWN, 0, TableControlAction::cursorPageDown },
{ KEY_PAGEUP, KEY_MOD1, TableControlAction::cursorToFirstLine },
{ KEY_PAGEDOWN, KEY_MOD1, TableControlAction::cursorToLastLine },
{ KEY_HOME, KEY_MOD1, TableControlAction::cursorTopLeft },
{ KEY_END, KEY_MOD1, TableControlAction::cursorBottomRight },
{ KEY_SPACE, KEY_MOD1, TableControlAction::cursorSelectRow },
{ KEY_UP, KEY_SHIFT, TableControlAction::cursorSelectRowUp },
{ KEY_DOWN, KEY_SHIFT, TableControlAction::cursorSelectRowDown },
{ KEY_END, KEY_SHIFT, TableControlAction::cursorSelectRowAreaBottom },
{ KEY_HOME, KEY_SHIFT, TableControlAction::cursorSelectRowAreaTop } };
for (const ActionMapEntry& rAction : aKnownActions)
{
if ((rAction.nKeyCode == nKeyCode) && (rAction.nKeyModifier == rKeyCode.GetModifier()))
{
bHandled = dispatchAction(rAction.eAction);
break;
}
}
if (!bHandled)
Control::KeyInput(rKEvt);
else
{
commitCellEvent(AccessibleEventId::STATE_CHANGED,
Any(css::accessibility::AccessibleStateType::FOCUSED), Any());
// Huh? What the heck? Why do we unconditionally notify a STATE_CHANGE/FOCUSED after each and every
// (handled) key stroke?
commitTableEvent(AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, Any(), Any());
// ditto: Why do we notify this unconditionally? We should find the right place to notify the
// ACTIVE_DESCENDANT_CHANGED event.
// Also, we should check if STATE_CHANGED/FOCUSED is really necessary: finally, the children are
// transient, aren't they?
}
}
void TableControl::StateChanged(StateChangedType i_nStateChange)
{
Control::StateChanged(i_nStateChange);
// forward certain settings to the data window
switch (i_nStateChange)
{
case StateChangedType::ControlFocus:
invalidateSelectedRows();
break;
case StateChangedType::ControlBackground:
if (IsControlBackground())
getDataWindow().SetControlBackground(GetControlBackground());
else
getDataWindow().SetControlBackground();
break;
case StateChangedType::ControlForeground:
if (IsControlForeground())
getDataWindow().SetControlForeground(GetControlForeground());
else
getDataWindow().SetControlForeground();
break;
case StateChangedType::ControlFont:
if (IsControlFont())
getDataWindow().SetControlFont(GetControlFont());
else
getDataWindow().SetControlFont();
break;
default:;
}
}
void TableControl::Resize()
{
Control::Resize();
onResize();
}
void TableControl::SetModel(const PTableModel& _pModel)
{
SuppressCursor aHideCursor(*this);
if (m_pModel)
m_pModel->removeTableModelListener(this);
m_pModel = _pModel;
if (!m_pModel)
m_pModel = std::make_shared<EmptyTableModel>();
m_pModel->addTableModelListener(this);
m_nCurRow = ROW_INVALID;
m_nCurColumn = COL_INVALID;
// recalc some model-dependent cached info
impl_ni_updateCachedModelValues();
impl_ni_relayout();
// completely invalidate
Invalidate();
// reset cursor to (0,0)
if (m_nRowCount)
m_nCurRow = 0;
if (m_nColumnCount)
m_nCurColumn = 0;
}
namespace
{
bool lcl_adjustSelectedRows(::std::vector<sal_Int32>& io_selectionIndexes,
sal_Int32 const i_firstAffectedRowIndex, TableSize const i_offset)
{
bool didChanges = false;
for (auto& selectionIndex : io_selectionIndexes)
{
if (selectionIndex < i_firstAffectedRowIndex)
continue;
selectionIndex += i_offset;
didChanges = true;
}
return didChanges;
}
}
void TableControl::rowsInserted(sal_Int32 i_first, sal_Int32 i_last)
{
OSL_PRECOND(i_last >= i_first, "TableControl::rowsInserted: invalid row indexes!");
TableSize const insertedRows = i_last - i_first + 1;
// adjust selection, if necessary
bool const selectionChanged = lcl_adjustSelectedRows(m_aSelectedRows, i_first, insertedRows);
// adjust our cached row count
m_nRowCount = m_pModel->getRowCount();
// if the rows have been inserted before the current row, adjust this
if (i_first <= m_nCurRow)
GoToCell(m_nCurColumn, m_nCurRow + insertedRows);
// relayout, since the scrollbar need might have changed
impl_ni_relayout();
// notify A1YY events
impl_commitAccessibleEvent(
AccessibleEventId::TABLE_MODEL_CHANGED,
Any(AccessibleTableModelChange(AccessibleTableModelChangeType::ROWS_INSERTED, i_first,
i_last, -1, -1)));
// schedule repaint
invalidateRowRange(i_first, ROW_INVALID);
// call selection handlers, if necessary
if (selectionChanged)
Select();
}
void TableControl::rowsRemoved(sal_Int32 i_first, sal_Int32 i_last)
{
sal_Int32 firstRemovedRow = i_first;
sal_Int32 lastRemovedRow = i_last;
// adjust selection, if necessary
bool selectionChanged = false;
if (i_first == -1)
{
selectionChanged = markAllRowsAsDeselected();
firstRemovedRow = 0;
lastRemovedRow = m_nRowCount - 1;
}
else
{
ENSURE_OR_RETURN_VOID(i_last >= i_first, "TableControl::rowsRemoved: illegal indexes!");
for (sal_Int32 row = i_first; row <= i_last; ++row)
{
if (markRowAsDeselected(row))
selectionChanged = true;
}
if (lcl_adjustSelectedRows(m_aSelectedRows, i_last + 1, i_first - i_last - 1))
selectionChanged = true;
}
// adjust cached row count
m_nRowCount = m_pModel->getRowCount();
// adjust the current row, if it is larger than the row count now
if (m_nCurRow >= m_nRowCount)
{
if (m_nRowCount > 0)
GoToCell(m_nCurColumn, m_nRowCount - 1);
else
{
m_nCurRow = ROW_INVALID;
m_nTopRow = 0;
}
}
else if (m_nRowCount == 0)
{
m_nTopRow = 0;
}
// relayout, since the scrollbar need might have changed
impl_ni_relayout();
// notify A11Y events
commitTableEvent(AccessibleEventId::TABLE_MODEL_CHANGED,
Any(AccessibleTableModelChange(AccessibleTableModelChangeType::ROWS_REMOVED,
firstRemovedRow, lastRemovedRow, -1, -1)),
Any());
// schedule a repaint
invalidateRowRange(firstRemovedRow, ROW_INVALID);
// call selection handlers, if necessary
if (selectionChanged)
Select();
}
void TableControl::columnInserted()
{
m_nColumnCount = m_pModel->getColumnCount();
impl_ni_relayout();
Invalidate();
}
void TableControl::columnRemoved()
{
m_nColumnCount = m_pModel->getColumnCount();
// adjust the current column, if it is larger than the column count now
if (m_nCurColumn >= m_nColumnCount)
{
if (m_nColumnCount > 0)
GoToCell(m_nCurColumn - 1, m_nCurRow);
else
m_nCurColumn = COL_INVALID;
}
impl_ni_relayout();
Invalidate();
}
void TableControl::allColumnsRemoved()
{
m_nColumnCount = m_pModel->getColumnCount();
impl_ni_relayout();
Invalidate();
}
void TableControl::cellsUpdated(sal_Int32 const i_firstRow, sal_Int32 const i_lastRow)
{
invalidateRowRange(i_firstRow, i_lastRow);
}
void TableControl::tableMetricsChanged()
{
impl_ni_updateCachedTableMetrics();
impl_ni_relayout();
Invalidate();
}
void TableControl::impl_invalidateColumn(sal_Int32 const i_column)
{
tools::Rectangle const aAllCellsArea(impl_getAllVisibleCellsArea());
const TableColumnGeometry aColumn(*this, aAllCellsArea, i_column);
if (aColumn.isValid())
Invalidate(aColumn.getRect());
}
void TableControl::columnChanged(sal_Int32 const i_column,
ColumnAttributeGroup const i_attributeGroup)
{
ColumnAttributeGroup nGroup(i_attributeGroup);
if (nGroup & ColumnAttributeGroup::APPEARANCE)
{
impl_invalidateColumn(i_column);
nGroup &= ~ColumnAttributeGroup::APPEARANCE;
}
if (nGroup & ColumnAttributeGroup::WIDTH)
{
if (!m_bUpdatingColWidths)
{
impl_ni_relayout(i_column);
invalidate(TableArea::All);
}
nGroup &= ~ColumnAttributeGroup::WIDTH;
}
OSL_ENSURE((nGroup == ColumnAttributeGroup::NONE)
|| (i_attributeGroup == ColumnAttributeGroup::ALL),
"TableControl::columnChanged: don't know how to handle this change!");
}
tools::Rectangle TableControl::impl_getAllVisibleCellsArea() const
{
tools::Rectangle aArea(Point(0, 0), Size(0, 0));
// determine the right-most border of the last column which is
// at least partially visible
aArea.SetRight(m_nRowHeaderWidthPixel);
if (!m_aColumnWidths.empty())
{
// the number of pixels which are scrolled out of the left hand
// side of the window
const tools::Long nScrolledOutLeft
= m_nLeftColumn == 0 ? 0 : m_aColumnWidths[m_nLeftColumn - 1].getEnd();
ColumnPositions::const_reverse_iterator loop = m_aColumnWidths.rbegin();
do
{
aArea.SetRight(loop->getEnd() - nScrolledOutLeft);
++loop;
} while ((loop != m_aColumnWidths.rend())
&& (loop->getEnd() - nScrolledOutLeft >= aArea.Right()));
}
// so far, aArea.Right() denotes the first pixel *after* the cell area
aArea.AdjustRight(-1);
// determine the last row which is at least partially visible
aArea.SetBottom(m_nColHeaderHeightPixel + impl_getVisibleRows(true) * m_nRowHeightPixel - 1);
return aArea;
}
tools::Rectangle TableControl::impl_getAllVisibleDataCellArea() const
{
tools::Rectangle aArea(impl_getAllVisibleCellsArea());
aArea.SetLeft(m_nRowHeaderWidthPixel);
aArea.SetTop(m_nColHeaderHeightPixel);
return aArea;
}
void TableControl::impl_ni_updateCachedTableMetrics()
{
m_nRowHeightPixel
= LogicToPixel(Size(0, m_pModel->getRowHeight()), MapMode(MapUnit::MapAppFont)).Height();
m_nColHeaderHeightPixel = 0;
if (m_pModel->hasColumnHeaders())
m_nColHeaderHeightPixel
= LogicToPixel(Size(0, m_pModel->getColumnHeaderHeight()), MapMode(MapUnit::MapAppFont))
.Height();
m_nRowHeaderWidthPixel = 0;
if (m_pModel->hasRowHeaders())
m_nRowHeaderWidthPixel
= LogicToPixel(Size(m_pModel->getRowHeaderWidth(), 0), MapMode(MapUnit::MapAppFont))
.Width();
}
void TableControl::impl_ni_updateCachedModelValues()
{
m_pInputHandler = m_pModel->getInputHandler();
if (!m_pInputHandler)
m_pInputHandler = std::make_shared<DefaultInputHandler>();
m_nColumnCount = m_pModel->getColumnCount();
if (m_nLeftColumn >= m_nColumnCount)
m_nLeftColumn = (m_nColumnCount > 0) ? m_nColumnCount - 1 : 0;
m_nRowCount = m_pModel->getRowCount();
if (m_nTopRow >= m_nRowCount)
m_nTopRow = (m_nRowCount > 0) ? m_nRowCount - 1 : 0;
impl_ni_updateCachedTableMetrics();
}
namespace
{
/// determines whether a scrollbar is needed for the given values
bool lcl_determineScrollbarNeed(tools::Long const i_position,
ScrollbarVisibility const i_visibility,
tools::Long const i_availableSpace, tools::Long const i_neededSpace)
{
if (i_visibility == ScrollbarShowNever)
return false;
if (i_visibility == ScrollbarShowAlways)
return true;
if (i_position > 0)
return true;
if (i_availableSpace >= i_neededSpace)
return false;
return true;
}
void lcl_setButtonRepeat(vcl::Window& _rWindow)
{
AllSettings aSettings = _rWindow.GetSettings();
MouseSettings aMouseSettings = aSettings.GetMouseSettings();
aMouseSettings.SetButtonRepeat(0);
aSettings.SetMouseSettings(aMouseSettings);
_rWindow.SetSettings(aSettings, true);
}
bool lcl_updateScrollbar(vcl::Window& _rParent, VclPtr<ScrollBar>& _rpBar, bool const i_needBar,
tools::Long _nVisibleUnits, tools::Long _nPosition, tools::Long _nRange,
bool _bHorizontal, const Link<ScrollBar*, void>& _rScrollHandler)
{
// do we currently have the scrollbar?
bool bHaveBar = _rpBar != nullptr;
// do we need to correct the scrollbar visibility?
if (bHaveBar && !i_needBar)
{
if (_rpBar->IsTracking())
_rpBar->EndTracking();
_rpBar.disposeAndClear();
}
else if (!bHaveBar && i_needBar)
{
_rpBar = VclPtr<ScrollBar>::Create(
&_rParent, WB_DRAG | (_bHorizontal ? WB_HSCROLL : WB_VSCROLL));
_rpBar->SetScrollHdl(_rScrollHandler);
// get some speed into the scrolling...
lcl_setButtonRepeat(*_rpBar);
}
if (_rpBar)
{
_rpBar->SetRange(Range(0, _nRange));
_rpBar->SetVisibleSize(_nVisibleUnits);
_rpBar->SetPageSize(_nVisibleUnits);
_rpBar->SetLineSize(1);
_rpBar->SetThumbPos(_nPosition);
_rpBar->Show();
}
return (bHaveBar != i_needBar);
}
/** returns the number of rows fitting into the given range,
for the given row height. Partially fitting rows are counted, too, if the
respective parameter says so.
*/
TableSize lcl_getRowsFittingInto(tools::Long _nOverallHeight, tools::Long _nRowHeightPixel,
bool _bAcceptPartialRow)
{
return _bAcceptPartialRow ? (_nOverallHeight + (_nRowHeightPixel - 1)) / _nRowHeightPixel
: _nOverallHeight / _nRowHeightPixel;
}
/** returns the number of columns fitting into the given area,
with the first visible column as given. Partially fitting columns are counted, too,
if the respective parameter says so.
*/
TableSize lcl_getColumnsVisibleWithin(const tools::Rectangle& _rArea,
sal_Int32 _nFirstVisibleColumn, const TableControl& _rControl,
bool _bAcceptPartialRow)
{
TableSize visibleColumns = 0;
TableColumnGeometry aColumn(_rControl, _rArea, _nFirstVisibleColumn);
while (aColumn.isValid())
{
if (!_bAcceptPartialRow)
if (aColumn.getRect().Right() > _rArea.Right())
// this column is only partially visible, and this is not allowed
break;
aColumn.moveRight();
++visibleColumns;
}
return visibleColumns;
}
}
tools::Long
TableControl::impl_ni_calculateColumnWidths(sal_Int32 const i_assumeInflexibleColumnsUpToIncluding,
bool const i_assumeVerticalScrollbar,
::std::vector<tools::Long>& o_newColWidthsPixel) const
{
// the available horizontal space
tools::Long gridWidthPixel = GetOutputSizePixel().Width();
ENSURE_OR_RETURN(!!m_pModel,
"TableControl::impl_ni_calculateColumnWidths: not allowed without a model!",
gridWidthPixel);
if (m_pModel->hasRowHeaders() && (gridWidthPixel != 0))
{
gridWidthPixel -= m_nRowHeaderWidthPixel;
}
if (i_assumeVerticalScrollbar
&& (m_pModel->getVerticalScrollbarVisibility() != ScrollbarShowNever))
{
tools::Long nScrollbarMetrics = GetSettings().GetStyleSettings().GetScrollBarSize();
gridWidthPixel -= nScrollbarMetrics;
}
// no need to do anything without columns
TableSize const colCount = m_pModel->getColumnCount();
if (colCount == 0)
return gridWidthPixel;
// collect some meta data for our columns:
// - their current (pixel) metrics
tools::Long accumulatedCurrentWidth = 0;
::std::vector<tools::Long> currentColWidths;
currentColWidths.reserve(colCount);
typedef ::std::vector<::std::pair<tools::Long, long>> ColumnLimits;
ColumnLimits effectiveColumnLimits;
effectiveColumnLimits.reserve(colCount);
tools::Long accumulatedMinWidth = 0;
tools::Long accumulatedMaxWidth = 0;
// - their relative flexibility
::std::vector<::sal_Int32> columnFlexibilities;
columnFlexibilities.reserve(colCount);
tools::Long flexibilityDenominator = 0;
size_t flexibleColumnCount = 0;
for (sal_Int32 col = 0; col < colCount; ++col)
{
PColumnModel const pColumn = m_pModel->getColumnModel(col);
ENSURE_OR_THROW(!!pColumn, "invalid column returned by the model!");
// current width
tools::Long const currentWidth = appFontWidthToPixel(pColumn->getWidth());
currentColWidths.push_back(currentWidth);
// accumulated width
accumulatedCurrentWidth += currentWidth;
// flexibility
::sal_Int32 flexibility = pColumn->getFlexibility();
OSL_ENSURE(flexibility >= 0, "TableControl::impl_ni_calculateColumnWidths: a column's "
"flexibility should be non-negative.");
if ((flexibility < 0) // normalization
|| (!pColumn->isResizable()) // column not resizable => no auto-resize
|| (col
<= i_assumeInflexibleColumnsUpToIncluding) // column shall be treated as inflexible => respect this
)
flexibility = 0;
// min/max width
tools::Long effectiveMin = currentWidth, effectiveMax = currentWidth;
// if the column is not flexible, it will not be asked for min/max, but we assume the current width as limit then
if (flexibility > 0)
{
tools::Long const minWidth = appFontWidthToPixel(pColumn->getMinWidth());
if (minWidth > 0)
effectiveMin = minWidth;
else
effectiveMin = MIN_COLUMN_WIDTH_PIXEL;
tools::Long const maxWidth = appFontWidthToPixel(pColumn->getMaxWidth());
OSL_ENSURE(minWidth <= maxWidth, "TableControl::impl_ni_calculateColumnWidths: "
"pretty undecided 'bout its width limits, this "
"column!");
if ((maxWidth > 0) && (maxWidth >= minWidth))
effectiveMax = maxWidth;
else
effectiveMax = gridWidthPixel; // TODO: any better guess here?
if (effectiveMin == effectiveMax)
// if the min and the max are identical, this implies no flexibility at all
flexibility = 0;
}
columnFlexibilities.push_back(flexibility);
flexibilityDenominator += flexibility;
if (flexibility > 0)
++flexibleColumnCount;
effectiveColumnLimits.emplace_back(effectiveMin, effectiveMax);
accumulatedMinWidth += effectiveMin;
accumulatedMaxWidth += effectiveMax;
}
o_newColWidthsPixel = currentColWidths;
if (flexibilityDenominator == 0)
{
// no column is flexible => don't adjust anything
}
else if (gridWidthPixel > accumulatedCurrentWidth)
{ // we have space to give away ...
tools::Long distributePixel = gridWidthPixel - accumulatedCurrentWidth;
if (gridWidthPixel > accumulatedMaxWidth)
{
// ... but the column's maximal widths are still less than we have
// => set them all to max
for (svt::table::TableSize i = 0; i < colCount; ++i)
{
o_newColWidthsPixel[i] = effectiveColumnLimits[i].second;
}
}
else
{
bool startOver = false;
do
{
startOver = false;
// distribute the remaining space amongst all columns with a positive flexibility
for (size_t i = 0; i < o_newColWidthsPixel.size() && !startOver; ++i)
{
tools::Long const columnFlexibility = columnFlexibilities[i];
if (columnFlexibility == 0)
continue;
tools::Long newColWidth
= currentColWidths[i]
+ columnFlexibility * distributePixel / flexibilityDenominator;
if (newColWidth > effectiveColumnLimits[i].second)
{ // that was too much, we hit the col's maximum
// set the new width to exactly this maximum
newColWidth = effectiveColumnLimits[i].second;
// adjust the flexibility denominator ...
flexibilityDenominator -= columnFlexibility;
columnFlexibilities[i] = 0;
--flexibleColumnCount;
// ... and the remaining width ...
tools::Long const difference = newColWidth - currentColWidths[i];
distributePixel -= difference;
// ... this way, we ensure that the width not taken up by this column is consumed by the other
// flexible ones (if there are some)
// and start over with the first column, since there might be earlier columns which need
// to be recalculated now
startOver = true;
}
o_newColWidthsPixel[i] = newColWidth;
}
} while (startOver);
// are there pixels left (might be caused by rounding errors)?
distributePixel
= gridWidthPixel
- ::std::accumulate(o_newColWidthsPixel.begin(), o_newColWidthsPixel.end(), 0);
while ((distributePixel > 0) && (flexibleColumnCount > 0))
{
// yes => ignore relative flexibilities, and subsequently distribute single pixels to all flexible
// columns which did not yet reach their maximum.
for (size_t i = 0; (i < o_newColWidthsPixel.size()) && (distributePixel > 0); ++i)
{
if (columnFlexibilities[i] == 0)
continue;
OSL_ENSURE(o_newColWidthsPixel[i] <= effectiveColumnLimits[i].second,
"TableControl::impl_ni_calculateColumnWidths: inconsistency!");
if (o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first)
{
columnFlexibilities[i] = 0;
--flexibleColumnCount;
continue;
}
++o_newColWidthsPixel[i];
--distributePixel;
}
}
}
}
else if (gridWidthPixel < accumulatedCurrentWidth)
{ // we need to take away some space from the columns which allow it ...
tools::Long takeAwayPixel = accumulatedCurrentWidth - gridWidthPixel;
if (gridWidthPixel < accumulatedMinWidth)
{
// ... but the column's minimal widths are still more than we have
// => set them all to min
for (svt::table::TableSize i = 0; i < colCount; ++i)
{
o_newColWidthsPixel[i] = effectiveColumnLimits[i].first;
}
}
else
{
bool startOver = false;
do
{
startOver = false;
// take away the space we need from the columns with a positive flexibility
for (size_t i = 0; i < o_newColWidthsPixel.size() && !startOver; ++i)
{
tools::Long const columnFlexibility = columnFlexibilities[i];
if (columnFlexibility == 0)
continue;
tools::Long newColWidth
= currentColWidths[i]
- columnFlexibility * takeAwayPixel / flexibilityDenominator;
if (newColWidth < effectiveColumnLimits[i].first)
{ // that was too much, we hit the col's minimum
// set the new width to exactly this minimum
newColWidth = effectiveColumnLimits[i].first;
// adjust the flexibility denominator ...
flexibilityDenominator -= columnFlexibility;
columnFlexibilities[i] = 0;
--flexibleColumnCount;
// ... and the remaining width ...
tools::Long const difference = currentColWidths[i] - newColWidth;
takeAwayPixel -= difference;
// and start over with the first column, since there might be earlier columns which need
// to be recalculated now
startOver = true;
}
o_newColWidthsPixel[i] = newColWidth;
}
} while (startOver);
// are there pixels left (might be caused by rounding errors)?
takeAwayPixel
= ::std::accumulate(o_newColWidthsPixel.begin(), o_newColWidthsPixel.end(), 0)
- gridWidthPixel;
while ((takeAwayPixel > 0) && (flexibleColumnCount > 0))
{
// yes => ignore relative flexibilities, and subsequently take away pixels from all flexible
// columns which did not yet reach their minimum.
for (size_t i = 0; (i < o_newColWidthsPixel.size()) && (takeAwayPixel > 0); ++i)
{
if (columnFlexibilities[i] == 0)
continue;
OSL_ENSURE(o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first,
"TableControl::impl_ni_calculateColumnWidths: inconsistency!");
if (o_newColWidthsPixel[i] <= effectiveColumnLimits[i].first)
{
columnFlexibilities[i] = 0;
--flexibleColumnCount;
continue;
}
--o_newColWidthsPixel[i];
--takeAwayPixel;
}
}
}
}
return gridWidthPixel;
}
void TableControl::impl_ni_relayout(sal_Int32 const i_assumeInflexibleColumnsUpToIncluding)
{
ENSURE_OR_RETURN_VOID(!m_bUpdatingColWidths,
"TableControl::impl_ni_relayout: recursive call detected!");
m_aColumnWidths.resize(0);
if (!m_pModel)
return;
::comphelper::FlagRestorationGuard const aWidthUpdateFlag(m_bUpdatingColWidths, true);
SuppressCursor aHideCursor(*this);
// layouting steps:
// 1. adjust column widths, leaving space for a vertical scrollbar
// 2. determine need for a vertical scrollbar
// - V-YES: all fine, result from 1. is still valid
// - V-NO: result from 1. is still under consideration
// 3. determine need for a horizontal scrollbar
// - H-NO: all fine, result from 2. is still valid
// - H-YES: reconsider need for a vertical scrollbar, if result of 2. was V-NO
// - V-YES: all fine, result from 1. is still valid
// - V-NO: redistribute the remaining space (if any) amongst all columns which allow it
::std::vector<tools::Long> newWidthsPixel;
tools::Long gridWidthPixel = impl_ni_calculateColumnWidths(
i_assumeInflexibleColumnsUpToIncluding, true, newWidthsPixel);
// the width/height of a scrollbar, needed several times below
tools::Long const nScrollbarMetrics = GetSettings().GetStyleSettings().GetScrollBarSize();
// determine the playground for the data cells (excluding headers)
// TODO: what if the control is smaller than needed for the headers/scrollbars?
tools::Rectangle aDataCellPlayground(Point(0, 0), GetOutputSizePixel());
aDataCellPlayground.SetLeft(m_nRowHeaderWidthPixel);
aDataCellPlayground.SetTop(m_nColHeaderHeightPixel);
OSL_ENSURE((m_nRowCount == m_pModel->getRowCount())
&& (m_nColumnCount == m_pModel->getColumnCount()),
"TableControl::impl_ni_relayout: how is this expected to work with invalid data?");
tools::Long const nAllColumnsWidth
= ::std::accumulate(newWidthsPixel.begin(), newWidthsPixel.end(), 0);
ScrollbarVisibility const eVertScrollbar = m_pModel->getVerticalScrollbarVisibility();
ScrollbarVisibility const eHorzScrollbar = m_pModel->getHorizontalScrollbarVisibility();
// do we need a vertical scrollbar?
bool bNeedVerticalScrollbar
= lcl_determineScrollbarNeed(m_nTopRow, eVertScrollbar, aDataCellPlayground.GetHeight(),
m_nRowHeightPixel * m_nRowCount);
bool bFirstRoundVScrollNeed = false;
if (bNeedVerticalScrollbar)
{
aDataCellPlayground.AdjustRight(-nScrollbarMetrics);
bFirstRoundVScrollNeed = true;
}
// do we need a horizontal scrollbar?
bool const bNeedHorizontalScrollbar = lcl_determineScrollbarNeed(
m_nLeftColumn, eHorzScrollbar, aDataCellPlayground.GetWidth(), nAllColumnsWidth);
if (bNeedHorizontalScrollbar)
{
aDataCellPlayground.AdjustBottom(-nScrollbarMetrics);
// now that we just found that we need a horizontal scrollbar,
// the need for a vertical one may have changed, since the horizontal
// SB might just occupy enough space so that not all rows do fit
// anymore
if (!bFirstRoundVScrollNeed)
{
bNeedVerticalScrollbar = lcl_determineScrollbarNeed(m_nTopRow, eVertScrollbar,
aDataCellPlayground.GetHeight(),
m_nRowHeightPixel * m_nRowCount);
if (bNeedVerticalScrollbar)
{
aDataCellPlayground.AdjustRight(-nScrollbarMetrics);
}
}
}
// the initial call to impl_ni_calculateColumnWidths assumed that we need a vertical scrollbar. If, by now,
// we know that this is not the case, re-calculate the column widths.
if (!bNeedVerticalScrollbar)
gridWidthPixel = impl_ni_calculateColumnWidths(i_assumeInflexibleColumnsUpToIncluding,
false, newWidthsPixel);
// update the column objects with the new widths we finally calculated
TableSize const colCount = m_pModel->getColumnCount();
m_aColumnWidths.reserve(colCount);
tools::Long accumulatedWidthPixel = m_nRowHeaderWidthPixel;
bool anyColumnWidthChanged = false;
for (sal_Int32 col = 0; col < colCount; ++col)
{
const tools::Long columnStart = accumulatedWidthPixel;
const tools::Long columnEnd = columnStart + newWidthsPixel[col];
m_aColumnWidths.emplace_back(columnStart, columnEnd);
accumulatedWidthPixel = columnEnd;
// and don't forget to forward this to the column models
PColumnModel const pColumn = m_pModel->getColumnModel(col);
ENSURE_OR_THROW(!!pColumn, "invalid column returned by the model!");
tools::Long const oldColumnWidthAppFont = pColumn->getWidth();
tools::Long const newColumnWidthAppFont = pixelWidthToAppFont(newWidthsPixel[col]);
pColumn->setWidth(newColumnWidthAppFont);
anyColumnWidthChanged |= (oldColumnWidthAppFont != newColumnWidthAppFont);
}
// if the column widths changed, ensure everything is repainted
if (anyColumnWidthChanged)
invalidate(TableArea::All);
// if the column resizing happened to leave some space at the right, but there are columns
// scrolled out to the left, scroll them in
while ((m_nLeftColumn > 0)
&& (accumulatedWidthPixel - m_aColumnWidths[m_nLeftColumn - 1].getStart()
<= gridWidthPixel))
{
--m_nLeftColumn;
}
// now adjust the column metrics, since they currently ignore the horizontal scroll position
if (m_nLeftColumn > 0)
{
const tools::Long offsetPixel
= m_aColumnWidths[0].getStart() - m_aColumnWidths[m_nLeftColumn].getStart();
for (auto& columnWidth : m_aColumnWidths)
{
columnWidth.move(offsetPixel);
}
}
// show or hide the scrollbars as needed, and position the data window
impl_ni_positionChildWindows(aDataCellPlayground, bNeedVerticalScrollbar,
bNeedHorizontalScrollbar);
}
void TableControl::impl_ni_positionChildWindows(tools::Rectangle const& i_dataCellPlayground,
bool const i_verticalScrollbar,
bool const i_horizontalScrollbar)
{
tools::Long const nScrollbarMetrics = GetSettings().GetStyleSettings().GetScrollBarSize();
// create or destroy the vertical scrollbar, as needed
lcl_updateScrollbar(
*this, m_pVScroll, i_verticalScrollbar,
lcl_getRowsFittingInto(i_dataCellPlayground.GetHeight(), m_nRowHeightPixel, false),
// visible units
m_nTopRow, // current position
m_nRowCount, // range
false, // vertical
LINK(this, TableControl, OnScroll) // scroll handler
);
// position it
if (m_pVScroll)
{
tools::Rectangle aScrollbarArea(Point(i_dataCellPlayground.Right() + 1, 0),
Size(nScrollbarMetrics, i_dataCellPlayground.Bottom() + 1));
m_pVScroll->SetPosSizePixel(aScrollbarArea.TopLeft(), aScrollbarArea.GetSize());
}
// create or destroy the horizontal scrollbar, as needed
lcl_updateScrollbar(
*this, m_pHScroll, i_horizontalScrollbar,
lcl_getColumnsVisibleWithin(i_dataCellPlayground, m_nLeftColumn, *this, false),
// visible units
m_nLeftColumn, // current position
m_nColumnCount, // range
true, // horizontal
LINK(this, TableControl, OnScroll) // scroll handler
);
// position it
if (m_pHScroll)
{
TableSize const nVisibleUnits
= lcl_getColumnsVisibleWithin(i_dataCellPlayground, m_nLeftColumn, *this, false);
TableMetrics const nRange = m_nColumnCount;
if (m_nLeftColumn + nVisibleUnits == nRange - 1)
{
if (m_aColumnWidths[nRange - 1].getStart() - m_aColumnWidths[m_nLeftColumn].getEnd()
+ m_aColumnWidths[nRange - 1].getWidth()
> i_dataCellPlayground.GetWidth())
{
m_pHScroll->SetVisibleSize(nVisibleUnits - 1);
m_pHScroll->SetPageSize(nVisibleUnits - 1);
}
}
tools::Rectangle aScrollbarArea(Point(0, i_dataCellPlayground.Bottom() + 1),
Size(i_dataCellPlayground.Right() + 1, nScrollbarMetrics));
m_pHScroll->SetPosSizePixel(aScrollbarArea.TopLeft(), aScrollbarArea.GetSize());
}
// the corner window connecting the two scrollbars in the lower right corner
bool bHaveScrollCorner = nullptr != m_pScrollCorner;
bool bNeedScrollCorner = (nullptr != m_pHScroll) && (nullptr != m_pVScroll);
if (bHaveScrollCorner && !bNeedScrollCorner)
{
m_pScrollCorner.disposeAndClear();
}
else if (!bHaveScrollCorner && bNeedScrollCorner)
{
m_pScrollCorner = VclPtr<ScrollBarBox>::Create(this);
m_pScrollCorner->SetSizePixel(Size(nScrollbarMetrics, nScrollbarMetrics));
m_pScrollCorner->SetPosPixel(
Point(i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1));
m_pScrollCorner->Show();
}
else if (bHaveScrollCorner && bNeedScrollCorner)
{
m_pScrollCorner->SetPosPixel(
Point(i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1));
m_pScrollCorner->Show();
}
// resize the data window
m_pDataWindow->SetSizePixel(Size(i_dataCellPlayground.GetWidth() + m_nRowHeaderWidthPixel,
i_dataCellPlayground.GetHeight() + m_nColHeaderHeightPixel));
}
void TableControl::onResize()
{
impl_ni_relayout();
checkCursorPosition();
}
void TableControl::Select()
{
ImplCallEventListenersAndHandler(VclEventId::TableRowSelect, nullptr);
commitAccessibleEvent(AccessibleEventId::SELECTION_CHANGED);
commitTableEvent(AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, Any(), Any());
// TODO: why do we notify this when the *selection* changed? Shouldn't we find a better place for this,
// actually, when the active descendant, i.e. the current cell, *really* changed?
}
void TableControl::doPaintContent(vcl::RenderContext& rRenderContext,
const tools::Rectangle& _rUpdateRect)
{
if (!GetModel())
return;
PTableRenderer pRenderer = GetModel()->getRenderer();
DBG_ASSERT(!!pRenderer, "TableDataWindow::doPaintContent: invalid renderer!");
if (!pRenderer)
return;
// our current style settings, to be passed to the renderer
const StyleSettings& rStyle = rRenderContext.GetSettings().GetStyleSettings();
m_nRowCount = m_pModel->getRowCount();
// the area occupied by all (at least partially) visible cells, including
// headers
tools::Rectangle const aAllCellsWithHeaders(impl_getAllVisibleCellsArea());
// draw the header column area
if (m_pModel->hasColumnHeaders())
{
TableRowGeometry const aHeaderRow(
*this, tools::Rectangle(Point(0, 0), aAllCellsWithHeaders.BottomRight()),
ROW_COL_HEADERS);
tools::Rectangle const aColRect(aHeaderRow.getRect());
pRenderer->PaintHeaderArea(rRenderContext, aColRect, true, false, rStyle);
// Note that strictly, aHeaderRow.getRect() also contains the intersection between column
// and row header area. However, below we go to paint this intersection, again,
// so this hopefully doesn't hurt if we already paint it here.
for (TableCellGeometry aCell(aHeaderRow, m_nLeftColumn); aCell.isValid(); aCell.moveRight())
{
if (_rUpdateRect.GetIntersection(aCell.getRect()).IsEmpty())
continue;
pRenderer->PaintColumnHeader(aCell.getColumn(), rRenderContext, aCell.getRect(),
rStyle);
}
}
// the area occupied by the row header, if any
tools::Rectangle aRowHeaderArea;
if (m_pModel->hasRowHeaders())
{
aRowHeaderArea = aAllCellsWithHeaders;
aRowHeaderArea.SetRight(m_nRowHeaderWidthPixel - 1);
TableSize const nVisibleRows = impl_getVisibleRows(true);
TableSize nActualRows = nVisibleRows;
if (m_nTopRow + nActualRows > m_nRowCount)
nActualRows = m_nRowCount - m_nTopRow;
aRowHeaderArea.SetBottom(m_nColHeaderHeightPixel + m_nRowHeightPixel * nActualRows - 1);
pRenderer->PaintHeaderArea(rRenderContext, aRowHeaderArea, false, true, rStyle);
// Note that strictly, aRowHeaderArea also contains the intersection between column
// and row header area. However, below we go to paint this intersection, again,
// so this hopefully doesn't hurt if we already paint it here.
if (m_pModel->hasColumnHeaders())
{
TableCellGeometry const aIntersection(
*this, tools::Rectangle(Point(0, 0), aAllCellsWithHeaders.BottomRight()),
COL_ROW_HEADERS, ROW_COL_HEADERS);
tools::Rectangle const aInters(aIntersection.getRect());
pRenderer->PaintHeaderArea(rRenderContext, aInters, true, true, rStyle);
}
}
// draw the table content row by row
TableSize colCount = GetModel()->getColumnCount();
// paint all rows
tools::Rectangle const aAllDataCellsArea(impl_getAllVisibleDataCellArea());
for (TableRowGeometry aRowIterator(*this, aAllCellsWithHeaders, getTopRow());
aRowIterator.isValid(); aRowIterator.moveDown())
{
if (_rUpdateRect.GetIntersection(aRowIterator.getRect()).IsEmpty())
continue;
bool const isControlFocused = HasControlFocus();
bool const isSelectedRow = IsRowSelected(aRowIterator.getRow());
tools::Rectangle const aRect = aRowIterator.getRect().GetIntersection(aAllDataCellsArea);
// give the renderer a chance to prepare the row
pRenderer->PrepareRow(aRowIterator.getRow(), isControlFocused, isSelectedRow,
rRenderContext, aRect, rStyle);
// paint the row header
if (m_pModel->hasRowHeaders())
{
const tools::Rectangle aCurrentRowHeader(
aRowHeaderArea.GetIntersection(aRowIterator.getRect()));
pRenderer->PaintRowHeader(rRenderContext, aCurrentRowHeader, rStyle);
}
if (!colCount)
continue;
// paint all cells in this row
for (TableCellGeometry aCell(aRowIterator, m_nLeftColumn); aCell.isValid();
aCell.moveRight())
{
pRenderer->PaintCell(aCell.getColumn(), isSelectedRow, isControlFocused, rRenderContext,
aCell.getRect(), rStyle);
}
}
}
void TableControl::hideCursor()
{
if (++m_nCursorHidden == 1)
impl_ni_doSwitchCursor(false);
}
void TableControl::showCursor()
{
DBG_ASSERT(m_nCursorHidden > 0, "TableControl::showCursor: cursor not hidden!");
if (--m_nCursorHidden == 0)
impl_ni_doSwitchCursor(true);
}
bool TableControl::dispatchAction(TableControlAction _eAction)
{
bool bSuccess = false;
bool selectionChanged = false;
switch (_eAction)
{
case TableControlAction::cursorDown:
if (m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
{
//if other rows already selected, deselect them
if (!m_aSelectedRows.empty())
{
invalidateSelectedRows();
m_aSelectedRows.clear();
}
if (m_nCurRow < m_nRowCount - 1)
{
++m_nCurRow;
m_aSelectedRows.push_back(m_nCurRow);
}
else
m_aSelectedRows.push_back(m_nCurRow);
invalidateRow(m_nCurRow);
ensureVisible(m_nCurColumn, m_nCurRow);
selectionChanged = true;
bSuccess = true;
}
else
{
if (m_nCurRow < m_nRowCount - 1)
bSuccess = GoToCell(m_nCurColumn, m_nCurRow + 1);
}
break;
case TableControlAction::cursorUp:
if (m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
{
if (!m_aSelectedRows.empty())
{
invalidateSelectedRows();
m_aSelectedRows.clear();
}
if (m_nCurRow > 0)
{
--m_nCurRow;
m_aSelectedRows.push_back(m_nCurRow);
invalidateRow(m_nCurRow);
}
else
{
m_aSelectedRows.push_back(m_nCurRow);
invalidateRow(m_nCurRow);
}
ensureVisible(m_nCurColumn, m_nCurRow);
selectionChanged = true;
bSuccess = true;
}
else
{
if (m_nCurRow > 0)
bSuccess = GoToCell(m_nCurColumn, m_nCurRow - 1);
}
break;
case TableControlAction::cursorLeft:
if (m_nCurColumn > 0)
bSuccess = GoToCell(m_nCurColumn - 1, m_nCurRow);
else if ((m_nCurColumn == 0) && (m_nCurRow > 0))
bSuccess = GoToCell(m_nColumnCount - 1, m_nCurRow - 1);
break;
case TableControlAction::cursorRight:
if (m_nCurColumn < m_nColumnCount - 1)
bSuccess = GoToCell(m_nCurColumn + 1, m_nCurRow);
else if ((m_nCurColumn == m_nColumnCount - 1) && (m_nCurRow < m_nRowCount - 1))
bSuccess = GoToCell(0, m_nCurRow + 1);
break;
case TableControlAction::cursorToLineStart:
bSuccess = GoToCell(0, m_nCurRow);
break;
case TableControlAction::cursorToLineEnd:
bSuccess = GoToCell(m_nColumnCount - 1, m_nCurRow);
break;
case TableControlAction::cursorToFirstLine:
bSuccess = GoToCell(m_nCurColumn, 0);
break;
case TableControlAction::cursorToLastLine:
bSuccess = GoToCell(m_nCurColumn, m_nRowCount - 1);
break;
case TableControlAction::cursorPageUp:
{
sal_Int32 nNewRow = ::std::max(sal_Int32(0), m_nCurRow - impl_getVisibleRows(false));
bSuccess = GoToCell(m_nCurColumn, nNewRow);
}
break;
case TableControlAction::cursorPageDown:
{
sal_Int32 nNewRow = ::std::min(m_nRowCount - 1, m_nCurRow + impl_getVisibleRows(false));
bSuccess = GoToCell(m_nCurColumn, nNewRow);
}
break;
case TableControlAction::cursorTopLeft:
bSuccess = GoToCell(0, 0);
break;
case TableControlAction::cursorBottomRight:
bSuccess = GoToCell(m_nColumnCount - 1, m_nRowCount - 1);
break;
case TableControlAction::cursorSelectRow:
{
if (m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
return false;
//pos is the position of the current row in the vector of selected rows, if current row is selected
int pos = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
//if current row is selected, it should be deselected, when ALT+SPACE are pressed
if (pos > -1)
{
m_aSelectedRows.erase(m_aSelectedRows.begin() + pos);
if (m_aSelectedRows.empty() && m_nAnchor != -1)
m_nAnchor = -1;
}
//else select the row->put it in the vector
else
m_aSelectedRows.push_back(m_nCurRow);
invalidateRow(m_nCurRow);
selectionChanged = true;
bSuccess = true;
}
break;
case TableControlAction::cursorSelectRowUp:
{
if (m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
return false;
else if (m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
{
//if there are other selected rows, deselect them
return false;
}
else
{
//there are other selected rows
if (!m_aSelectedRows.empty())
{
//the anchor wasn't set -> a region is not selected, that's why clear all selection
//and select the current row
if (m_nAnchor == -1)
{
invalidateSelectedRows();
m_aSelectedRows.clear();
m_aSelectedRows.push_back(m_nCurRow);
invalidateRow(m_nCurRow);
}
else
{
//a region is already selected, prevRow is last selected row and the row above - nextRow - should be selected
int prevRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
int nextRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow - 1);
if (prevRow > -1)
{
//if m_nCurRow isn't the upper one, can move up, otherwise not
if (m_nCurRow > 0)
m_nCurRow--;
else
return true;
//if nextRow already selected, deselect it, otherwise select it
if (nextRow > -1 && m_aSelectedRows[nextRow] == m_nCurRow)
{
m_aSelectedRows.erase(m_aSelectedRows.begin() + prevRow);
invalidateRow(m_nCurRow + 1);
}
else
{
m_aSelectedRows.push_back(m_nCurRow);
invalidateRow(m_nCurRow);
}
}
else
{
if (m_nCurRow > 0)
{
m_aSelectedRows.push_back(m_nCurRow);
m_nCurRow--;
m_aSelectedRows.push_back(m_nCurRow);
invalidateSelectedRegion(m_nCurRow + 1, m_nCurRow);
}
}
}
}
else
{
//if nothing is selected and the current row isn't the upper one
//select the current and one row above
//otherwise select only the upper row
if (m_nCurRow > 0)
{
m_aSelectedRows.push_back(m_nCurRow);
m_nCurRow--;
m_aSelectedRows.push_back(m_nCurRow);
invalidateSelectedRegion(m_nCurRow + 1, m_nCurRow);
}
else
{
m_aSelectedRows.push_back(m_nCurRow);
invalidateRow(m_nCurRow);
}
}
m_pSelEngine->SetAnchor(true);
m_nAnchor = m_nCurRow;
ensureVisible(m_nCurColumn, m_nCurRow);
selectionChanged = true;
bSuccess = true;
}
}
break;
case TableControlAction::cursorSelectRowDown:
{
if (m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
bSuccess = false;
else if (m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
{
bSuccess = false;
}
else
{
if (!m_aSelectedRows.empty())
{
//the anchor wasn't set -> a region is not selected, that's why clear all selection
//and select the current row
if (m_nAnchor == -1)
{
invalidateSelectedRows();
m_aSelectedRows.clear();
m_aSelectedRows.push_back(m_nCurRow);
invalidateRow(m_nCurRow);
}
else
{
//a region is already selected, prevRow is last selected row and the row beneath - nextRow - should be selected
int prevRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
int nextRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow + 1);
if (prevRow > -1)
{
//if m_nCurRow isn't the last one, can move down, otherwise not
if (m_nCurRow < m_nRowCount - 1)
m_nCurRow++;
else
return true;
//if next row already selected, deselect it, otherwise select it
if (nextRow > -1 && m_aSelectedRows[nextRow] == m_nCurRow)
{
m_aSelectedRows.erase(m_aSelectedRows.begin() + prevRow);
invalidateRow(m_nCurRow - 1);
}
else
{
m_aSelectedRows.push_back(m_nCurRow);
invalidateRow(m_nCurRow);
}
}
else
{
if (m_nCurRow < m_nRowCount - 1)
{
m_aSelectedRows.push_back(m_nCurRow);
m_nCurRow++;
m_aSelectedRows.push_back(m_nCurRow);
invalidateSelectedRegion(m_nCurRow - 1, m_nCurRow);
}
}
}
}
else
{
//there wasn't any selection, select current and row beneath, otherwise only row beneath
if (m_nCurRow < m_nRowCount - 1)
{
m_aSelectedRows.push_back(m_nCurRow);
m_nCurRow++;
m_aSelectedRows.push_back(m_nCurRow);
invalidateSelectedRegion(m_nCurRow - 1, m_nCurRow);
}
else
{
m_aSelectedRows.push_back(m_nCurRow);
invalidateRow(m_nCurRow);
}
}
m_pSelEngine->SetAnchor(true);
m_nAnchor = m_nCurRow;
ensureVisible(m_nCurColumn, m_nCurRow);
selectionChanged = true;
bSuccess = true;
}
}
break;
case TableControlAction::cursorSelectRowAreaTop:
{
if (m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
bSuccess = false;
else if (m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
bSuccess = false;
else
{
//select the region between the current and the upper row
sal_Int32 iter = m_nCurRow;
invalidateSelectedRegion(m_nCurRow, 0);
//put the rows in vector
while (iter >= 0)
{
if (!IsRowSelected(iter))
m_aSelectedRows.push_back(iter);
--iter;
}
m_nCurRow = 0;
m_nAnchor = m_nCurRow;
m_pSelEngine->SetAnchor(true);
ensureVisible(m_nCurColumn, 0);
selectionChanged = true;
bSuccess = true;
}
}
break;
case TableControlAction::cursorSelectRowAreaBottom:
{
if (m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
return false;
else if (m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
return false;
//select the region between the current and the last row
sal_Int32 iter = m_nCurRow;
invalidateSelectedRegion(m_nCurRow, m_nRowCount - 1);
//put the rows in the vector
while (iter <= m_nRowCount)
{
if (!IsRowSelected(iter))
m_aSelectedRows.push_back(iter);
++iter;
}
m_nCurRow = m_nRowCount - 1;
m_nAnchor = m_nCurRow;
m_pSelEngine->SetAnchor(true);
ensureVisible(m_nCurColumn, m_nRowCount - 1);
selectionChanged = true;
bSuccess = true;
}
break;
default:
OSL_FAIL("TableControl::dispatchAction: unsupported action!");
break;
}
if (bSuccess && selectionChanged)
{
Select();
}
return bSuccess;
}
void TableControl::impl_ni_doSwitchCursor(bool _bShow)
{
PTableRenderer pRenderer = m_pModel ? m_pModel->getRenderer() : PTableRenderer();
if (pRenderer)
{
tools::Rectangle aCellRect = calcCellRect(m_nCurRow, m_nCurColumn);
if (_bShow)
pRenderer->ShowCellCursor(*m_pDataWindow, aCellRect);
else
pRenderer->HideCellCursor(*m_pDataWindow);
}
}
sal_Int32 TableControl::getRowAtPoint(const Point& rPoint) const
{
return impl_getRowForAbscissa(rPoint.Y());
}
sal_Int32 TableControl::getColAtPoint(const Point& rPoint) const
{
return impl_getColumnForOrdinate(rPoint.X());
}
TableCell TableControl::hitTest(Point const& i_point) const
{
TableCell aCell(getColAtPoint(i_point), getRowAtPoint(i_point));
if (aCell.nColumn > COL_ROW_HEADERS)
{
PColumnModel const pColumn = m_pModel->getColumnModel(aCell.nColumn);
MutableColumnMetrics const& rColInfo(m_aColumnWidths[aCell.nColumn]);
if ((rColInfo.getEnd() - 3 <= i_point.X()) && (rColInfo.getEnd() >= i_point.X())
&& pColumn->isResizable())
{
aCell.eArea = ColumnDivider;
}
}
return aCell;
}
ColumnMetrics TableControl::getColumnMetrics(sal_Int32 const i_column) const
{
ENSURE_OR_RETURN((i_column >= 0) && (i_column < m_pModel->getColumnCount()),
"TableControl::getColumnMetrics: illegal column index!", ColumnMetrics());
return m_aColumnWidths[i_column];
}
PTableModel TableControl::GetModel() const { return m_pModel; }
sal_Int32 TableControl::GetCurrentColumn() const { return m_nCurColumn; }
sal_Int32 TableControl::GetCurrentRow() const { return m_nCurRow; }
::Size TableControl::getTableSizePixel() const { return m_pDataWindow->GetOutputSizePixel(); }
void TableControl::setPointer(PointerStyle i_pointer) { m_pDataWindow->SetPointer(i_pointer); }
void TableControl::captureMouse() { m_pDataWindow->CaptureMouse(); }
void TableControl::releaseMouse() { m_pDataWindow->ReleaseMouse(); }
void TableControl::invalidate(TableArea const i_what)
{
switch (i_what)
{
case TableArea::ColumnHeaders:
m_pDataWindow->Invalidate(calcHeaderRect(true));
break;
case TableArea::RowHeaders:
m_pDataWindow->Invalidate(calcHeaderRect(false));
break;
case TableArea::All:
m_pDataWindow->Invalidate();
m_pDataWindow->GetParent()->Invalidate(InvalidateFlags::Transparent);
break;
}
}
tools::Long TableControl::pixelWidthToAppFont(tools::Long const i_pixels) const
{
return m_pDataWindow->PixelToLogic(Size(i_pixels, 0), MapMode(MapUnit::MapAppFont)).Width();
}
tools::Long TableControl::appFontWidthToPixel(tools::Long const i_appFontUnits) const
{
return m_pDataWindow->LogicToPixel(Size(i_appFontUnits, 0), MapMode(MapUnit::MapAppFont))
.Width();
}
void TableControl::hideTracking() { m_pDataWindow->HideTracking(); }
void TableControl::showTracking(tools::Rectangle const& i_location, ShowTrackFlags const i_flags)
{
m_pDataWindow->ShowTracking(i_location, i_flags);
}
void TableControl::activateCell(sal_Int32 const i_col, sal_Int32 const i_row)
{
GoToCell(i_col, i_row);
}
void TableControl::invalidateSelectedRegion(sal_Int32 _nPrevRow, sal_Int32 _nCurRow)
{
// get the visible area of the table control and set the Left and right border of the region to be repainted
tools::Rectangle const aAllCells(impl_getAllVisibleCellsArea());
tools::Rectangle aInvalidateRect;
aInvalidateRect.SetLeft(aAllCells.Left());
aInvalidateRect.SetRight(aAllCells.Right());
// if only one row is selected
if (_nPrevRow == _nCurRow)
{
tools::Rectangle aCellRect = calcCellRect(_nCurRow, m_nCurColumn);
aInvalidateRect.SetTop(aCellRect.Top());
aInvalidateRect.SetBottom(aCellRect.Bottom());
}
//if the region is above the current row
else if (_nPrevRow < _nCurRow)
{
tools::Rectangle aCellRect = calcCellRect(_nPrevRow, m_nCurColumn);
aInvalidateRect.SetTop(aCellRect.Top());
aCellRect = calcCellRect(_nCurRow, m_nCurColumn);
aInvalidateRect.SetBottom(aCellRect.Bottom());
}
//if the region is beneath the current row
else
{
tools::Rectangle aCellRect = calcCellRect(_nCurRow, m_nCurColumn);
aInvalidateRect.SetTop(aCellRect.Top());
aCellRect = calcCellRect(_nPrevRow, m_nCurColumn);
aInvalidateRect.SetBottom(aCellRect.Bottom());
}
invalidateRect(aInvalidateRect);
}
void TableControl::invalidateRect(const tools::Rectangle& rInvalidateRect)
{
m_pDataWindow->Invalidate(rInvalidateRect, m_pDataWindow->GetControlBackground().IsTransparent()
? InvalidateFlags::Transparent
: InvalidateFlags::NONE);
}
void TableControl::invalidateSelectedRows()
{
for (auto const& selectedRow : m_aSelectedRows)
{
invalidateRow(selectedRow);
}
}
void TableControl::invalidateRowRange(sal_Int32 const i_firstRow, sal_Int32 const i_lastRow)
{
sal_Int32 const firstRow = i_firstRow < m_nTopRow ? m_nTopRow : i_firstRow;
sal_Int32 const lastVisibleRow = m_nTopRow + impl_getVisibleRows(true) - 1;
sal_Int32 const lastRow
= ((i_lastRow == ROW_INVALID) || (i_lastRow > lastVisibleRow)) ? lastVisibleRow : i_lastRow;
tools::Rectangle aInvalidateRect;
tools::Rectangle const aVisibleCellsArea(impl_getAllVisibleCellsArea());
TableRowGeometry aRow(*this, aVisibleCellsArea, firstRow, true);
while (aRow.isValid() && (aRow.getRow() <= lastRow))
{
aInvalidateRect.Union(aRow.getRect());
aRow.moveDown();
}
if (i_lastRow == ROW_INVALID)
aInvalidateRect.SetBottom(m_pDataWindow->GetOutputSizePixel().Height());
invalidateRect(aInvalidateRect);
}
void TableControl::checkCursorPosition()
{
TableSize nVisibleRows = impl_getVisibleRows(true);
TableSize nVisibleCols = impl_getVisibleColumns(true);
if ((m_nTopRow + nVisibleRows > m_nRowCount) && (m_nRowCount >= nVisibleRows))
{
--m_nTopRow;
}
else
{
m_nTopRow = 0;
}
if ((m_nLeftColumn + nVisibleCols > m_nColumnCount) && (m_nColumnCount >= nVisibleCols))
{
--m_nLeftColumn;
}
else
{
m_nLeftColumn = 0;
}
m_pDataWindow->Invalidate();
}
TableSize TableControl::impl_getVisibleRows(bool _bAcceptPartialRow) const
{
DBG_ASSERT(m_pDataWindow, "TableControl::impl_getVisibleRows: no data window!");
return lcl_getRowsFittingInto(m_pDataWindow->GetOutputSizePixel().Height()
- m_nColHeaderHeightPixel,
m_nRowHeightPixel, _bAcceptPartialRow);
}
TableSize TableControl::impl_getVisibleColumns(bool _bAcceptPartialCol) const
{
DBG_ASSERT(m_pDataWindow, "TableControl::impl_getVisibleColumns: no data window!");
return lcl_getColumnsVisibleWithin(
tools::Rectangle(Point(0, 0), m_pDataWindow->GetOutputSizePixel()), m_nLeftColumn, *this,
_bAcceptPartialCol);
}
bool TableControl::GoToCell(sal_Int32 _nColumn, sal_Int32 _nRow)
{
// TODO: give veto listeners a chance
if ((_nColumn < 0) || (_nColumn >= m_nColumnCount) || (_nRow < 0) || (_nRow >= m_nRowCount))
{
OSL_ENSURE(false, "TableControl::GoToCell: invalid row or column index!");
return false;
}
SuppressCursor aHideCursor(*this);
m_nCurColumn = _nColumn;
m_nCurRow = _nRow;
// ensure that the new cell is visible
ensureVisible(m_nCurColumn, m_nCurRow);
return true;
}
void TableControl::ensureVisible(sal_Int32 _nColumn, sal_Int32 _nRow)
{
DBG_ASSERT((_nColumn >= 0) && (_nColumn < m_nColumnCount) && (_nRow >= 0)
&& (_nRow < m_nRowCount),
"TableControl::ensureVisible: invalid coordinates!");
SuppressCursor aHideCursor(*this);
if (_nColumn < m_nLeftColumn)
impl_scrollColumns(_nColumn - m_nLeftColumn);
else
{
TableSize nVisibleColumns = impl_getVisibleColumns(false /*bAcceptPartialVisibility*/);
if (_nColumn > m_nLeftColumn + nVisibleColumns - 1)
{
impl_scrollColumns(_nColumn - (m_nLeftColumn + nVisibleColumns - 1));
// TODO: since not all columns have the same width, this might in theory result
// in the column still not being visible.
}
}
if (_nRow < m_nTopRow)
impl_scrollRows(_nRow - m_nTopRow);
else
{
TableSize nVisibleRows = impl_getVisibleRows(false /*_bAcceptPartialVisibility*/);
if (_nRow > m_nTopRow + nVisibleRows - 1)
impl_scrollRows(_nRow - (m_nTopRow + nVisibleRows - 1));
}
}
OUString TableControl::GetAccessibleCellText(sal_Int32 const i_row, sal_Int32 const i_col)
{
Any aCellValue;
m_pModel->getCellContent(i_col, i_row, aCellValue);
OUString sCellStringContent;
m_pModel->getRenderer()->GetFormattedCellString(aCellValue, sCellStringContent);
return sCellStringContent;
}
TableSize TableControl::impl_ni_ScrollRows(TableSize _nRowDelta)
{
// compute new top row
sal_Int32 nNewTopRow = ::std::max(::std::min(static_cast<sal_Int32>(m_nTopRow + _nRowDelta),
static_cast<sal_Int32>(m_nRowCount - 1)),
sal_Int32(0));
sal_Int32 nOldTopRow = m_nTopRow;
m_nTopRow = nNewTopRow;
// if updates are enabled currently, scroll the viewport
if (m_nTopRow != nOldTopRow)
{
SuppressCursor aHideCursor(*this);
// TODO: call an onStartScroll at our listener (or better an own onStartScroll,
// which hides the cursor and then calls the listener)
// Same for onEndScroll
// scroll the view port, if possible
tools::Long nPixelDelta = m_nRowHeightPixel * (m_nTopRow - nOldTopRow);
tools::Rectangle aDataArea(Point(0, m_nColHeaderHeightPixel),
m_pDataWindow->GetOutputSizePixel());
if (m_pDataWindow->GetBackground().IsScrollable()
&& std::abs(nPixelDelta) < aDataArea.GetHeight())
{
m_pDataWindow->Scroll(0, static_cast<tools::Long>(-nPixelDelta), aDataArea,
ScrollFlags::Clip | ScrollFlags::Update | ScrollFlags::Children);
}
else
{
m_pDataWindow->Invalidate(InvalidateFlags::Update);
m_pDataWindow->GetParent()->Invalidate(InvalidateFlags::Transparent);
}
// update the position at the vertical scrollbar
if (m_pVScroll != nullptr)
m_pVScroll->SetThumbPos(m_nTopRow);
}
// The scroll bar availability might change when we scrolled.
// For instance, imagine a view with 10 rows, if which 5 fit into the window, numbered 1 to 10.
// Now let
// - the user scroll to row number 6, so the last 5 rows are visible
// - somebody remove the last 4 rows
// - the user scroll to row number 5 being the top row, so the last two rows are visible
// - somebody remove row number 6
// - the user scroll to row number 1
// => in this case, the need for the scrollbar vanishes immediately.
if (m_nTopRow == 0)
PostUserEvent(LINK(this, TableControl, OnUpdateScrollbars));
return static_cast<TableSize>(m_nTopRow - nOldTopRow);
}
TableSize TableControl::impl_scrollRows(TableSize const i_rowDelta)
{
return impl_ni_ScrollRows(i_rowDelta);
}
TableSize TableControl::impl_ni_ScrollColumns(TableSize _nColumnDelta)
{
// compute new left column
const sal_Int32 nNewLeftColumn
= ::std::max(::std::min(static_cast<sal_Int32>(m_nLeftColumn + _nColumnDelta),
static_cast<sal_Int32>(m_nColumnCount - 1)),
sal_Int32(0));
const sal_Int32 nOldLeftColumn = m_nLeftColumn;
m_nLeftColumn = nNewLeftColumn;
// if updates are enabled currently, scroll the viewport
if (m_nLeftColumn != nOldLeftColumn)
{
SuppressCursor aHideCursor(*this);
// TODO: call an onStartScroll at our listener (or better an own onStartScroll,
// which hides the cursor and then calls the listener)
// Same for onEndScroll
// scroll the view port, if possible
const tools::Rectangle aDataArea(Point(m_nRowHeaderWidthPixel, 0),
m_pDataWindow->GetOutputSizePixel());
tools::Long nPixelDelta = m_aColumnWidths[nOldLeftColumn].getStart()
- m_aColumnWidths[m_nLeftColumn].getStart();
// update our column positions
// Do this *before* scrolling, as ScrollFlags::Update will trigger a paint, which already needs the correct
// information in m_aColumnWidths
for (auto& columnWidth : m_aColumnWidths)
{
columnWidth.move(nPixelDelta);
}
// scroll the window content (if supported and possible), or invalidate the complete window
if (m_pDataWindow->GetBackground().IsScrollable()
&& std::abs(nPixelDelta) < aDataArea.GetWidth())
{
m_pDataWindow->Scroll(nPixelDelta, 0, aDataArea,
ScrollFlags::Clip | ScrollFlags::Update);
}
else
{
m_pDataWindow->Invalidate(InvalidateFlags::Update);
m_pDataWindow->GetParent()->Invalidate(InvalidateFlags::Transparent);
}
// update the position at the horizontal scrollbar
if (m_pHScroll != nullptr)
m_pHScroll->SetThumbPos(m_nLeftColumn);
}
// The scroll bar availability might change when we scrolled. This is because we do not hide
// the scrollbar when it is, in theory, unnecessary, but currently at a position > 0. In this case, it will
// be auto-hidden when it's scrolled back to pos 0.
if (m_nLeftColumn == 0)
PostUserEvent(LINK(this, TableControl, OnUpdateScrollbars));
return static_cast<TableSize>(m_nLeftColumn - nOldLeftColumn);
}
TableSize TableControl::impl_scrollColumns(TableSize const i_columnDelta)
{
return impl_ni_ScrollColumns(i_columnDelta);
}
SelectionEngine* TableControl::getSelEngine() { return m_pSelEngine.get(); }
sal_Int32 TableControl::GetRowCount() const { return m_pModel->getRowCount(); }
sal_Int32 TableControl::GetColumnCount() const { return m_pModel->getColumnCount(); }
OUString TableControl::GetRowName(sal_Int32 nIndex) const
{
OUString sRowName;
m_pModel->getRowHeading(nIndex) >>= sRowName;
return sRowName;
}
OUString TableControl::GetColumnName(sal_Int32 nIndex) const
{
return m_pModel->getColumnModel(nIndex)->getName();
}
bool TableControl::HasRowHeader() { return m_pModel->hasRowHeaders(); }
bool TableControl::HasColumnHeader() { return m_pModel->hasColumnHeaders(); }
bool TableControl::IsRowSelected(sal_Int32 i_row) const
{
return ::std::find(m_aSelectedRows.begin(), m_aSelectedRows.end(), i_row)
!= m_aSelectedRows.end();
}
sal_Int32 TableControl::GetSelectedRowIndex(size_t const i_selectionIndex) const
{
if (i_selectionIndex < m_aSelectedRows.size())
return m_aSelectedRows[i_selectionIndex];
return ROW_INVALID;
}
int TableControl::getRowSelectedNumber(const ::std::vector<sal_Int32>& selectedRows,
sal_Int32 current)
{
std::vector<sal_Int32>::const_iterator it
= ::std::find(selectedRows.begin(), selectedRows.end(), current);
if (it != selectedRows.end())
{
return it - selectedRows.begin();
}
return -1;
}
void TableControl::SelectRow(sal_Int32 nRowIndex, bool bSelect)
{
ENSURE_OR_RETURN_VOID((nRowIndex >= 0) && (nRowIndex < m_pModel->getRowCount()),
"TableControl::SelectRow: invalid row index!");
if (bSelect)
{
if (!markRowAsSelected(nRowIndex))
// nothing to do
return;
}
else
{
markRowAsDeselected(nRowIndex);
}
invalidateRowRange(nRowIndex, nRowIndex);
Select();
}
void TableControl::SelectAllRows(bool bSelect)
{
if (bSelect)
{
if (!markAllRowsAsSelected())
// nothing to do
return;
}
else
{
if (!markAllRowsAsDeselected())
// nothing to do
return;
}
Invalidate();
// TODO: can't we do better than this, and invalidate only the rows which changed?
Select();
}
sal_Int32 TableControl::impl_getColumnForOrdinate(tools::Long const i_ordinate) const
{
if ((m_aColumnWidths.empty()) || (i_ordinate < 0))
return COL_INVALID;
if (i_ordinate < m_nRowHeaderWidthPixel)
return COL_ROW_HEADERS;
ColumnPositions::const_iterator lowerBound = ::std::lower_bound(
m_aColumnWidths.begin(), m_aColumnWidths.end(),
MutableColumnMetrics(i_ordinate + 1, i_ordinate + 1), ColumnInfoPositionLess());
if (lowerBound == m_aColumnWidths.end())
{
// point is *behind* the start of the last column ...
if (i_ordinate < m_aColumnWidths.rbegin()->getEnd())
// ... but still before its end
return m_nColumnCount - 1;
return COL_INVALID;
}
return lowerBound - m_aColumnWidths.begin();
}
sal_Int32 TableControl::impl_getRowForAbscissa(tools::Long const i_abscissa) const
{
if (i_abscissa < 0)
return ROW_INVALID;
if (i_abscissa < m_nColHeaderHeightPixel)
return ROW_COL_HEADERS;
tools::Long const abscissa = i_abscissa - m_nColHeaderHeightPixel;
tools::Long const row = m_nTopRow + abscissa / m_nRowHeightPixel;
return row < m_pModel->getRowCount() ? row : ROW_INVALID;
}
bool TableControl::markRowAsDeselected(sal_Int32 const i_rowIndex)
{
::std::vector<sal_Int32>::iterator selPos
= ::std::find(m_aSelectedRows.begin(), m_aSelectedRows.end(), i_rowIndex);
if (selPos == m_aSelectedRows.end())
return false;
m_aSelectedRows.erase(selPos);
return true;
}
bool TableControl::markRowAsSelected(sal_Int32 const i_rowIndex)
{
if (IsRowSelected(i_rowIndex))
return false;
SelectionMode const eSelMode = getSelEngine()->GetSelectionMode();
switch (eSelMode)
{
case SelectionMode::Single:
if (!m_aSelectedRows.empty())
{
OSL_ENSURE(m_aSelectedRows.size() == 1, "TableControl::markRowAsSelected: "
"SingleSelection with more than one "
"selected element?");
m_aSelectedRows[0] = i_rowIndex;
break;
}
[[fallthrough]];
case SelectionMode::Multiple:
m_aSelectedRows.push_back(i_rowIndex);
break;
default:
OSL_ENSURE(false, "TableControl::markRowAsSelected: unsupported selection mode!");
return false;
}
return true;
}
bool TableControl::markAllRowsAsDeselected()
{
if (m_aSelectedRows.empty())
return false;
m_aSelectedRows.clear();
return true;
}
bool TableControl::markAllRowsAsSelected()
{
SelectionMode const eSelMode = getSelEngine()->GetSelectionMode();
ENSURE_OR_RETURN_FALSE(eSelMode == SelectionMode::Multiple,
"TableControl::markAllRowsAsSelected: unsupported selection mode!");
if (m_aSelectedRows.size() == size_t(m_pModel->getRowCount()))
{
#if OSL_DEBUG_LEVEL > 0
for (TableSize row = 0; row < m_pModel->getRowCount(); ++row)
{
OSL_ENSURE(IsRowSelected(row),
"TableControl::markAllRowsAsSelected: inconsistency in the selected rows!");
}
#endif
// already all rows marked as selected
return false;
}
m_aSelectedRows.clear();
for (sal_Int32 i = 0; i < m_pModel->getRowCount(); ++i)
m_aSelectedRows.push_back(i);
return true;
}
void TableControl::commitAccessibleEvent(sal_Int16 const i_eventID)
{
impl_commitAccessibleEvent(i_eventID, Any());
}
void TableControl::commitCellEvent(sal_Int16 const i_eventID, const Any& i_newValue,
const Any& i_oldValue)
{
if (m_xAccessibleTable.is())
m_xAccessibleTable->commitCellEvent(i_eventID, i_newValue, i_oldValue);
}
void TableControl::commitTableEvent(sal_Int16 const i_eventID, const Any& i_newValue,
const Any& i_oldValue)
{
if (m_xAccessibleTable.is())
m_xAccessibleTable->commitTableEvent(i_eventID, i_newValue, i_oldValue);
}
bool TableControl::ConvertPointToCellAddress(sal_Int32& rRow, sal_Int32& rColPos,
const Point& rPoint)
{
rRow = getRowAtPoint(rPoint);
rColPos = getColAtPoint(rPoint);
return rRow >= 0;
}
tools::Rectangle TableControl::calcHeaderRect(bool bColHeader)
{
tools::Rectangle const aRectTableWithHeaders(impl_getAllVisibleCellsArea());
Size const aSizeTableWithHeaders(aRectTableWithHeaders.GetSize());
if (bColHeader)
return tools::Rectangle(aRectTableWithHeaders.TopLeft(),
Size(aSizeTableWithHeaders.Width(), m_nColHeaderHeightPixel));
else
return tools::Rectangle(aRectTableWithHeaders.TopLeft(),
Size(m_nRowHeaderWidthPixel, aSizeTableWithHeaders.Height()));
}
tools::Rectangle TableControl::calcHeaderCellRect(bool bColHeader, sal_Int32 nPos)
{
tools::Rectangle const aHeaderRect = calcHeaderRect(bColHeader);
TableCellGeometry const aGeometry(*this, aHeaderRect, bColHeader ? nPos : COL_ROW_HEADERS,
bColHeader ? ROW_COL_HEADERS : nPos);
return aGeometry.getRect();
}
tools::Rectangle TableControl::calcTableRect() const { return impl_getAllVisibleDataCellArea(); }
tools::Rectangle TableControl::calcCellRect(sal_Int32 nRow, sal_Int32 nCol) const
{
if (!m_pModel || (nRow == ROW_INVALID) || (nCol == COL_INVALID))
return tools::Rectangle();
TableCellGeometry aCell(*this, impl_getAllVisibleCellsArea(), nCol, nRow);
return aCell.getRect();
}
IMPL_LINK_NOARG(TableControl, OnUpdateScrollbars, void*, void)
{
// TODO: can't we simply use lcl_updateScrollbar here, so the scrollbars ranges are updated, instead of
// doing a complete re-layout?
impl_ni_relayout();
}
IMPL_LINK(TableControl, OnScroll, ScrollBar*, _pScrollbar, void)
{
DBG_ASSERT((_pScrollbar == m_pVScroll) || (_pScrollbar == m_pHScroll),
"TableControl::OnScroll: where did this come from?");
if (_pScrollbar == m_pVScroll)
impl_ni_ScrollRows(_pScrollbar->GetDelta());
else
impl_ni_ScrollColumns(_pScrollbar->GetDelta());
}
IMPL_LINK_NOARG(TableControl, ImplSelectHdl, LinkParamNone*, void) { Select(); }
rtl::Reference<comphelper::OAccessible> TableControl::CreateAccessible()
{
rtl::Reference<comphelper::OAccessible> pParent = GetAccessibleParent();
return getAccessible(pParent);
}
const rtl::Reference<accessibility::AccessibleGridControl>&
TableControl::getAccessible(const rtl::Reference<comphelper::OAccessible>& rpParent)
{
if (m_xAccessibleTable.is())
return m_xAccessibleTable;
DBG_TESTSOLARMUTEX();
if (rpParent.is())
{
m_xAccessibleTable = new accessibility::AccessibleGridControl(rpParent, *this);
}
return m_xAccessibleTable;
}
void TableControl::disposeAccessible()
{
if (m_xAccessibleTable.is())
m_xAccessibleTable->dispose();
m_xAccessibleTable.clear();
}
sal_Int32 TableControl::GetAccessibleControlCount() const
{
// TC_TABLE is always defined, no matter whether empty or not
sal_Int32 count = 1;
if (m_pModel->hasRowHeaders())
++count;
if (m_pModel->hasColumnHeaders())
++count;
return count;
}
OUString TableControl::GetAccessibleObjectName(AccessibleTableControlObjType eObjType,
sal_Int32 nRow, sal_Int32 nCol) const
{
OUString sRetText;
//Window* pWin;
switch (eObjType)
{
case AccessibleTableControlObjType::GRIDCONTROL:
sRetText = "Grid control";
break;
case AccessibleTableControlObjType::TABLE:
sRetText = "Grid control";
break;
case AccessibleTableControlObjType::ROWHEADERBAR:
sRetText = "RowHeaderBar";
break;
case AccessibleTableControlObjType::COLUMNHEADERBAR:
sRetText = "ColumnHeaderBar";
break;
case AccessibleTableControlObjType::TABLECELL:
//the name of the cell consists of column name and row name if defined
//if the name is equal to cell content, it'll be read twice
if (m_pModel->hasColumnHeaders())
{
sRetText = GetColumnName(nCol) + " , ";
}
if (m_pModel->hasRowHeaders())
{
sRetText += GetRowName(nRow) + " , ";
}
//aRetText = GetAccessibleCellText(_nRow, _nCol);
break;
case AccessibleTableControlObjType::ROWHEADERCELL:
sRetText = GetRowName(nRow);
break;
case AccessibleTableControlObjType::COLUMNHEADERCELL:
sRetText = GetColumnName(nCol);
break;
default:
OSL_FAIL("GridControl::GetAccessibleName: invalid enum!");
}
return sRetText;
}
OUString TableControl::GetAccessibleObjectDescription(AccessibleTableControlObjType eObjType) const
{
OUString sRetText;
switch (eObjType)
{
case AccessibleTableControlObjType::GRIDCONTROL:
sRetText = "Grid control description";
break;
case AccessibleTableControlObjType::TABLE:
sRetText = "TABLE description";
break;
case AccessibleTableControlObjType::ROWHEADERBAR:
sRetText = "ROWHEADERBAR description";
break;
case AccessibleTableControlObjType::COLUMNHEADERBAR:
sRetText = "COLUMNHEADERBAR description";
break;
case AccessibleTableControlObjType::TABLECELL:
// the description of the cell consists of column name and row name if defined
// if the name is equal to cell content, it'll be read twice
if (m_pModel->hasColumnHeaders())
{
sRetText = GetColumnName(GetCurrentColumn()) + " , ";
}
if (m_pModel->hasRowHeaders())
{
sRetText += GetRowName(GetCurrentRow());
}
break;
case AccessibleTableControlObjType::ROWHEADERCELL:
sRetText = "ROWHEADERCELL description";
break;
case AccessibleTableControlObjType::COLUMNHEADERCELL:
sRetText = "COLUMNHEADERCELL description";
break;
}
return sRetText;
}
void TableControl::FillAccessibleStateSet(sal_Int64& rStateSet,
AccessibleTableControlObjType eObjType) const
{
switch (eObjType)
{
case AccessibleTableControlObjType::GRIDCONTROL:
case AccessibleTableControlObjType::TABLE:
rStateSet |= css::accessibility::AccessibleStateType::FOCUSABLE;
if (m_pSelEngine->GetSelectionMode() == SelectionMode::Multiple)
rStateSet |= css::accessibility::AccessibleStateType::MULTI_SELECTABLE;
if (HasChildPathFocus())
rStateSet |= css::accessibility::AccessibleStateType::FOCUSED;
if (IsActive())
rStateSet |= css::accessibility::AccessibleStateType::ACTIVE;
if (getDataWindow().IsEnabled())
{
rStateSet |= css::accessibility::AccessibleStateType::ENABLED;
rStateSet |= css::accessibility::AccessibleStateType::SENSITIVE;
}
if (IsReallyVisible())
rStateSet |= css::accessibility::AccessibleStateType::VISIBLE;
if (eObjType == AccessibleTableControlObjType::TABLE)
rStateSet |= css::accessibility::AccessibleStateType::MANAGES_DESCENDANTS;
break;
case AccessibleTableControlObjType::COLUMNHEADERBAR:
case AccessibleTableControlObjType::ROWHEADERBAR:
rStateSet |= css::accessibility::AccessibleStateType::VISIBLE;
rStateSet |= css::accessibility::AccessibleStateType::MANAGES_DESCENDANTS;
break;
case AccessibleTableControlObjType::TABLECELL:
{
rStateSet |= css::accessibility::AccessibleStateType::FOCUSABLE;
if (HasChildPathFocus())
rStateSet |= css::accessibility::AccessibleStateType::FOCUSED;
rStateSet |= css::accessibility::AccessibleStateType::ACTIVE;
rStateSet |= css::accessibility::AccessibleStateType::TRANSIENT;
rStateSet |= css::accessibility::AccessibleStateType::SELECTABLE;
rStateSet |= css::accessibility::AccessibleStateType::VISIBLE;
rStateSet |= css::accessibility::AccessibleStateType::SHOWING;
if (IsRowSelected(GetCurrentRow()))
// Hmm? Wouldn't we expect the affected row to be a parameter to this function?
rStateSet |= css::accessibility::AccessibleStateType::SELECTED;
}
break;
case AccessibleTableControlObjType::ROWHEADERCELL:
rStateSet |= css::accessibility::AccessibleStateType::VISIBLE;
rStateSet |= css::accessibility::AccessibleStateType::TRANSIENT;
break;
case AccessibleTableControlObjType::COLUMNHEADERCELL:
rStateSet |= css::accessibility::AccessibleStateType::VISIBLE;
break;
}
}
void TableControl::FillAccessibleStateSetForCell(sal_Int64& rStateSet, sal_Int32 nRow,
sal_uInt16) const
{
if (IsRowSelected(nRow))
rStateSet |= css::accessibility::AccessibleStateType::SELECTED;
if (HasChildPathFocus())
rStateSet |= css::accessibility::AccessibleStateType::FOCUSED;
else // only transient when column is not focused
rStateSet |= css::accessibility::AccessibleStateType::TRANSIENT;
rStateSet |= css::accessibility::AccessibleStateType::VISIBLE;
rStateSet |= css::accessibility::AccessibleStateType::SHOWING;
rStateSet |= css::accessibility::AccessibleStateType::ENABLED;
rStateSet |= css::accessibility::AccessibleStateType::SENSITIVE;
rStateSet |= css::accessibility::AccessibleStateType::ACTIVE;
}
void TableControl::impl_commitAccessibleEvent(sal_Int16 const i_eventID, Any const& i_newValue)
{
if (m_xAccessibleTable.is())
m_xAccessibleTable->commitEvent(i_eventID, i_newValue, css::uno::Any());
}
} // namespace svt::table
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */