Files
libreoffice/toolkit/source/controls/table/tablecontrol_impl.cxx
Michael Weghorn d040635b8e a11y: Move AccessibleGridControl* classes to toolkit
Move AccessibleGridControlAccess and all related
classes from vcl to toolkit, as that is where the
grid control that makes use of them is located.

Stop using the AccessibleFactory to create the
AccessibleGridControlAccess, but call the ctor
directly in TableControl_Impl::getAccessible,
now that the class is also in the toolkit module.

Further simplificiation can happen in upcoming
commits.

Change-Id: Ic93796bce96916192da7cfffcf1faf00905373c7
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/180668
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
Tested-by: Jenkins
2025-01-24 07:37:59 +01:00

2553 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/defaultinputhandler.hxx>
#include <controls/table/tablemodel.hxx>
#include "tabledatawindow.hxx"
#include "tablecontrol_impl.hxx"
#include "tablegeometry.hxx"
#include <com/sun/star/accessibility/XAccessible.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 <vcl/accessiblefactory.hxx>
#include <vcl/toolkit/scrbar.hxx>
#include <vcl/seleng.hxx>
#include <vcl/settings.hxx>
#include <vcl/image.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:
ITableControl& m_rTable;
public:
explicit SuppressCursor( ITableControl& _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( ColPos ) 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 PTableModelListener& ) override {}
virtual void removeTableModelListener( const PTableModelListener& ) 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( ColPos const, RowPos const, css::uno::Any& o_cellContent ) override
{
o_cellContent.clear();
}
virtual void getCellToolTip( ColPos const, RowPos const, css::uno::Any& ) override
{
}
virtual Any getRowHeading( RowPos const ) const override
{
return Any();
}
};
}
TableControl_Impl::TableControl_Impl( TableControl& _rAntiImpl )
:m_rAntiImpl ( _rAntiImpl )
,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();
}
TableControl_Impl::~TableControl_Impl()
{
m_pVScroll.disposeAndClear();
m_pHScroll.disposeAndClear();
m_pScrollCorner.disposeAndClear();
m_pDataWindow.disposeAndClear();
m_pTableFunctionSet.reset();
m_pSelEngine.reset();
}
void TableControl_Impl::setModel( const PTableModel& _pModel )
{
SuppressCursor aHideCursor( *this );
if ( m_pModel )
m_pModel->removeTableModelListener( shared_from_this() );
m_pModel = _pModel;
if ( !m_pModel)
m_pModel = std::make_shared<EmptyTableModel>();
m_pModel->addTableModelListener( shared_from_this() );
m_nCurRow = ROW_INVALID;
m_nCurColumn = COL_INVALID;
// recalc some model-dependent cached info
impl_ni_updateCachedModelValues();
impl_ni_relayout();
// completely invalidate
m_rAntiImpl.Invalidate();
// reset cursor to (0,0)
if ( m_nRowCount ) m_nCurRow = 0;
if ( m_nColumnCount ) m_nCurColumn = 0;
}
namespace
{
bool lcl_adjustSelectedRows( ::std::vector< RowPos >& io_selectionIndexes, RowPos 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_Impl::rowsInserted( RowPos i_first, RowPos i_last )
{
OSL_PRECOND( i_last >= i_first, "TableControl_Impl::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 )
goTo( m_nCurColumn, m_nCurRow + insertedRows );
// relayout, since the scrollbar need might have changed
impl_ni_relayout();
// notify A1YY events
if ( impl_isAccessibleAlive() )
{
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 )
m_rAntiImpl.Select();
}
void TableControl_Impl::rowsRemoved( RowPos i_first, RowPos 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_Impl::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 )
goTo( 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
if ( impl_isAccessibleAlive() )
{
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 )
m_rAntiImpl.Select();
}
void TableControl_Impl::columnInserted()
{
m_nColumnCount = m_pModel->getColumnCount();
impl_ni_relayout();
m_rAntiImpl.Invalidate();
}
void TableControl_Impl::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 )
goTo( m_nCurColumn - 1, m_nCurRow );
else
m_nCurColumn = COL_INVALID;
}
impl_ni_relayout();
m_rAntiImpl.Invalidate();
}
void TableControl_Impl::allColumnsRemoved()
{
m_nColumnCount = m_pModel->getColumnCount();
impl_ni_relayout();
m_rAntiImpl.Invalidate();
}
void TableControl_Impl::cellsUpdated( RowPos const i_firstRow, RowPos const i_lastRow )
{
invalidateRowRange( i_firstRow, i_lastRow );
}
void TableControl_Impl::tableMetricsChanged()
{
impl_ni_updateCachedTableMetrics();
impl_ni_relayout();
m_rAntiImpl.Invalidate();
}
void TableControl_Impl::impl_invalidateColumn( ColPos const i_column )
{
tools::Rectangle const aAllCellsArea( impl_getAllVisibleCellsArea() );
const TableColumnGeometry aColumn( *this, aAllCellsArea, i_column );
if ( aColumn.isValid() )
m_rAntiImpl.Invalidate( aColumn.getRect() );
}
void TableControl_Impl::columnChanged( ColPos 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_Impl::columnChanged: don't know how to handle this change!" );
}
tools::Rectangle TableControl_Impl::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::impl_getAllVisibleDataCellArea() const
{
tools::Rectangle aArea( impl_getAllVisibleCellsArea() );
aArea.SetLeft( m_nRowHeaderWidthPixel );
aArea.SetTop( m_nColHeaderHeightPixel );
return aArea;
}
void TableControl_Impl::impl_ni_updateCachedTableMetrics()
{
m_nRowHeightPixel = m_rAntiImpl.LogicToPixel(Size(0, m_pModel->getRowHeight()), MapMode(MapUnit::MapAppFont)).Height();
m_nColHeaderHeightPixel = 0;
if ( m_pModel->hasColumnHeaders() )
m_nColHeaderHeightPixel = m_rAntiImpl.LogicToPixel(Size(0, m_pModel->getColumnHeaderHeight()), MapMode(MapUnit::MapAppFont)).Height();
m_nRowHeaderWidthPixel = 0;
if ( m_pModel->hasRowHeaders() )
m_nRowHeaderWidthPixel = m_rAntiImpl.LogicToPixel(Size(m_pModel->getRowHeaderWidth(), 0), MapMode(MapUnit::MapAppFont)).Width();
}
void TableControl_Impl::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, ColPos _nFirstVisibleColumn,
const TableControl_Impl& _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::impl_ni_calculateColumnWidths( ColPos const i_assumeInflexibleColumnsUpToIncluding,
bool const i_assumeVerticalScrollbar, ::std::vector< tools::Long >& o_newColWidthsPixel ) const
{
// the available horizontal space
tools::Long gridWidthPixel = m_rAntiImpl.GetOutputSizePixel().Width();
ENSURE_OR_RETURN( !!m_pModel, "TableControl_Impl::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 = m_rAntiImpl.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 ( ColPos 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::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::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::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::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::impl_ni_relayout( ColPos const i_assumeInflexibleColumnsUpToIncluding )
{
ENSURE_OR_RETURN_VOID( !m_bUpdatingColWidths, "TableControl_Impl::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 = m_rAntiImpl.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 ), m_rAntiImpl.GetOutputSizePixel() );
aDataCellPlayground.SetLeft( m_nRowHeaderWidthPixel );
aDataCellPlayground.SetTop( m_nColHeaderHeightPixel );
OSL_ENSURE( ( m_nRowCount == m_pModel->getRowCount() ) && ( m_nColumnCount == m_pModel->getColumnCount() ),
"TableControl_Impl::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 ( ColPos 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::impl_ni_positionChildWindows( tools::Rectangle const & i_dataCellPlayground,
bool const i_verticalScrollbar, bool const i_horizontalScrollbar )
{
tools::Long const nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
// create or destroy the vertical scrollbar, as needed
lcl_updateScrollbar(
m_rAntiImpl,
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_Impl, 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(
m_rAntiImpl,
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_Impl, 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( &m_rAntiImpl );
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_Impl::onResize()
{
impl_ni_relayout();
checkCursorPosition();
}
void TableControl_Impl::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 = m_rAntiImpl.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_Impl::hideCursor()
{
if ( ++m_nCursorHidden == 1 )
impl_ni_doSwitchCursor( false );
}
void TableControl_Impl::showCursor()
{
DBG_ASSERT( m_nCursorHidden > 0, "TableControl_Impl::showCursor: cursor not hidden!" );
if ( --m_nCursorHidden == 0 )
impl_ni_doSwitchCursor( true );
}
bool TableControl_Impl::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 = goTo( 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 = goTo( m_nCurColumn, m_nCurRow - 1 );
}
break;
case TableControlAction::cursorLeft:
if ( m_nCurColumn > 0 )
bSuccess = goTo( m_nCurColumn - 1, m_nCurRow );
else
if ( ( m_nCurColumn == 0) && ( m_nCurRow > 0 ) )
bSuccess = goTo( m_nColumnCount - 1, m_nCurRow - 1 );
break;
case TableControlAction::cursorRight:
if ( m_nCurColumn < m_nColumnCount - 1 )
bSuccess = goTo( m_nCurColumn + 1, m_nCurRow );
else
if ( ( m_nCurColumn == m_nColumnCount - 1 ) && ( m_nCurRow < m_nRowCount - 1 ) )
bSuccess = goTo( 0, m_nCurRow + 1 );
break;
case TableControlAction::cursorToLineStart:
bSuccess = goTo( 0, m_nCurRow );
break;
case TableControlAction::cursorToLineEnd:
bSuccess = goTo( m_nColumnCount - 1, m_nCurRow );
break;
case TableControlAction::cursorToFirstLine:
bSuccess = goTo( m_nCurColumn, 0 );
break;
case TableControlAction::cursorToLastLine:
bSuccess = goTo( m_nCurColumn, m_nRowCount - 1 );
break;
case TableControlAction::cursorPageUp:
{
RowPos nNewRow = ::std::max( RowPos(0), m_nCurRow - impl_getVisibleRows( false ) );
bSuccess = goTo( m_nCurColumn, nNewRow );
}
break;
case TableControlAction::cursorPageDown:
{
RowPos nNewRow = ::std::min( m_nRowCount - 1, m_nCurRow + impl_getVisibleRows( false ) );
bSuccess = goTo( m_nCurColumn, nNewRow );
}
break;
case TableControlAction::cursorTopLeft:
bSuccess = goTo( 0, 0 );
break;
case TableControlAction::cursorBottomRight:
bSuccess = goTo( 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
RowPos 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
RowPos 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_Impl::dispatchAction: unsupported action!" );
break;
}
if ( bSuccess && selectionChanged )
{
m_rAntiImpl.Select();
}
return bSuccess;
}
void TableControl_Impl::impl_ni_doSwitchCursor( bool _bShow )
{
PTableRenderer pRenderer = m_pModel ? m_pModel->getRenderer() : PTableRenderer();
if ( pRenderer )
{
tools::Rectangle aCellRect;
impl_getCellRect( m_nCurColumn, m_nCurRow, aCellRect );
if ( _bShow )
pRenderer->ShowCellCursor( *m_pDataWindow, aCellRect );
else
pRenderer->HideCellCursor( *m_pDataWindow );
}
}
void TableControl_Impl::impl_getCellRect( ColPos _nColumn, RowPos _nRow, tools::Rectangle& _rCellRect ) const
{
if ( !m_pModel
|| ( COL_INVALID == _nColumn )
|| ( ROW_INVALID == _nRow )
)
{
_rCellRect.SetEmpty();
return;
}
TableCellGeometry aCell( *this, impl_getAllVisibleCellsArea(), _nColumn, _nRow );
_rCellRect = aCell.getRect();
}
RowPos TableControl_Impl::getRowAtPoint( const Point& rPoint ) const
{
return impl_getRowForAbscissa( rPoint.Y() );
}
ColPos TableControl_Impl::getColAtPoint( const Point& rPoint ) const
{
return impl_getColumnForOrdinate( rPoint.X() );
}
TableCell TableControl_Impl::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_Impl::getColumnMetrics( ColPos const i_column ) const
{
ENSURE_OR_RETURN( ( i_column >= 0 ) && ( i_column < m_pModel->getColumnCount() ),
"TableControl_Impl::getColumnMetrics: illegal column index!", ColumnMetrics() );
return m_aColumnWidths[ i_column ];
}
PTableModel TableControl_Impl::getModel() const
{
return m_pModel;
}
ColPos TableControl_Impl::getCurrentColumn() const
{
return m_nCurColumn;
}
RowPos TableControl_Impl::getCurrentRow() const
{
return m_nCurRow;
}
::Size TableControl_Impl::getTableSizePixel() const
{
return m_pDataWindow->GetOutputSizePixel();
}
void TableControl_Impl::setPointer( PointerStyle i_pointer )
{
m_pDataWindow->SetPointer( i_pointer );
}
void TableControl_Impl::captureMouse()
{
m_pDataWindow->CaptureMouse();
}
void TableControl_Impl::releaseMouse()
{
m_pDataWindow->ReleaseMouse();
}
void TableControl_Impl::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_Impl::pixelWidthToAppFont( tools::Long const i_pixels ) const
{
return m_pDataWindow->PixelToLogic(Size(i_pixels, 0), MapMode(MapUnit::MapAppFont)).Width();
}
tools::Long TableControl_Impl::appFontWidthToPixel( tools::Long const i_appFontUnits ) const
{
return m_pDataWindow->LogicToPixel(Size(i_appFontUnits, 0), MapMode(MapUnit::MapAppFont)).Width();
}
void TableControl_Impl::hideTracking()
{
m_pDataWindow->HideTracking();
}
void TableControl_Impl::showTracking( tools::Rectangle const & i_location, ShowTrackFlags const i_flags )
{
m_pDataWindow->ShowTracking( i_location, i_flags );
}
void TableControl_Impl::activateCell( ColPos const i_col, RowPos const i_row )
{
goTo( i_col, i_row );
}
void TableControl_Impl::invalidateSelectedRegion( RowPos _nPrevRow, RowPos _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;
impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
aInvalidateRect.SetTop( aCellRect.Top() );
aInvalidateRect.SetBottom( aCellRect.Bottom() );
}
//if the region is above the current row
else if(_nPrevRow < _nCurRow )
{
tools::Rectangle aCellRect;
impl_getCellRect( m_nCurColumn, _nPrevRow, aCellRect );
aInvalidateRect.SetTop( aCellRect.Top() );
impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
aInvalidateRect.SetBottom( aCellRect.Bottom() );
}
//if the region is beneath the current row
else
{
tools::Rectangle aCellRect;
impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
aInvalidateRect.SetTop( aCellRect.Top() );
impl_getCellRect( m_nCurColumn, _nPrevRow, aCellRect );
aInvalidateRect.SetBottom( aCellRect.Bottom() );
}
invalidateRect(aInvalidateRect);
}
void TableControl_Impl::invalidateRect(const tools::Rectangle &rInvalidateRect)
{
m_pDataWindow->Invalidate( rInvalidateRect,
m_pDataWindow->GetControlBackground().IsTransparent() ? InvalidateFlags::Transparent : InvalidateFlags::NONE );
}
void TableControl_Impl::invalidateSelectedRows()
{
for (auto const& selectedRow : m_aSelectedRows)
{
invalidateRow(selectedRow);
}
}
void TableControl_Impl::invalidateRowRange( RowPos const i_firstRow, RowPos const i_lastRow )
{
RowPos const firstRow = i_firstRow < m_nTopRow ? m_nTopRow : i_firstRow;
RowPos const lastVisibleRow = m_nTopRow + impl_getVisibleRows( true ) - 1;
RowPos 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_Impl::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::impl_getVisibleRows( bool _bAcceptPartialRow ) const
{
DBG_ASSERT( m_pDataWindow, "TableControl_Impl::impl_getVisibleRows: no data window!" );
return lcl_getRowsFittingInto(
m_pDataWindow->GetOutputSizePixel().Height() - m_nColHeaderHeightPixel,
m_nRowHeightPixel,
_bAcceptPartialRow
);
}
TableSize TableControl_Impl::impl_getVisibleColumns( bool _bAcceptPartialCol ) const
{
DBG_ASSERT( m_pDataWindow, "TableControl_Impl::impl_getVisibleColumns: no data window!" );
return lcl_getColumnsVisibleWithin(
tools::Rectangle( Point( 0, 0 ), m_pDataWindow->GetOutputSizePixel() ),
m_nLeftColumn,
*this,
_bAcceptPartialCol
);
}
bool TableControl_Impl::goTo( ColPos _nColumn, RowPos _nRow )
{
// TODO: give veto listeners a chance
if ( ( _nColumn < 0 ) || ( _nColumn >= m_nColumnCount )
|| ( _nRow < 0 ) || ( _nRow >= m_nRowCount )
)
{
OSL_ENSURE( false, "TableControl_Impl::goTo: 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_Impl::ensureVisible( ColPos _nColumn, RowPos _nRow )
{
DBG_ASSERT( ( _nColumn >= 0 ) && ( _nColumn < m_nColumnCount )
&& ( _nRow >= 0 ) && ( _nRow < m_nRowCount ),
"TableControl_Impl::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_Impl::getCellContentAsString( RowPos const i_row, ColPos 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::impl_ni_ScrollRows( TableSize _nRowDelta )
{
// compute new top row
RowPos nNewTopRow =
::std::max(
::std::min( static_cast<RowPos>( m_nTopRow + _nRowDelta ), static_cast<RowPos>( m_nRowCount - 1 ) ),
RowPos(0)
);
RowPos 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 )
m_rAntiImpl.PostUserEvent( LINK( this, TableControl_Impl, OnUpdateScrollbars ) );
return static_cast<TableSize>( m_nTopRow - nOldTopRow );
}
TableSize TableControl_Impl::impl_scrollRows( TableSize const i_rowDelta )
{
return impl_ni_ScrollRows( i_rowDelta );
}
TableSize TableControl_Impl::impl_ni_ScrollColumns( TableSize _nColumnDelta )
{
// compute new left column
const ColPos nNewLeftColumn =
::std::max(
::std::min( static_cast<ColPos>( m_nLeftColumn + _nColumnDelta ), static_cast<ColPos>( m_nColumnCount - 1 ) ),
ColPos(0)
);
const ColPos 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 )
m_rAntiImpl.PostUserEvent( LINK( this, TableControl_Impl, OnUpdateScrollbars ) );
return static_cast<TableSize>( m_nLeftColumn - nOldLeftColumn );
}
TableSize TableControl_Impl::impl_scrollColumns( TableSize const i_columnDelta )
{
return impl_ni_ScrollColumns( i_columnDelta );
}
SelectionEngine* TableControl_Impl::getSelEngine()
{
return m_pSelEngine.get();
}
bool TableControl_Impl::isRowSelected( RowPos i_row ) const
{
return ::std::find( m_aSelectedRows.begin(), m_aSelectedRows.end(), i_row ) != m_aSelectedRows.end();
}
RowPos TableControl_Impl::getSelectedRowIndex( size_t const i_selectionIndex ) const
{
if ( i_selectionIndex < m_aSelectedRows.size() )
return m_aSelectedRows[ i_selectionIndex ];
return ROW_INVALID;
}
int TableControl_Impl::getRowSelectedNumber(const ::std::vector<RowPos>& selectedRows, RowPos current)
{
std::vector<RowPos>::const_iterator it = ::std::find(selectedRows.begin(),selectedRows.end(),current);
if ( it != selectedRows.end() )
{
return it - selectedRows.begin();
}
return -1;
}
ColPos TableControl_Impl::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();
}
RowPos TableControl_Impl::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_Impl::markRowAsDeselected( RowPos const i_rowIndex )
{
::std::vector< RowPos >::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_Impl::markRowAsSelected( RowPos 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_Impl::markRowAsSelected: unsupported selection mode!" );
return false;
}
return true;
}
bool TableControl_Impl::markAllRowsAsDeselected()
{
if ( m_aSelectedRows.empty() )
return false;
m_aSelectedRows.clear();
return true;
}
bool TableControl_Impl::markAllRowsAsSelected()
{
SelectionMode const eSelMode = getSelEngine()->GetSelectionMode();
ENSURE_OR_RETURN_FALSE( eSelMode == SelectionMode::Multiple, "TableControl_Impl::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_Impl::markAllRowsAsSelected: inconsistency in the selected rows!" );
}
#endif
// already all rows marked as selected
return false;
}
m_aSelectedRows.clear();
for ( RowPos i=0; i < m_pModel->getRowCount(); ++i )
m_aSelectedRows.push_back(i);
return true;
}
void TableControl_Impl::commitAccessibleEvent( sal_Int16 const i_eventID )
{
impl_commitAccessibleEvent( i_eventID, Any() );
}
void TableControl_Impl::commitCellEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue )
{
if ( impl_isAccessibleAlive() )
m_pAccessibleTable->commitCellEvent( i_eventID, i_newValue, i_oldValue );
}
void TableControl_Impl::commitTableEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue )
{
if ( impl_isAccessibleAlive() )
m_pAccessibleTable->commitTableEvent( i_eventID, i_newValue, i_oldValue );
}
tools::Rectangle TableControl_Impl::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_Impl::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_Impl::calcTableRect() const
{
return impl_getAllVisibleDataCellArea();
}
tools::Rectangle TableControl_Impl::calcCellRect( sal_Int32 nRow, sal_Int32 nCol ) const
{
tools::Rectangle aCellRect;
impl_getCellRect( nRow, nCol, aCellRect );
return aCellRect;
}
IMPL_LINK_NOARG( TableControl_Impl, 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_Impl, OnScroll, ScrollBar*, _pScrollbar, void )
{
DBG_ASSERT( ( _pScrollbar == m_pVScroll ) || ( _pScrollbar == m_pHScroll ),
"TableControl_Impl::OnScroll: where did this come from?" );
if ( _pScrollbar == m_pVScroll )
impl_ni_ScrollRows( _pScrollbar->GetDelta() );
else
impl_ni_ScrollColumns( _pScrollbar->GetDelta() );
}
const rtl::Reference<vcl::table::IAccessibleTableControl> & TableControl_Impl::getAccessible( vcl::Window& i_parentWindow )
{
if (m_pAccessibleTable)
return m_pAccessibleTable;
DBG_TESTSOLARMUTEX();
if ( m_pAccessibleTable == nullptr )
{
Reference< XAccessible > const xAccParent = i_parentWindow.GetAccessible();
if ( xAccParent.is() )
{
m_pAccessibleTable = new accessibility::AccessibleGridControlAccess(
xAccParent, m_rAntiImpl
);
}
}
return m_pAccessibleTable;
}
void TableControl_Impl::disposeAccessible()
{
if ( m_pAccessibleTable )
m_pAccessibleTable->DisposeAccessImpl();
m_pAccessibleTable = nullptr;
}
bool TableControl_Impl::impl_isAccessibleAlive() const
{
return m_pAccessibleTable && m_pAccessibleTable->isAlive();
}
void TableControl_Impl::impl_commitAccessibleEvent( sal_Int16 const i_eventID, Any const & i_newValue )
{
if ( impl_isAccessibleAlive() )
m_pAccessibleTable->commitEvent( i_eventID, i_newValue );
}
//= TableFunctionSet
TableFunctionSet::TableFunctionSet(TableControl_Impl* _pTableControl)
:m_pTableControl( _pTableControl)
,m_nCurrentRow( ROW_INVALID )
{
}
TableFunctionSet::~TableFunctionSet()
{
}
void TableFunctionSet::BeginDrag()
{
}
void TableFunctionSet::CreateAnchor()
{
m_pTableControl->setAnchor( m_pTableControl->getCurRow() );
}
void TableFunctionSet::DestroyAnchor()
{
m_pTableControl->setAnchor( ROW_INVALID );
}
void TableFunctionSet::SetCursorAtPoint(const Point& rPoint, bool bDontSelectAtCursor)
{
// newRow is the row which includes the point, getCurRow() is the last selected row, before the mouse click
RowPos newRow = m_pTableControl->getRowAtPoint( rPoint );
if ( newRow == ROW_COL_HEADERS )
newRow = m_pTableControl->getTopRow();
ColPos newCol = m_pTableControl->getColAtPoint( rPoint );
if ( newCol == COL_ROW_HEADERS )
newCol = m_pTableControl->getLeftColumn();
if ( ( newRow == ROW_INVALID ) || ( newCol == COL_INVALID ) )
return;
if ( bDontSelectAtCursor )
{
if ( m_pTableControl->getSelectedRowCount() > 1 )
m_pTableControl->getSelEngine()->AddAlways(true);
}
else if ( m_pTableControl->getAnchor() == m_pTableControl->getCurRow() )
{
//selected region lies above the last selection
if( m_pTableControl->getCurRow() >= newRow)
{
//put selected rows in vector
while ( m_pTableControl->getAnchor() >= newRow )
{
m_pTableControl->markRowAsSelected( m_pTableControl->getAnchor() );
m_pTableControl->setAnchor( m_pTableControl->getAnchor() - 1 );
}
m_pTableControl->setAnchor( m_pTableControl->getAnchor() + 1 );
}
//selected region lies beneath the last selected row
else
{
while ( m_pTableControl->getAnchor() <= newRow )
{
m_pTableControl->markRowAsSelected( m_pTableControl->getAnchor() );
m_pTableControl->setAnchor( m_pTableControl->getAnchor() + 1 );
}
m_pTableControl->setAnchor( m_pTableControl->getAnchor() - 1 );
}
m_pTableControl->invalidateSelectedRegion( m_pTableControl->getCurRow(), newRow );
}
//no region selected
else
{
if ( !m_pTableControl->hasRowSelection() )
m_pTableControl->markRowAsSelected( newRow );
else
{
if ( m_pTableControl->getSelEngine()->GetSelectionMode() == SelectionMode::Single )
{
DeselectAll();
m_pTableControl->markRowAsSelected( newRow );
}
else
{
m_pTableControl->markRowAsSelected( newRow );
}
}
if ( m_pTableControl->getSelectedRowCount() > 1 && m_pTableControl->getSelEngine()->GetSelectionMode() != SelectionMode::Single )
m_pTableControl->getSelEngine()->AddAlways(true);
m_pTableControl->invalidateRow( newRow );
}
m_pTableControl->goTo( newCol, newRow );
}
bool TableFunctionSet::IsSelectionAtPoint( const Point& rPoint )
{
m_pTableControl->getSelEngine()->AddAlways(false);
if ( !m_pTableControl->hasRowSelection() )
return false;
else
{
RowPos curRow = m_pTableControl->getRowAtPoint( rPoint );
m_pTableControl->setAnchor( ROW_INVALID );
bool selected = m_pTableControl->isRowSelected( curRow );
m_nCurrentRow = curRow;
return selected;
}
}
void TableFunctionSet::DeselectAtPoint( const Point& )
{
m_pTableControl->invalidateRow( m_nCurrentRow );
m_pTableControl->markRowAsDeselected( m_nCurrentRow );
}
void TableFunctionSet::DeselectAll()
{
if ( m_pTableControl->hasRowSelection() )
{
for ( size_t i=0; i<m_pTableControl->getSelectedRowCount(); ++i )
{
RowPos const rowIndex = m_pTableControl->getSelectedRowIndex(i);
m_pTableControl->invalidateRow( rowIndex );
}
m_pTableControl->markAllRowsAsDeselected();
}
}
} // namespace svt::table
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */