Avoid potential negative array index access to cached text.
[LibreOffice.git] / toolkit / source / controls / table / tablecontrol_impl.cxx
blob99c7b815ca9722c6595e7d961c4f596ad307f230
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <controls/table/tablecontrol.hxx>
22 #include <controls/table/defaultinputhandler.hxx>
23 #include <controls/table/tablemodel.hxx>
25 #include "tabledatawindow.hxx"
26 #include "tablecontrol_impl.hxx"
27 #include "tablegeometry.hxx"
29 #include <com/sun/star/accessibility/XAccessible.hpp>
30 #include <com/sun/star/accessibility/AccessibleTableModelChange.hpp>
31 #include <com/sun/star/accessibility/AccessibleEventId.hpp>
32 #include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp>
34 #include <comphelper/flagguard.hxx>
35 #include <vcl/accessiblefactory.hxx>
36 #include <vcl/toolkit/scrbar.hxx>
37 #include <vcl/seleng.hxx>
38 #include <vcl/settings.hxx>
39 #include <vcl/image.hxx>
40 #include <comphelper/diagnose_ex.hxx>
41 #include <tools/debug.hxx>
43 #include <cstdlib>
44 #include <numeric>
46 #define MIN_COLUMN_WIDTH_PIXEL 4
49 namespace svt::table
53 using ::com::sun::star::accessibility::AccessibleTableModelChange;
54 using ::com::sun::star::uno::Any;
55 using ::com::sun::star::accessibility::XAccessible;
56 using ::com::sun::star::uno::Reference;
58 namespace AccessibleEventId = ::com::sun::star::accessibility::AccessibleEventId;
59 namespace AccessibleTableModelChangeType = ::com::sun::star::accessibility::AccessibleTableModelChangeType;
62 //= SuppressCursor
64 namespace {
66 class SuppressCursor
68 private:
69 ITableControl& m_rTable;
71 public:
72 explicit SuppressCursor( ITableControl& _rTable )
73 :m_rTable( _rTable )
75 m_rTable.hideCursor();
77 ~SuppressCursor()
79 m_rTable.showCursor();
84 //= EmptyTableModel
86 /** default implementation of an ->ITableModel, used as fallback when no
87 real model is present
89 Instances of this class are static in any way, and provide the least
90 necessary default functionality for a table model.
92 class EmptyTableModel : public ITableModel
94 public:
95 EmptyTableModel()
99 // ITableModel overridables
100 virtual TableSize getColumnCount() const override
102 return 0;
104 virtual TableSize getRowCount() const override
106 return 0;
108 virtual bool hasColumnHeaders() const override
110 return false;
112 virtual bool hasRowHeaders() const override
114 return false;
116 virtual PColumnModel getColumnModel( ColPos ) override
118 OSL_FAIL( "EmptyTableModel::getColumnModel: invalid call!" );
119 return PColumnModel();
121 virtual PTableRenderer getRenderer() const override
123 return PTableRenderer();
125 virtual PTableInputHandler getInputHandler() const override
127 return PTableInputHandler();
129 virtual TableMetrics getRowHeight() const override
131 return 5 * 100;
133 virtual TableMetrics getColumnHeaderHeight() const override
135 return 0;
137 virtual TableMetrics getRowHeaderWidth() const override
139 return 0;
141 virtual ScrollbarVisibility getVerticalScrollbarVisibility() const override
143 return ScrollbarShowNever;
145 virtual ScrollbarVisibility getHorizontalScrollbarVisibility() const override
147 return ScrollbarShowNever;
149 virtual void addTableModelListener( const PTableModelListener& ) override {}
150 virtual void removeTableModelListener( const PTableModelListener& ) override {}
151 virtual ::std::optional< ::Color > getLineColor() const override
153 return ::std::optional< ::Color >();
155 virtual ::std::optional< ::Color > getHeaderBackgroundColor() const override
157 return ::std::optional< ::Color >();
159 virtual ::std::optional< ::Color > getHeaderTextColor() const override
161 return ::std::optional< ::Color >();
163 virtual ::std::optional< ::Color > getActiveSelectionBackColor() const override
165 return ::std::optional< ::Color >();
167 virtual ::std::optional< ::Color > getInactiveSelectionBackColor() const override
169 return ::std::optional< ::Color >();
171 virtual ::std::optional< ::Color > getActiveSelectionTextColor() const override
173 return ::std::optional< ::Color >();
175 virtual ::std::optional< ::Color > getInactiveSelectionTextColor() const override
177 return ::std::optional< ::Color >();
179 virtual ::std::optional< ::Color > getTextColor() const override
181 return ::std::optional< ::Color >();
183 virtual ::std::optional< ::Color > getTextLineColor() const override
185 return ::std::optional< ::Color >();
187 virtual ::std::optional< ::std::vector< ::Color > > getRowBackgroundColors() const override
189 return ::std::optional< ::std::vector< ::Color > >();
191 virtual css::style::VerticalAlignment getVerticalAlign() const override
193 return css::style::VerticalAlignment(0);
195 virtual ITableDataSort* getSortAdapter() override
197 return nullptr;
199 virtual bool isEnabled() const override
201 return true;
203 virtual void getCellContent( ColPos const, RowPos const, css::uno::Any& o_cellContent ) override
205 o_cellContent.clear();
207 virtual void getCellToolTip( ColPos const, RowPos const, css::uno::Any& ) override
210 virtual Any getRowHeading( RowPos const ) const override
212 return Any();
218 TableControl_Impl::TableControl_Impl( TableControl& _rAntiImpl )
219 :m_rAntiImpl ( _rAntiImpl )
220 ,m_pModel ( std::make_shared<EmptyTableModel>() )
221 ,m_pInputHandler ( )
222 ,m_nRowHeightPixel ( 15 )
223 ,m_nColHeaderHeightPixel( 0 )
224 ,m_nRowHeaderWidthPixel ( 0 )
225 ,m_nColumnCount ( 0 )
226 ,m_nRowCount ( 0 )
227 ,m_nCurColumn ( COL_INVALID )
228 ,m_nCurRow ( ROW_INVALID )
229 ,m_nLeftColumn ( 0 )
230 ,m_nTopRow ( 0 )
231 ,m_nCursorHidden ( 1 )
232 ,m_pDataWindow ( VclPtr<TableDataWindow>::Create( *this ) )
233 ,m_pVScroll ( nullptr )
234 ,m_pHScroll ( nullptr )
235 ,m_pScrollCorner ( nullptr )
236 ,m_aSelectedRows ( )
237 ,m_pTableFunctionSet ( new TableFunctionSet( this ) )
238 ,m_nAnchor ( -1 )
239 ,m_bUpdatingColWidths ( false )
241 m_pSelEngine.reset( new SelectionEngine( m_pDataWindow.get(), m_pTableFunctionSet.get() ) );
242 m_pSelEngine->SetSelectionMode(SelectionMode::Single);
243 m_pDataWindow->SetPosPixel( Point( 0, 0 ) );
244 m_pDataWindow->Show();
247 TableControl_Impl::~TableControl_Impl()
249 m_pVScroll.disposeAndClear();
250 m_pHScroll.disposeAndClear();
251 m_pScrollCorner.disposeAndClear();
252 m_pDataWindow.disposeAndClear();
253 m_pTableFunctionSet.reset();
254 m_pSelEngine.reset();
257 void TableControl_Impl::setModel( const PTableModel& _pModel )
259 SuppressCursor aHideCursor( *this );
261 if ( m_pModel )
262 m_pModel->removeTableModelListener( shared_from_this() );
264 m_pModel = _pModel;
265 if ( !m_pModel)
266 m_pModel = std::make_shared<EmptyTableModel>();
268 m_pModel->addTableModelListener( shared_from_this() );
270 m_nCurRow = ROW_INVALID;
271 m_nCurColumn = COL_INVALID;
273 // recalc some model-dependent cached info
274 impl_ni_updateCachedModelValues();
275 impl_ni_relayout();
277 // completely invalidate
278 m_rAntiImpl.Invalidate();
280 // reset cursor to (0,0)
281 if ( m_nRowCount ) m_nCurRow = 0;
282 if ( m_nColumnCount ) m_nCurColumn = 0;
286 namespace
288 bool lcl_adjustSelectedRows( ::std::vector< RowPos >& io_selectionIndexes, RowPos const i_firstAffectedRowIndex, TableSize const i_offset )
290 bool didChanges = false;
291 for (auto & selectionIndex : io_selectionIndexes)
293 if ( selectionIndex < i_firstAffectedRowIndex )
294 continue;
295 selectionIndex += i_offset;
296 didChanges = true;
298 return didChanges;
303 void TableControl_Impl::rowsInserted( RowPos i_first, RowPos i_last )
305 OSL_PRECOND( i_last >= i_first, "TableControl_Impl::rowsInserted: invalid row indexes!" );
307 TableSize const insertedRows = i_last - i_first + 1;
309 // adjust selection, if necessary
310 bool const selectionChanged = lcl_adjustSelectedRows( m_aSelectedRows, i_first, insertedRows );
312 // adjust our cached row count
313 m_nRowCount = m_pModel->getRowCount();
315 // if the rows have been inserted before the current row, adjust this
316 if ( i_first <= m_nCurRow )
317 goTo( m_nCurColumn, m_nCurRow + insertedRows );
319 // relayout, since the scrollbar need might have changed
320 impl_ni_relayout();
322 // notify A1YY events
323 if ( impl_isAccessibleAlive() )
325 impl_commitAccessibleEvent( AccessibleEventId::TABLE_MODEL_CHANGED,
326 Any( AccessibleTableModelChange( AccessibleTableModelChangeType::ROWS_INSERTED, i_first, i_last, -1, -1 ) )
330 // schedule repaint
331 invalidateRowRange( i_first, ROW_INVALID );
333 // call selection handlers, if necessary
334 if ( selectionChanged )
335 m_rAntiImpl.Select();
339 void TableControl_Impl::rowsRemoved( RowPos i_first, RowPos i_last )
341 sal_Int32 firstRemovedRow = i_first;
342 sal_Int32 lastRemovedRow = i_last;
344 // adjust selection, if necessary
345 bool selectionChanged = false;
346 if ( i_first == -1 )
348 selectionChanged = markAllRowsAsDeselected();
350 firstRemovedRow = 0;
351 lastRemovedRow = m_nRowCount - 1;
353 else
355 ENSURE_OR_RETURN_VOID( i_last >= i_first, "TableControl_Impl::rowsRemoved: illegal indexes!" );
357 for ( sal_Int32 row = i_first; row <= i_last; ++row )
359 if ( markRowAsDeselected( row ) )
360 selectionChanged = true;
363 if ( lcl_adjustSelectedRows( m_aSelectedRows, i_last + 1, i_first - i_last - 1 ) )
364 selectionChanged = true;
367 // adjust cached row count
368 m_nRowCount = m_pModel->getRowCount();
370 // adjust the current row, if it is larger than the row count now
371 if ( m_nCurRow >= m_nRowCount )
373 if ( m_nRowCount > 0 )
374 goTo( m_nCurColumn, m_nRowCount - 1 );
375 else
377 m_nCurRow = ROW_INVALID;
378 m_nTopRow = 0;
381 else if ( m_nRowCount == 0 )
383 m_nTopRow = 0;
387 // relayout, since the scrollbar need might have changed
388 impl_ni_relayout();
390 // notify A11Y events
391 if ( impl_isAccessibleAlive() )
393 commitTableEvent(
394 AccessibleEventId::TABLE_MODEL_CHANGED,
395 Any( AccessibleTableModelChange(
396 AccessibleTableModelChangeType::ROWS_REMOVED,
397 firstRemovedRow,
398 lastRemovedRow,
401 ) ),
402 Any()
406 // schedule a repaint
407 invalidateRowRange( firstRemovedRow, ROW_INVALID );
409 // call selection handlers, if necessary
410 if ( selectionChanged )
411 m_rAntiImpl.Select();
415 void TableControl_Impl::columnInserted()
417 m_nColumnCount = m_pModel->getColumnCount();
418 impl_ni_relayout();
420 m_rAntiImpl.Invalidate();
424 void TableControl_Impl::columnRemoved()
426 m_nColumnCount = m_pModel->getColumnCount();
428 // adjust the current column, if it is larger than the column count now
429 if ( m_nCurColumn >= m_nColumnCount )
431 if ( m_nColumnCount > 0 )
432 goTo( m_nCurColumn - 1, m_nCurRow );
433 else
434 m_nCurColumn = COL_INVALID;
437 impl_ni_relayout();
439 m_rAntiImpl.Invalidate();
443 void TableControl_Impl::allColumnsRemoved()
445 m_nColumnCount = m_pModel->getColumnCount();
446 impl_ni_relayout();
448 m_rAntiImpl.Invalidate();
452 void TableControl_Impl::cellsUpdated( RowPos const i_firstRow, RowPos const i_lastRow )
454 invalidateRowRange( i_firstRow, i_lastRow );
458 void TableControl_Impl::tableMetricsChanged()
460 impl_ni_updateCachedTableMetrics();
461 impl_ni_relayout();
462 m_rAntiImpl.Invalidate();
466 void TableControl_Impl::impl_invalidateColumn( ColPos const i_column )
468 tools::Rectangle const aAllCellsArea( impl_getAllVisibleCellsArea() );
470 const TableColumnGeometry aColumn( *this, aAllCellsArea, i_column );
471 if ( aColumn.isValid() )
472 m_rAntiImpl.Invalidate( aColumn.getRect() );
476 void TableControl_Impl::columnChanged( ColPos const i_column, ColumnAttributeGroup const i_attributeGroup )
478 ColumnAttributeGroup nGroup( i_attributeGroup );
479 if ( nGroup & ColumnAttributeGroup::APPEARANCE )
481 impl_invalidateColumn( i_column );
482 nGroup &= ~ColumnAttributeGroup::APPEARANCE;
485 if ( nGroup & ColumnAttributeGroup::WIDTH )
487 if ( !m_bUpdatingColWidths )
489 impl_ni_relayout( i_column );
490 invalidate( TableArea::All );
493 nGroup &= ~ColumnAttributeGroup::WIDTH;
496 OSL_ENSURE( ( nGroup == ColumnAttributeGroup::NONE ) || ( i_attributeGroup == ColumnAttributeGroup::ALL ),
497 "TableControl_Impl::columnChanged: don't know how to handle this change!" );
501 tools::Rectangle TableControl_Impl::impl_getAllVisibleCellsArea() const
503 tools::Rectangle aArea( Point( 0, 0 ), Size( 0, 0 ) );
505 // determine the right-most border of the last column which is
506 // at least partially visible
507 aArea.SetRight( m_nRowHeaderWidthPixel );
508 if ( !m_aColumnWidths.empty() )
510 // the number of pixels which are scrolled out of the left hand
511 // side of the window
512 const tools::Long nScrolledOutLeft = m_nLeftColumn == 0 ? 0 : m_aColumnWidths[ m_nLeftColumn - 1 ].getEnd();
514 ColumnPositions::const_reverse_iterator loop = m_aColumnWidths.rbegin();
517 aArea.SetRight(loop->getEnd() - nScrolledOutLeft);
518 ++loop;
520 while ( ( loop != m_aColumnWidths.rend() )
521 && ( loop->getEnd() - nScrolledOutLeft >= aArea.Right() )
524 // so far, aArea.Right() denotes the first pixel *after* the cell area
525 aArea.AdjustRight( -1 );
527 // determine the last row which is at least partially visible
528 aArea.SetBottom(
529 m_nColHeaderHeightPixel
530 + impl_getVisibleRows( true ) * m_nRowHeightPixel
531 - 1 );
533 return aArea;
537 tools::Rectangle TableControl_Impl::impl_getAllVisibleDataCellArea() const
539 tools::Rectangle aArea( impl_getAllVisibleCellsArea() );
540 aArea.SetLeft( m_nRowHeaderWidthPixel );
541 aArea.SetTop( m_nColHeaderHeightPixel );
542 return aArea;
546 void TableControl_Impl::impl_ni_updateCachedTableMetrics()
548 m_nRowHeightPixel = m_rAntiImpl.LogicToPixel(Size(0, m_pModel->getRowHeight()), MapMode(MapUnit::MapAppFont)).Height();
550 m_nColHeaderHeightPixel = 0;
551 if ( m_pModel->hasColumnHeaders() )
552 m_nColHeaderHeightPixel = m_rAntiImpl.LogicToPixel(Size(0, m_pModel->getColumnHeaderHeight()), MapMode(MapUnit::MapAppFont)).Height();
554 m_nRowHeaderWidthPixel = 0;
555 if ( m_pModel->hasRowHeaders() )
556 m_nRowHeaderWidthPixel = m_rAntiImpl.LogicToPixel(Size(m_pModel->getRowHeaderWidth(), 0), MapMode(MapUnit::MapAppFont)).Width();
560 void TableControl_Impl::impl_ni_updateCachedModelValues()
562 m_pInputHandler = m_pModel->getInputHandler();
563 if ( !m_pInputHandler )
564 m_pInputHandler = std::make_shared<DefaultInputHandler>();
566 m_nColumnCount = m_pModel->getColumnCount();
567 if ( m_nLeftColumn >= m_nColumnCount )
568 m_nLeftColumn = ( m_nColumnCount > 0 ) ? m_nColumnCount - 1 : 0;
570 m_nRowCount = m_pModel->getRowCount();
571 if ( m_nTopRow >= m_nRowCount )
572 m_nTopRow = ( m_nRowCount > 0 ) ? m_nRowCount - 1 : 0;
574 impl_ni_updateCachedTableMetrics();
578 namespace
581 /// determines whether a scrollbar is needed for the given values
582 bool lcl_determineScrollbarNeed( tools::Long const i_position, ScrollbarVisibility const i_visibility,
583 tools::Long const i_availableSpace, tools::Long const i_neededSpace )
585 if ( i_visibility == ScrollbarShowNever )
586 return false;
587 if ( i_visibility == ScrollbarShowAlways )
588 return true;
589 if ( i_position > 0 )
590 return true;
591 if ( i_availableSpace >= i_neededSpace )
592 return false;
593 return true;
597 void lcl_setButtonRepeat( vcl::Window& _rWindow )
599 AllSettings aSettings = _rWindow.GetSettings();
600 MouseSettings aMouseSettings = aSettings.GetMouseSettings();
602 aMouseSettings.SetButtonRepeat( 0 );
603 aSettings.SetMouseSettings( aMouseSettings );
605 _rWindow.SetSettings( aSettings, true );
609 bool lcl_updateScrollbar( vcl::Window& _rParent, VclPtr<ScrollBar>& _rpBar,
610 bool const i_needBar, tools::Long _nVisibleUnits,
611 tools::Long _nPosition, tools::Long _nRange,
612 bool _bHorizontal, const Link<ScrollBar*,void>& _rScrollHandler )
614 // do we currently have the scrollbar?
615 bool bHaveBar = _rpBar != nullptr;
617 // do we need to correct the scrollbar visibility?
618 if ( bHaveBar && !i_needBar )
620 if ( _rpBar->IsTracking() )
621 _rpBar->EndTracking();
622 _rpBar.disposeAndClear();
624 else if ( !bHaveBar && i_needBar )
626 _rpBar = VclPtr<ScrollBar>::Create(
628 &_rParent,
629 WB_DRAG | ( _bHorizontal ? WB_HSCROLL : WB_VSCROLL )
631 _rpBar->SetScrollHdl( _rScrollHandler );
632 // get some speed into the scrolling...
633 lcl_setButtonRepeat( *_rpBar );
636 if ( _rpBar )
638 _rpBar->SetRange( Range( 0, _nRange ) );
639 _rpBar->SetVisibleSize( _nVisibleUnits );
640 _rpBar->SetPageSize( _nVisibleUnits );
641 _rpBar->SetLineSize( 1 );
642 _rpBar->SetThumbPos( _nPosition );
643 _rpBar->Show();
646 return ( bHaveBar != i_needBar );
650 /** returns the number of rows fitting into the given range,
651 for the given row height. Partially fitting rows are counted, too, if the
652 respective parameter says so.
654 TableSize lcl_getRowsFittingInto( tools::Long _nOverallHeight, tools::Long _nRowHeightPixel, bool _bAcceptPartialRow )
656 return _bAcceptPartialRow
657 ? ( _nOverallHeight + ( _nRowHeightPixel - 1 ) ) / _nRowHeightPixel
658 : _nOverallHeight / _nRowHeightPixel;
662 /** returns the number of columns fitting into the given area,
663 with the first visible column as given. Partially fitting columns are counted, too,
664 if the respective parameter says so.
666 TableSize lcl_getColumnsVisibleWithin( const tools::Rectangle& _rArea, ColPos _nFirstVisibleColumn,
667 const TableControl_Impl& _rControl, bool _bAcceptPartialRow )
669 TableSize visibleColumns = 0;
670 TableColumnGeometry aColumn( _rControl, _rArea, _nFirstVisibleColumn );
671 while ( aColumn.isValid() )
673 if ( !_bAcceptPartialRow )
674 if ( aColumn.getRect().Right() > _rArea.Right() )
675 // this column is only partially visible, and this is not allowed
676 break;
678 aColumn.moveRight();
679 ++visibleColumns;
681 return visibleColumns;
687 tools::Long TableControl_Impl::impl_ni_calculateColumnWidths( ColPos const i_assumeInflexibleColumnsUpToIncluding,
688 bool const i_assumeVerticalScrollbar, ::std::vector< tools::Long >& o_newColWidthsPixel ) const
690 // the available horizontal space
691 tools::Long gridWidthPixel = m_rAntiImpl.GetOutputSizePixel().Width();
692 ENSURE_OR_RETURN( !!m_pModel, "TableControl_Impl::impl_ni_calculateColumnWidths: not allowed without a model!", gridWidthPixel );
693 if ( m_pModel->hasRowHeaders() && ( gridWidthPixel != 0 ) )
695 gridWidthPixel -= m_nRowHeaderWidthPixel;
698 if ( i_assumeVerticalScrollbar && ( m_pModel->getVerticalScrollbarVisibility() != ScrollbarShowNever ) )
700 tools::Long nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
701 gridWidthPixel -= nScrollbarMetrics;
704 // no need to do anything without columns
705 TableSize const colCount = m_pModel->getColumnCount();
706 if ( colCount == 0 )
707 return gridWidthPixel;
709 // collect some meta data for our columns:
710 // - their current (pixel) metrics
711 tools::Long accumulatedCurrentWidth = 0;
712 ::std::vector< tools::Long > currentColWidths;
713 currentColWidths.reserve( colCount );
714 typedef ::std::vector< ::std::pair< tools::Long, long > > ColumnLimits;
715 ColumnLimits effectiveColumnLimits;
716 effectiveColumnLimits.reserve( colCount );
717 tools::Long accumulatedMinWidth = 0;
718 tools::Long accumulatedMaxWidth = 0;
719 // - their relative flexibility
720 ::std::vector< ::sal_Int32 > columnFlexibilities;
721 columnFlexibilities.reserve( colCount );
722 tools::Long flexibilityDenominator = 0;
723 size_t flexibleColumnCount = 0;
724 for ( ColPos col = 0; col < colCount; ++col )
726 PColumnModel const pColumn = m_pModel->getColumnModel( col );
727 ENSURE_OR_THROW( !!pColumn, "invalid column returned by the model!" );
729 // current width
730 tools::Long const currentWidth = appFontWidthToPixel( pColumn->getWidth() );
731 currentColWidths.push_back( currentWidth );
733 // accumulated width
734 accumulatedCurrentWidth += currentWidth;
736 // flexibility
737 ::sal_Int32 flexibility = pColumn->getFlexibility();
738 OSL_ENSURE( flexibility >= 0, "TableControl_Impl::impl_ni_calculateColumnWidths: a column's flexibility should be non-negative." );
739 if ( ( flexibility < 0 ) // normalization
740 || ( !pColumn->isResizable() ) // column not resizable => no auto-resize
741 || ( col <= i_assumeInflexibleColumnsUpToIncluding ) // column shall be treated as inflexible => respect this
743 flexibility = 0;
745 // min/max width
746 tools::Long effectiveMin = currentWidth, effectiveMax = currentWidth;
747 // if the column is not flexible, it will not be asked for min/max, but we assume the current width as limit then
748 if ( flexibility > 0 )
750 tools::Long const minWidth = appFontWidthToPixel( pColumn->getMinWidth() );
751 if ( minWidth > 0 )
752 effectiveMin = minWidth;
753 else
754 effectiveMin = MIN_COLUMN_WIDTH_PIXEL;
756 tools::Long const maxWidth = appFontWidthToPixel( pColumn->getMaxWidth() );
757 OSL_ENSURE( minWidth <= maxWidth, "TableControl_Impl::impl_ni_calculateColumnWidths: pretty undecided 'bout its width limits, this column!" );
758 if ( ( maxWidth > 0 ) && ( maxWidth >= minWidth ) )
759 effectiveMax = maxWidth;
760 else
761 effectiveMax = gridWidthPixel; // TODO: any better guess here?
763 if ( effectiveMin == effectiveMax )
764 // if the min and the max are identical, this implies no flexibility at all
765 flexibility = 0;
768 columnFlexibilities.push_back( flexibility );
769 flexibilityDenominator += flexibility;
770 if ( flexibility > 0 )
771 ++flexibleColumnCount;
773 effectiveColumnLimits.emplace_back( effectiveMin, effectiveMax );
774 accumulatedMinWidth += effectiveMin;
775 accumulatedMaxWidth += effectiveMax;
778 o_newColWidthsPixel = currentColWidths;
779 if ( flexibilityDenominator == 0 )
781 // no column is flexible => don't adjust anything
783 else if ( gridWidthPixel > accumulatedCurrentWidth )
784 { // we have space to give away ...
785 tools::Long distributePixel = gridWidthPixel - accumulatedCurrentWidth;
786 if ( gridWidthPixel > accumulatedMaxWidth )
788 // ... but the column's maximal widths are still less than we have
789 // => set them all to max
790 for ( svt::table::TableSize i = 0; i < colCount; ++i )
792 o_newColWidthsPixel[i] = effectiveColumnLimits[i].second;
795 else
797 bool startOver = false;
800 startOver = false;
801 // distribute the remaining space amongst all columns with a positive flexibility
802 for ( size_t i=0; i<o_newColWidthsPixel.size() && !startOver; ++i )
804 tools::Long const columnFlexibility = columnFlexibilities[i];
805 if ( columnFlexibility == 0 )
806 continue;
808 tools::Long newColWidth = currentColWidths[i] + columnFlexibility * distributePixel / flexibilityDenominator;
810 if ( newColWidth > effectiveColumnLimits[i].second )
811 { // that was too much, we hit the col's maximum
812 // set the new width to exactly this maximum
813 newColWidth = effectiveColumnLimits[i].second;
814 // adjust the flexibility denominator ...
815 flexibilityDenominator -= columnFlexibility;
816 columnFlexibilities[i] = 0;
817 --flexibleColumnCount;
818 // ... and the remaining width ...
819 tools::Long const difference = newColWidth - currentColWidths[i];
820 distributePixel -= difference;
821 // ... this way, we ensure that the width not taken up by this column is consumed by the other
822 // flexible ones (if there are some)
824 // and start over with the first column, since there might be earlier columns which need
825 // to be recalculated now
826 startOver = true;
829 o_newColWidthsPixel[i] = newColWidth;
832 while ( startOver );
834 // are there pixels left (might be caused by rounding errors)?
835 distributePixel = gridWidthPixel - ::std::accumulate( o_newColWidthsPixel.begin(), o_newColWidthsPixel.end(), 0 );
836 while ( ( distributePixel > 0 ) && ( flexibleColumnCount > 0 ) )
838 // yes => ignore relative flexibilities, and subsequently distribute single pixels to all flexible
839 // columns which did not yet reach their maximum.
840 for ( size_t i=0; ( i < o_newColWidthsPixel.size() ) && ( distributePixel > 0 ); ++i )
842 if ( columnFlexibilities[i] == 0 )
843 continue;
845 OSL_ENSURE( o_newColWidthsPixel[i] <= effectiveColumnLimits[i].second,
846 "TableControl_Impl::impl_ni_calculateColumnWidths: inconsistency!" );
847 if ( o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first )
849 columnFlexibilities[i] = 0;
850 --flexibleColumnCount;
851 continue;
854 ++o_newColWidthsPixel[i];
855 --distributePixel;
860 else if ( gridWidthPixel < accumulatedCurrentWidth )
861 { // we need to take away some space from the columns which allow it ...
862 tools::Long takeAwayPixel = accumulatedCurrentWidth - gridWidthPixel;
863 if ( gridWidthPixel < accumulatedMinWidth )
865 // ... but the column's minimal widths are still more than we have
866 // => set them all to min
867 for ( svt::table::TableSize i = 0; i < colCount; ++i )
869 o_newColWidthsPixel[i] = effectiveColumnLimits[i].first;
872 else
874 bool startOver = false;
877 startOver = false;
878 // take away the space we need from the columns with a positive flexibility
879 for ( size_t i=0; i<o_newColWidthsPixel.size() && !startOver; ++i )
881 tools::Long const columnFlexibility = columnFlexibilities[i];
882 if ( columnFlexibility == 0 )
883 continue;
885 tools::Long newColWidth = currentColWidths[i] - columnFlexibility * takeAwayPixel / flexibilityDenominator;
887 if ( newColWidth < effectiveColumnLimits[i].first )
888 { // that was too much, we hit the col's minimum
889 // set the new width to exactly this minimum
890 newColWidth = effectiveColumnLimits[i].first;
891 // adjust the flexibility denominator ...
892 flexibilityDenominator -= columnFlexibility;
893 columnFlexibilities[i] = 0;
894 --flexibleColumnCount;
895 // ... and the remaining width ...
896 tools::Long const difference = currentColWidths[i] - newColWidth;
897 takeAwayPixel -= difference;
899 // and start over with the first column, since there might be earlier columns which need
900 // to be recalculated now
901 startOver = true;
904 o_newColWidthsPixel[i] = newColWidth;
907 while ( startOver );
909 // are there pixels left (might be caused by rounding errors)?
910 takeAwayPixel = ::std::accumulate( o_newColWidthsPixel.begin(), o_newColWidthsPixel.end(), 0 ) - gridWidthPixel;
911 while ( ( takeAwayPixel > 0 ) && ( flexibleColumnCount > 0 ) )
913 // yes => ignore relative flexibilities, and subsequently take away pixels from all flexible
914 // columns which did not yet reach their minimum.
915 for ( size_t i=0; ( i < o_newColWidthsPixel.size() ) && ( takeAwayPixel > 0 ); ++i )
917 if ( columnFlexibilities[i] == 0 )
918 continue;
920 OSL_ENSURE( o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first,
921 "TableControl_Impl::impl_ni_calculateColumnWidths: inconsistency!" );
922 if ( o_newColWidthsPixel[i] <= effectiveColumnLimits[i].first )
924 columnFlexibilities[i] = 0;
925 --flexibleColumnCount;
926 continue;
929 --o_newColWidthsPixel[i];
930 --takeAwayPixel;
936 return gridWidthPixel;
940 void TableControl_Impl::impl_ni_relayout( ColPos const i_assumeInflexibleColumnsUpToIncluding )
942 ENSURE_OR_RETURN_VOID( !m_bUpdatingColWidths, "TableControl_Impl::impl_ni_relayout: recursive call detected!" );
944 m_aColumnWidths.resize( 0 );
945 if ( !m_pModel )
946 return;
948 ::comphelper::FlagRestorationGuard const aWidthUpdateFlag( m_bUpdatingColWidths, true );
949 SuppressCursor aHideCursor( *this );
951 // layouting steps:
953 // 1. adjust column widths, leaving space for a vertical scrollbar
954 // 2. determine need for a vertical scrollbar
955 // - V-YES: all fine, result from 1. is still valid
956 // - V-NO: result from 1. is still under consideration
958 // 3. determine need for a horizontal scrollbar
959 // - H-NO: all fine, result from 2. is still valid
960 // - H-YES: reconsider need for a vertical scrollbar, if result of 2. was V-NO
961 // - V-YES: all fine, result from 1. is still valid
962 // - V-NO: redistribute the remaining space (if any) amongst all columns which allow it
964 ::std::vector< tools::Long > newWidthsPixel;
965 tools::Long gridWidthPixel = impl_ni_calculateColumnWidths( i_assumeInflexibleColumnsUpToIncluding, true, newWidthsPixel );
967 // the width/height of a scrollbar, needed several times below
968 tools::Long const nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
970 // determine the playground for the data cells (excluding headers)
971 // TODO: what if the control is smaller than needed for the headers/scrollbars?
972 tools::Rectangle aDataCellPlayground( Point( 0, 0 ), m_rAntiImpl.GetOutputSizePixel() );
973 aDataCellPlayground.SetLeft( m_nRowHeaderWidthPixel );
974 aDataCellPlayground.SetTop( m_nColHeaderHeightPixel );
976 OSL_ENSURE( ( m_nRowCount == m_pModel->getRowCount() ) && ( m_nColumnCount == m_pModel->getColumnCount() ),
977 "TableControl_Impl::impl_ni_relayout: how is this expected to work with invalid data?" );
978 tools::Long const nAllColumnsWidth = ::std::accumulate( newWidthsPixel.begin(), newWidthsPixel.end(), 0 );
980 ScrollbarVisibility const eVertScrollbar = m_pModel->getVerticalScrollbarVisibility();
981 ScrollbarVisibility const eHorzScrollbar = m_pModel->getHorizontalScrollbarVisibility();
983 // do we need a vertical scrollbar?
984 bool bNeedVerticalScrollbar = lcl_determineScrollbarNeed(
985 m_nTopRow, eVertScrollbar, aDataCellPlayground.GetHeight(), m_nRowHeightPixel * m_nRowCount );
986 bool bFirstRoundVScrollNeed = false;
987 if ( bNeedVerticalScrollbar )
989 aDataCellPlayground.AdjustRight( -nScrollbarMetrics );
990 bFirstRoundVScrollNeed = true;
993 // do we need a horizontal scrollbar?
994 bool const bNeedHorizontalScrollbar = lcl_determineScrollbarNeed(
995 m_nLeftColumn, eHorzScrollbar, aDataCellPlayground.GetWidth(), nAllColumnsWidth );
996 if ( bNeedHorizontalScrollbar )
998 aDataCellPlayground.AdjustBottom( -nScrollbarMetrics );
1000 // now that we just found that we need a horizontal scrollbar,
1001 // the need for a vertical one may have changed, since the horizontal
1002 // SB might just occupy enough space so that not all rows do fit
1003 // anymore
1004 if ( !bFirstRoundVScrollNeed )
1006 bNeedVerticalScrollbar = lcl_determineScrollbarNeed(
1007 m_nTopRow, eVertScrollbar, aDataCellPlayground.GetHeight(), m_nRowHeightPixel * m_nRowCount );
1008 if ( bNeedVerticalScrollbar )
1010 aDataCellPlayground.AdjustRight( -nScrollbarMetrics );
1015 // the initial call to impl_ni_calculateColumnWidths assumed that we need a vertical scrollbar. If, by now,
1016 // we know that this is not the case, re-calculate the column widths.
1017 if ( !bNeedVerticalScrollbar )
1018 gridWidthPixel = impl_ni_calculateColumnWidths( i_assumeInflexibleColumnsUpToIncluding, false, newWidthsPixel );
1020 // update the column objects with the new widths we finally calculated
1021 TableSize const colCount = m_pModel->getColumnCount();
1022 m_aColumnWidths.reserve( colCount );
1023 tools::Long accumulatedWidthPixel = m_nRowHeaderWidthPixel;
1024 bool anyColumnWidthChanged = false;
1025 for ( ColPos col = 0; col < colCount; ++col )
1027 const tools::Long columnStart = accumulatedWidthPixel;
1028 const tools::Long columnEnd = columnStart + newWidthsPixel[col];
1029 m_aColumnWidths.emplace_back( columnStart, columnEnd );
1030 accumulatedWidthPixel = columnEnd;
1032 // and don't forget to forward this to the column models
1033 PColumnModel const pColumn = m_pModel->getColumnModel( col );
1034 ENSURE_OR_THROW( !!pColumn, "invalid column returned by the model!" );
1036 tools::Long const oldColumnWidthAppFont = pColumn->getWidth();
1037 tools::Long const newColumnWidthAppFont = pixelWidthToAppFont( newWidthsPixel[col] );
1038 pColumn->setWidth( newColumnWidthAppFont );
1040 anyColumnWidthChanged |= ( oldColumnWidthAppFont != newColumnWidthAppFont );
1043 // if the column widths changed, ensure everything is repainted
1044 if ( anyColumnWidthChanged )
1045 invalidate( TableArea::All );
1047 // if the column resizing happened to leave some space at the right, but there are columns
1048 // scrolled out to the left, scroll them in
1049 while ( ( m_nLeftColumn > 0 )
1050 && ( accumulatedWidthPixel - m_aColumnWidths[ m_nLeftColumn - 1 ].getStart() <= gridWidthPixel )
1053 --m_nLeftColumn;
1056 // now adjust the column metrics, since they currently ignore the horizontal scroll position
1057 if ( m_nLeftColumn > 0 )
1059 const tools::Long offsetPixel = m_aColumnWidths[ 0 ].getStart() - m_aColumnWidths[ m_nLeftColumn ].getStart();
1060 for (auto & columnWidth : m_aColumnWidths)
1062 columnWidth.move( offsetPixel );
1066 // show or hide the scrollbars as needed, and position the data window
1067 impl_ni_positionChildWindows( aDataCellPlayground, bNeedVerticalScrollbar, bNeedHorizontalScrollbar );
1071 void TableControl_Impl::impl_ni_positionChildWindows( tools::Rectangle const & i_dataCellPlayground,
1072 bool const i_verticalScrollbar, bool const i_horizontalScrollbar )
1074 tools::Long const nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
1076 // create or destroy the vertical scrollbar, as needed
1077 lcl_updateScrollbar(
1078 m_rAntiImpl,
1079 m_pVScroll,
1080 i_verticalScrollbar,
1081 lcl_getRowsFittingInto( i_dataCellPlayground.GetHeight(), m_nRowHeightPixel, false ),
1082 // visible units
1083 m_nTopRow, // current position
1084 m_nRowCount, // range
1085 false, // vertical
1086 LINK( this, TableControl_Impl, OnScroll ) // scroll handler
1089 // position it
1090 if ( m_pVScroll )
1092 tools::Rectangle aScrollbarArea(
1093 Point( i_dataCellPlayground.Right() + 1, 0 ),
1094 Size( nScrollbarMetrics, i_dataCellPlayground.Bottom() + 1 )
1096 m_pVScroll->SetPosSizePixel(
1097 aScrollbarArea.TopLeft(), aScrollbarArea.GetSize() );
1100 // create or destroy the horizontal scrollbar, as needed
1101 lcl_updateScrollbar(
1102 m_rAntiImpl,
1103 m_pHScroll,
1104 i_horizontalScrollbar,
1105 lcl_getColumnsVisibleWithin( i_dataCellPlayground, m_nLeftColumn, *this, false ),
1106 // visible units
1107 m_nLeftColumn, // current position
1108 m_nColumnCount, // range
1109 true, // horizontal
1110 LINK( this, TableControl_Impl, OnScroll ) // scroll handler
1113 // position it
1114 if ( m_pHScroll )
1116 TableSize const nVisibleUnits = lcl_getColumnsVisibleWithin( i_dataCellPlayground, m_nLeftColumn, *this, false );
1117 TableMetrics const nRange = m_nColumnCount;
1118 if( m_nLeftColumn + nVisibleUnits == nRange - 1 )
1120 if ( m_aColumnWidths[ nRange - 1 ].getStart() - m_aColumnWidths[ m_nLeftColumn ].getEnd() + m_aColumnWidths[ nRange-1 ].getWidth() > i_dataCellPlayground.GetWidth() )
1122 m_pHScroll->SetVisibleSize( nVisibleUnits -1 );
1123 m_pHScroll->SetPageSize( nVisibleUnits - 1 );
1126 tools::Rectangle aScrollbarArea(
1127 Point( 0, i_dataCellPlayground.Bottom() + 1 ),
1128 Size( i_dataCellPlayground.Right() + 1, nScrollbarMetrics )
1130 m_pHScroll->SetPosSizePixel(
1131 aScrollbarArea.TopLeft(), aScrollbarArea.GetSize() );
1134 // the corner window connecting the two scrollbars in the lower right corner
1135 bool bHaveScrollCorner = nullptr != m_pScrollCorner;
1136 bool bNeedScrollCorner = ( nullptr != m_pHScroll ) && ( nullptr != m_pVScroll );
1137 if ( bHaveScrollCorner && !bNeedScrollCorner )
1139 m_pScrollCorner.disposeAndClear();
1141 else if ( !bHaveScrollCorner && bNeedScrollCorner )
1143 m_pScrollCorner = VclPtr<ScrollBarBox>::Create( &m_rAntiImpl );
1144 m_pScrollCorner->SetSizePixel( Size( nScrollbarMetrics, nScrollbarMetrics ) );
1145 m_pScrollCorner->SetPosPixel( Point( i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1 ) );
1146 m_pScrollCorner->Show();
1148 else if(bHaveScrollCorner && bNeedScrollCorner)
1150 m_pScrollCorner->SetPosPixel( Point( i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1 ) );
1151 m_pScrollCorner->Show();
1154 // resize the data window
1155 m_pDataWindow->SetSizePixel( Size(
1156 i_dataCellPlayground.GetWidth() + m_nRowHeaderWidthPixel,
1157 i_dataCellPlayground.GetHeight() + m_nColHeaderHeightPixel
1158 ) );
1162 void TableControl_Impl::onResize()
1164 impl_ni_relayout();
1165 checkCursorPosition();
1169 void TableControl_Impl::doPaintContent(vcl::RenderContext& rRenderContext, const tools::Rectangle& _rUpdateRect)
1171 if (!getModel())
1172 return;
1173 PTableRenderer pRenderer = getModel()->getRenderer();
1174 DBG_ASSERT(!!pRenderer, "TableDataWindow::doPaintContent: invalid renderer!");
1175 if (!pRenderer)
1176 return;
1178 // our current style settings, to be passed to the renderer
1179 const StyleSettings& rStyle = rRenderContext.GetSettings().GetStyleSettings();
1180 m_nRowCount = m_pModel->getRowCount();
1181 // the area occupied by all (at least partially) visible cells, including
1182 // headers
1183 tools::Rectangle const aAllCellsWithHeaders( impl_getAllVisibleCellsArea() );
1185 // draw the header column area
1186 if (m_pModel->hasColumnHeaders())
1188 TableRowGeometry const aHeaderRow(*this, tools::Rectangle(Point(0, 0), aAllCellsWithHeaders.BottomRight()), ROW_COL_HEADERS);
1189 tools::Rectangle const aColRect(aHeaderRow.getRect());
1190 pRenderer->PaintHeaderArea(rRenderContext, aColRect, true, false, rStyle);
1191 // Note that strictly, aHeaderRow.getRect() also contains the intersection between column
1192 // and row header area. However, below we go to paint this intersection, again,
1193 // so this hopefully doesn't hurt if we already paint it here.
1195 for (TableCellGeometry aCell(aHeaderRow, m_nLeftColumn); aCell.isValid(); aCell.moveRight())
1197 if (_rUpdateRect.GetIntersection(aCell.getRect()).IsEmpty())
1198 continue;
1200 pRenderer->PaintColumnHeader(aCell.getColumn(), rRenderContext, aCell.getRect(), rStyle);
1203 // the area occupied by the row header, if any
1204 tools::Rectangle aRowHeaderArea;
1205 if (m_pModel->hasRowHeaders())
1207 aRowHeaderArea = aAllCellsWithHeaders;
1208 aRowHeaderArea.SetRight( m_nRowHeaderWidthPixel - 1 );
1210 TableSize const nVisibleRows = impl_getVisibleRows(true);
1211 TableSize nActualRows = nVisibleRows;
1212 if (m_nTopRow + nActualRows > m_nRowCount)
1213 nActualRows = m_nRowCount - m_nTopRow;
1214 aRowHeaderArea.SetBottom( m_nColHeaderHeightPixel + m_nRowHeightPixel * nActualRows - 1 );
1216 pRenderer->PaintHeaderArea(rRenderContext, aRowHeaderArea, false, true, rStyle);
1217 // Note that strictly, aRowHeaderArea also contains the intersection between column
1218 // and row header area. However, below we go to paint this intersection, again,
1219 // so this hopefully doesn't hurt if we already paint it here.
1221 if (m_pModel->hasColumnHeaders())
1223 TableCellGeometry const aIntersection(*this, tools::Rectangle(Point(0, 0), aAllCellsWithHeaders.BottomRight()),
1224 COL_ROW_HEADERS, ROW_COL_HEADERS);
1225 tools::Rectangle const aInters(aIntersection.getRect());
1226 pRenderer->PaintHeaderArea(rRenderContext, aInters, true, true, rStyle);
1230 // draw the table content row by row
1231 TableSize colCount = getModel()->getColumnCount();
1233 // paint all rows
1234 tools::Rectangle const aAllDataCellsArea(impl_getAllVisibleDataCellArea());
1235 for (TableRowGeometry aRowIterator(*this, aAllCellsWithHeaders, getTopRow()); aRowIterator.isValid(); aRowIterator.moveDown())
1237 if (_rUpdateRect.GetIntersection(aRowIterator.getRect() ).IsEmpty())
1238 continue;
1240 bool const isControlFocused = m_rAntiImpl.HasControlFocus();
1241 bool const isSelectedRow = isRowSelected(aRowIterator.getRow());
1243 tools::Rectangle const aRect = aRowIterator.getRect().GetIntersection(aAllDataCellsArea);
1245 // give the renderer a chance to prepare the row
1246 pRenderer->PrepareRow(aRowIterator.getRow(), isControlFocused, isSelectedRow, rRenderContext, aRect, rStyle);
1248 // paint the row header
1249 if (m_pModel->hasRowHeaders())
1251 const tools::Rectangle aCurrentRowHeader(aRowHeaderArea.GetIntersection(aRowIterator.getRect()));
1252 pRenderer->PaintRowHeader(rRenderContext, aCurrentRowHeader, rStyle);
1255 if (!colCount)
1256 continue;
1258 // paint all cells in this row
1259 for (TableCellGeometry aCell(aRowIterator, m_nLeftColumn); aCell.isValid(); aCell.moveRight())
1261 pRenderer->PaintCell(aCell.getColumn(), isSelectedRow, isControlFocused,
1262 rRenderContext, aCell.getRect(), rStyle);
1267 void TableControl_Impl::hideCursor()
1269 if ( ++m_nCursorHidden == 1 )
1270 impl_ni_doSwitchCursor( false );
1274 void TableControl_Impl::showCursor()
1276 DBG_ASSERT( m_nCursorHidden > 0, "TableControl_Impl::showCursor: cursor not hidden!" );
1277 if ( --m_nCursorHidden == 0 )
1278 impl_ni_doSwitchCursor( true );
1282 bool TableControl_Impl::dispatchAction( TableControlAction _eAction )
1284 bool bSuccess = false;
1285 bool selectionChanged = false;
1287 switch ( _eAction )
1289 case cursorDown:
1290 if ( m_pSelEngine->GetSelectionMode() == SelectionMode::Single )
1292 //if other rows already selected, deselect them
1293 if(!m_aSelectedRows.empty())
1295 invalidateSelectedRows();
1296 m_aSelectedRows.clear();
1298 if ( m_nCurRow < m_nRowCount-1 )
1300 ++m_nCurRow;
1301 m_aSelectedRows.push_back(m_nCurRow);
1303 else
1304 m_aSelectedRows.push_back(m_nCurRow);
1305 invalidateRow( m_nCurRow );
1306 ensureVisible(m_nCurColumn,m_nCurRow);
1307 selectionChanged = true;
1308 bSuccess = true;
1310 else
1312 if ( m_nCurRow < m_nRowCount - 1 )
1313 bSuccess = goTo( m_nCurColumn, m_nCurRow + 1 );
1315 break;
1317 case cursorUp:
1318 if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
1320 if(!m_aSelectedRows.empty())
1322 invalidateSelectedRows();
1323 m_aSelectedRows.clear();
1325 if(m_nCurRow>0)
1327 --m_nCurRow;
1328 m_aSelectedRows.push_back(m_nCurRow);
1329 invalidateRow( m_nCurRow );
1331 else
1333 m_aSelectedRows.push_back(m_nCurRow);
1334 invalidateRow( m_nCurRow );
1336 ensureVisible(m_nCurColumn,m_nCurRow);
1337 selectionChanged = true;
1338 bSuccess = true;
1340 else
1342 if ( m_nCurRow > 0 )
1343 bSuccess = goTo( m_nCurColumn, m_nCurRow - 1 );
1345 break;
1346 case cursorLeft:
1347 if ( m_nCurColumn > 0 )
1348 bSuccess = goTo( m_nCurColumn - 1, m_nCurRow );
1349 else
1350 if ( ( m_nCurColumn == 0) && ( m_nCurRow > 0 ) )
1351 bSuccess = goTo( m_nColumnCount - 1, m_nCurRow - 1 );
1352 break;
1354 case cursorRight:
1355 if ( m_nCurColumn < m_nColumnCount - 1 )
1356 bSuccess = goTo( m_nCurColumn + 1, m_nCurRow );
1357 else
1358 if ( ( m_nCurColumn == m_nColumnCount - 1 ) && ( m_nCurRow < m_nRowCount - 1 ) )
1359 bSuccess = goTo( 0, m_nCurRow + 1 );
1360 break;
1362 case cursorToLineStart:
1363 bSuccess = goTo( 0, m_nCurRow );
1364 break;
1366 case cursorToLineEnd:
1367 bSuccess = goTo( m_nColumnCount - 1, m_nCurRow );
1368 break;
1370 case cursorToFirstLine:
1371 bSuccess = goTo( m_nCurColumn, 0 );
1372 break;
1374 case cursorToLastLine:
1375 bSuccess = goTo( m_nCurColumn, m_nRowCount - 1 );
1376 break;
1378 case cursorPageUp:
1380 RowPos nNewRow = ::std::max( RowPos(0), m_nCurRow - impl_getVisibleRows( false ) );
1381 bSuccess = goTo( m_nCurColumn, nNewRow );
1383 break;
1385 case cursorPageDown:
1387 RowPos nNewRow = ::std::min( m_nRowCount - 1, m_nCurRow + impl_getVisibleRows( false ) );
1388 bSuccess = goTo( m_nCurColumn, nNewRow );
1390 break;
1392 case cursorTopLeft:
1393 bSuccess = goTo( 0, 0 );
1394 break;
1396 case cursorBottomRight:
1397 bSuccess = goTo( m_nColumnCount - 1, m_nRowCount - 1 );
1398 break;
1400 case cursorSelectRow:
1402 if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
1403 return false;
1404 //pos is the position of the current row in the vector of selected rows, if current row is selected
1405 int pos = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
1406 //if current row is selected, it should be deselected, when ALT+SPACE are pressed
1407 if(pos>-1)
1409 m_aSelectedRows.erase(m_aSelectedRows.begin()+pos);
1410 if(m_aSelectedRows.empty() && m_nAnchor != -1)
1411 m_nAnchor = -1;
1413 //else select the row->put it in the vector
1414 else
1415 m_aSelectedRows.push_back(m_nCurRow);
1416 invalidateRow( m_nCurRow );
1417 selectionChanged = true;
1418 bSuccess = true;
1420 break;
1421 case cursorSelectRowUp:
1423 if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
1424 return false;
1425 else if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
1427 //if there are other selected rows, deselect them
1428 return false;
1430 else
1432 //there are other selected rows
1433 if(!m_aSelectedRows.empty())
1435 //the anchor wasn't set -> a region is not selected, that's why clear all selection
1436 //and select the current row
1437 if(m_nAnchor==-1)
1439 invalidateSelectedRows();
1440 m_aSelectedRows.clear();
1441 m_aSelectedRows.push_back(m_nCurRow);
1442 invalidateRow( m_nCurRow );
1444 else
1446 //a region is already selected, prevRow is last selected row and the row above - nextRow - should be selected
1447 int prevRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
1448 int nextRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow-1);
1449 if(prevRow>-1)
1451 //if m_nCurRow isn't the upper one, can move up, otherwise not
1452 if(m_nCurRow>0)
1453 m_nCurRow--;
1454 else
1455 return true;
1456 //if nextRow already selected, deselect it, otherwise select it
1457 if(nextRow>-1 && m_aSelectedRows[nextRow] == m_nCurRow)
1459 m_aSelectedRows.erase(m_aSelectedRows.begin()+prevRow);
1460 invalidateRow( m_nCurRow + 1 );
1462 else
1464 m_aSelectedRows.push_back(m_nCurRow);
1465 invalidateRow( m_nCurRow );
1468 else
1470 if(m_nCurRow>0)
1472 m_aSelectedRows.push_back(m_nCurRow);
1473 m_nCurRow--;
1474 m_aSelectedRows.push_back(m_nCurRow);
1475 invalidateSelectedRegion( m_nCurRow+1, m_nCurRow );
1480 else
1482 //if nothing is selected and the current row isn't the upper one
1483 //select the current and one row above
1484 //otherwise select only the upper row
1485 if(m_nCurRow>0)
1487 m_aSelectedRows.push_back(m_nCurRow);
1488 m_nCurRow--;
1489 m_aSelectedRows.push_back(m_nCurRow);
1490 invalidateSelectedRegion( m_nCurRow+1, m_nCurRow );
1492 else
1494 m_aSelectedRows.push_back(m_nCurRow);
1495 invalidateRow( m_nCurRow );
1498 m_pSelEngine->SetAnchor(true);
1499 m_nAnchor = m_nCurRow;
1500 ensureVisible(m_nCurColumn, m_nCurRow);
1501 selectionChanged = true;
1502 bSuccess = true;
1505 break;
1506 case cursorSelectRowDown:
1508 if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
1509 bSuccess = false;
1510 else if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
1512 bSuccess = false;
1514 else
1516 if(!m_aSelectedRows.empty())
1518 //the anchor wasn't set -> a region is not selected, that's why clear all selection
1519 //and select the current row
1520 if(m_nAnchor==-1)
1522 invalidateSelectedRows();
1523 m_aSelectedRows.clear();
1524 m_aSelectedRows.push_back(m_nCurRow);
1525 invalidateRow( m_nCurRow );
1527 else
1529 //a region is already selected, prevRow is last selected row and the row beneath - nextRow - should be selected
1530 int prevRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
1531 int nextRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow+1);
1532 if(prevRow>-1)
1534 //if m_nCurRow isn't the last one, can move down, otherwise not
1535 if(m_nCurRow<m_nRowCount-1)
1536 m_nCurRow++;
1537 else
1538 return true;
1539 //if next row already selected, deselect it, otherwise select it
1540 if(nextRow>-1 && m_aSelectedRows[nextRow] == m_nCurRow)
1542 m_aSelectedRows.erase(m_aSelectedRows.begin()+prevRow);
1543 invalidateRow( m_nCurRow - 1 );
1545 else
1547 m_aSelectedRows.push_back(m_nCurRow);
1548 invalidateRow( m_nCurRow );
1551 else
1553 if(m_nCurRow<m_nRowCount-1)
1555 m_aSelectedRows.push_back(m_nCurRow);
1556 m_nCurRow++;
1557 m_aSelectedRows.push_back(m_nCurRow);
1558 invalidateSelectedRegion( m_nCurRow-1, m_nCurRow );
1563 else
1565 //there wasn't any selection, select current and row beneath, otherwise only row beneath
1566 if(m_nCurRow<m_nRowCount-1)
1568 m_aSelectedRows.push_back(m_nCurRow);
1569 m_nCurRow++;
1570 m_aSelectedRows.push_back(m_nCurRow);
1571 invalidateSelectedRegion( m_nCurRow-1, m_nCurRow );
1573 else
1575 m_aSelectedRows.push_back(m_nCurRow);
1576 invalidateRow( m_nCurRow );
1579 m_pSelEngine->SetAnchor(true);
1580 m_nAnchor = m_nCurRow;
1581 ensureVisible(m_nCurColumn, m_nCurRow);
1582 selectionChanged = true;
1583 bSuccess = true;
1586 break;
1588 case cursorSelectRowAreaTop:
1590 if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
1591 bSuccess = false;
1592 else if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
1593 bSuccess = false;
1594 else
1596 //select the region between the current and the upper row
1597 RowPos iter = m_nCurRow;
1598 invalidateSelectedRegion( m_nCurRow, 0 );
1599 //put the rows in vector
1600 while(iter>=0)
1602 if ( !isRowSelected( iter ) )
1603 m_aSelectedRows.push_back(iter);
1604 --iter;
1606 m_nCurRow = 0;
1607 m_nAnchor = m_nCurRow;
1608 m_pSelEngine->SetAnchor(true);
1609 ensureVisible(m_nCurColumn, 0);
1610 selectionChanged = true;
1611 bSuccess = true;
1614 break;
1616 case cursorSelectRowAreaBottom:
1618 if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
1619 return false;
1620 else if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
1621 return false;
1622 //select the region between the current and the last row
1623 RowPos iter = m_nCurRow;
1624 invalidateSelectedRegion( m_nCurRow, m_nRowCount-1 );
1625 //put the rows in the vector
1626 while(iter<=m_nRowCount)
1628 if ( !isRowSelected( iter ) )
1629 m_aSelectedRows.push_back(iter);
1630 ++iter;
1632 m_nCurRow = m_nRowCount-1;
1633 m_nAnchor = m_nCurRow;
1634 m_pSelEngine->SetAnchor(true);
1635 ensureVisible(m_nCurColumn, m_nRowCount-1);
1636 selectionChanged = true;
1637 bSuccess = true;
1639 break;
1640 default:
1641 OSL_FAIL( "TableControl_Impl::dispatchAction: unsupported action!" );
1642 break;
1645 if ( bSuccess && selectionChanged )
1647 m_rAntiImpl.Select();
1650 return bSuccess;
1654 void TableControl_Impl::impl_ni_doSwitchCursor( bool _bShow )
1656 PTableRenderer pRenderer = m_pModel ? m_pModel->getRenderer() : PTableRenderer();
1657 if ( pRenderer )
1659 tools::Rectangle aCellRect;
1660 impl_getCellRect( m_nCurColumn, m_nCurRow, aCellRect );
1661 if ( _bShow )
1662 pRenderer->ShowCellCursor( *m_pDataWindow, aCellRect );
1663 else
1664 pRenderer->HideCellCursor( *m_pDataWindow );
1669 void TableControl_Impl::impl_getCellRect( ColPos _nColumn, RowPos _nRow, tools::Rectangle& _rCellRect ) const
1671 if ( !m_pModel
1672 || ( COL_INVALID == _nColumn )
1673 || ( ROW_INVALID == _nRow )
1676 _rCellRect.SetEmpty();
1677 return;
1680 TableCellGeometry aCell( *this, impl_getAllVisibleCellsArea(), _nColumn, _nRow );
1681 _rCellRect = aCell.getRect();
1685 RowPos TableControl_Impl::getRowAtPoint( const Point& rPoint ) const
1687 return impl_getRowForAbscissa( rPoint.Y() );
1691 ColPos TableControl_Impl::getColAtPoint( const Point& rPoint ) const
1693 return impl_getColumnForOrdinate( rPoint.X() );
1697 TableCell TableControl_Impl::hitTest( Point const & i_point ) const
1699 TableCell aCell( getColAtPoint( i_point ), getRowAtPoint( i_point ) );
1700 if ( aCell.nColumn > COL_ROW_HEADERS )
1702 PColumnModel const pColumn = m_pModel->getColumnModel( aCell.nColumn );
1703 MutableColumnMetrics const & rColInfo( m_aColumnWidths[ aCell.nColumn ] );
1704 if ( ( rColInfo.getEnd() - 3 <= i_point.X() )
1705 && ( rColInfo.getEnd() >= i_point.X() )
1706 && pColumn->isResizable()
1709 aCell.eArea = ColumnDivider;
1712 return aCell;
1716 ColumnMetrics TableControl_Impl::getColumnMetrics( ColPos const i_column ) const
1718 ENSURE_OR_RETURN( ( i_column >= 0 ) && ( i_column < m_pModel->getColumnCount() ),
1719 "TableControl_Impl::getColumnMetrics: illegal column index!", ColumnMetrics() );
1720 return m_aColumnWidths[ i_column ];
1724 PTableModel TableControl_Impl::getModel() const
1726 return m_pModel;
1730 ColPos TableControl_Impl::getCurrentColumn() const
1732 return m_nCurColumn;
1736 RowPos TableControl_Impl::getCurrentRow() const
1738 return m_nCurRow;
1742 ::Size TableControl_Impl::getTableSizePixel() const
1744 return m_pDataWindow->GetOutputSizePixel();
1748 void TableControl_Impl::setPointer( PointerStyle i_pointer )
1750 m_pDataWindow->SetPointer( i_pointer );
1754 void TableControl_Impl::captureMouse()
1756 m_pDataWindow->CaptureMouse();
1760 void TableControl_Impl::releaseMouse()
1762 m_pDataWindow->ReleaseMouse();
1766 void TableControl_Impl::invalidate( TableArea const i_what )
1768 switch ( i_what )
1770 case TableArea::ColumnHeaders:
1771 m_pDataWindow->Invalidate( calcHeaderRect( true ) );
1772 break;
1774 case TableArea::RowHeaders:
1775 m_pDataWindow->Invalidate( calcHeaderRect( false ) );
1776 break;
1778 case TableArea::All:
1779 m_pDataWindow->Invalidate();
1780 m_pDataWindow->GetParent()->Invalidate( InvalidateFlags::Transparent );
1781 break;
1786 tools::Long TableControl_Impl::pixelWidthToAppFont( tools::Long const i_pixels ) const
1788 return m_pDataWindow->PixelToLogic(Size(i_pixels, 0), MapMode(MapUnit::MapAppFont)).Width();
1792 tools::Long TableControl_Impl::appFontWidthToPixel( tools::Long const i_appFontUnits ) const
1794 return m_pDataWindow->LogicToPixel(Size(i_appFontUnits, 0), MapMode(MapUnit::MapAppFont)).Width();
1798 void TableControl_Impl::hideTracking()
1800 m_pDataWindow->HideTracking();
1804 void TableControl_Impl::showTracking( tools::Rectangle const & i_location, ShowTrackFlags const i_flags )
1806 m_pDataWindow->ShowTracking( i_location, i_flags );
1810 void TableControl_Impl::activateCell( ColPos const i_col, RowPos const i_row )
1812 goTo( i_col, i_row );
1816 void TableControl_Impl::invalidateSelectedRegion( RowPos _nPrevRow, RowPos _nCurRow )
1818 // get the visible area of the table control and set the Left and right border of the region to be repainted
1819 tools::Rectangle const aAllCells( impl_getAllVisibleCellsArea() );
1821 tools::Rectangle aInvalidateRect;
1822 aInvalidateRect.SetLeft( aAllCells.Left() );
1823 aInvalidateRect.SetRight( aAllCells.Right() );
1824 // if only one row is selected
1825 if ( _nPrevRow == _nCurRow )
1827 tools::Rectangle aCellRect;
1828 impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
1829 aInvalidateRect.SetTop( aCellRect.Top() );
1830 aInvalidateRect.SetBottom( aCellRect.Bottom() );
1832 //if the region is above the current row
1833 else if(_nPrevRow < _nCurRow )
1835 tools::Rectangle aCellRect;
1836 impl_getCellRect( m_nCurColumn, _nPrevRow, aCellRect );
1837 aInvalidateRect.SetTop( aCellRect.Top() );
1838 impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
1839 aInvalidateRect.SetBottom( aCellRect.Bottom() );
1841 //if the region is beneath the current row
1842 else
1844 tools::Rectangle aCellRect;
1845 impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
1846 aInvalidateRect.SetTop( aCellRect.Top() );
1847 impl_getCellRect( m_nCurColumn, _nPrevRow, aCellRect );
1848 aInvalidateRect.SetBottom( aCellRect.Bottom() );
1851 invalidateRect(aInvalidateRect);
1854 void TableControl_Impl::invalidateRect(const tools::Rectangle &rInvalidateRect)
1856 m_pDataWindow->Invalidate( rInvalidateRect,
1857 m_pDataWindow->GetControlBackground().IsTransparent() ? InvalidateFlags::Transparent : InvalidateFlags::NONE );
1861 void TableControl_Impl::invalidateSelectedRows()
1863 for (auto const& selectedRow : m_aSelectedRows)
1865 invalidateRow(selectedRow);
1870 void TableControl_Impl::invalidateRowRange( RowPos const i_firstRow, RowPos const i_lastRow )
1872 RowPos const firstRow = i_firstRow < m_nTopRow ? m_nTopRow : i_firstRow;
1873 RowPos const lastVisibleRow = m_nTopRow + impl_getVisibleRows( true ) - 1;
1874 RowPos const lastRow = ( ( i_lastRow == ROW_INVALID ) || ( i_lastRow > lastVisibleRow ) ) ? lastVisibleRow : i_lastRow;
1876 tools::Rectangle aInvalidateRect;
1878 tools::Rectangle const aVisibleCellsArea( impl_getAllVisibleCellsArea() );
1879 TableRowGeometry aRow( *this, aVisibleCellsArea, firstRow, true );
1880 while ( aRow.isValid() && ( aRow.getRow() <= lastRow ) )
1882 aInvalidateRect.Union( aRow.getRect() );
1883 aRow.moveDown();
1886 if ( i_lastRow == ROW_INVALID )
1887 aInvalidateRect.SetBottom( m_pDataWindow->GetOutputSizePixel().Height() );
1889 invalidateRect(aInvalidateRect);
1893 void TableControl_Impl::checkCursorPosition()
1896 TableSize nVisibleRows = impl_getVisibleRows(true);
1897 TableSize nVisibleCols = impl_getVisibleColumns(true);
1898 if ( ( m_nTopRow + nVisibleRows > m_nRowCount )
1899 && ( m_nRowCount >= nVisibleRows )
1902 --m_nTopRow;
1904 else
1906 m_nTopRow = 0;
1909 if ( ( m_nLeftColumn + nVisibleCols > m_nColumnCount )
1910 && ( m_nColumnCount >= nVisibleCols )
1913 --m_nLeftColumn;
1915 else
1917 m_nLeftColumn = 0;
1920 m_pDataWindow->Invalidate();
1924 TableSize TableControl_Impl::impl_getVisibleRows( bool _bAcceptPartialRow ) const
1926 DBG_ASSERT( m_pDataWindow, "TableControl_Impl::impl_getVisibleRows: no data window!" );
1928 return lcl_getRowsFittingInto(
1929 m_pDataWindow->GetOutputSizePixel().Height() - m_nColHeaderHeightPixel,
1930 m_nRowHeightPixel,
1931 _bAcceptPartialRow
1936 TableSize TableControl_Impl::impl_getVisibleColumns( bool _bAcceptPartialCol ) const
1938 DBG_ASSERT( m_pDataWindow, "TableControl_Impl::impl_getVisibleColumns: no data window!" );
1940 return lcl_getColumnsVisibleWithin(
1941 tools::Rectangle( Point( 0, 0 ), m_pDataWindow->GetOutputSizePixel() ),
1942 m_nLeftColumn,
1943 *this,
1944 _bAcceptPartialCol
1949 bool TableControl_Impl::goTo( ColPos _nColumn, RowPos _nRow )
1951 // TODO: give veto listeners a chance
1953 if ( ( _nColumn < 0 ) || ( _nColumn >= m_nColumnCount )
1954 || ( _nRow < 0 ) || ( _nRow >= m_nRowCount )
1957 OSL_ENSURE( false, "TableControl_Impl::goTo: invalid row or column index!" );
1958 return false;
1961 SuppressCursor aHideCursor( *this );
1962 m_nCurColumn = _nColumn;
1963 m_nCurRow = _nRow;
1965 // ensure that the new cell is visible
1966 ensureVisible( m_nCurColumn, m_nCurRow );
1967 return true;
1971 void TableControl_Impl::ensureVisible( ColPos _nColumn, RowPos _nRow )
1973 DBG_ASSERT( ( _nColumn >= 0 ) && ( _nColumn < m_nColumnCount )
1974 && ( _nRow >= 0 ) && ( _nRow < m_nRowCount ),
1975 "TableControl_Impl::ensureVisible: invalid coordinates!" );
1977 SuppressCursor aHideCursor( *this );
1979 if ( _nColumn < m_nLeftColumn )
1980 impl_scrollColumns( _nColumn - m_nLeftColumn );
1981 else
1983 TableSize nVisibleColumns = impl_getVisibleColumns( false/*bAcceptPartialVisibility*/ );
1984 if ( _nColumn > m_nLeftColumn + nVisibleColumns - 1 )
1986 impl_scrollColumns( _nColumn - ( m_nLeftColumn + nVisibleColumns - 1 ) );
1987 // TODO: since not all columns have the same width, this might in theory result
1988 // in the column still not being visible.
1992 if ( _nRow < m_nTopRow )
1993 impl_scrollRows( _nRow - m_nTopRow );
1994 else
1996 TableSize nVisibleRows = impl_getVisibleRows( false/*_bAcceptPartialVisibility*/ );
1997 if ( _nRow > m_nTopRow + nVisibleRows - 1 )
1998 impl_scrollRows( _nRow - ( m_nTopRow + nVisibleRows - 1 ) );
2003 OUString TableControl_Impl::getCellContentAsString( RowPos const i_row, ColPos const i_col )
2005 Any aCellValue;
2006 m_pModel->getCellContent( i_col, i_row, aCellValue );
2008 OUString sCellStringContent;
2009 m_pModel->getRenderer()->GetFormattedCellString( aCellValue, sCellStringContent );
2011 return sCellStringContent;
2015 TableSize TableControl_Impl::impl_ni_ScrollRows( TableSize _nRowDelta )
2017 // compute new top row
2018 RowPos nNewTopRow =
2019 ::std::max(
2020 ::std::min( static_cast<RowPos>( m_nTopRow + _nRowDelta ), static_cast<RowPos>( m_nRowCount - 1 ) ),
2021 RowPos(0)
2024 RowPos nOldTopRow = m_nTopRow;
2025 m_nTopRow = nNewTopRow;
2027 // if updates are enabled currently, scroll the viewport
2028 if ( m_nTopRow != nOldTopRow )
2030 SuppressCursor aHideCursor( *this );
2031 // TODO: call an onStartScroll at our listener (or better an own onStartScroll,
2032 // which hides the cursor and then calls the listener)
2033 // Same for onEndScroll
2035 // scroll the view port, if possible
2036 tools::Long nPixelDelta = m_nRowHeightPixel * ( m_nTopRow - nOldTopRow );
2038 tools::Rectangle aDataArea( Point( 0, m_nColHeaderHeightPixel ), m_pDataWindow->GetOutputSizePixel() );
2040 if ( m_pDataWindow->GetBackground().IsScrollable()
2041 && std::abs( nPixelDelta ) < aDataArea.GetHeight()
2044 m_pDataWindow->Scroll( 0, static_cast<tools::Long>(-nPixelDelta), aDataArea, ScrollFlags::Clip | ScrollFlags::Update | ScrollFlags::Children);
2046 else
2048 m_pDataWindow->Invalidate( InvalidateFlags::Update );
2049 m_pDataWindow->GetParent()->Invalidate( InvalidateFlags::Transparent );
2052 // update the position at the vertical scrollbar
2053 if ( m_pVScroll != nullptr )
2054 m_pVScroll->SetThumbPos( m_nTopRow );
2057 // The scroll bar availability might change when we scrolled.
2058 // For instance, imagine a view with 10 rows, if which 5 fit into the window, numbered 1 to 10.
2059 // Now let
2060 // - the user scroll to row number 6, so the last 5 rows are visible
2061 // - somebody remove the last 4 rows
2062 // - the user scroll to row number 5 being the top row, so the last two rows are visible
2063 // - somebody remove row number 6
2064 // - the user scroll to row number 1
2065 // => in this case, the need for the scrollbar vanishes immediately.
2066 if ( m_nTopRow == 0 )
2067 m_rAntiImpl.PostUserEvent( LINK( this, TableControl_Impl, OnUpdateScrollbars ) );
2069 return static_cast<TableSize>( m_nTopRow - nOldTopRow );
2073 TableSize TableControl_Impl::impl_scrollRows( TableSize const i_rowDelta )
2075 return impl_ni_ScrollRows( i_rowDelta );
2079 TableSize TableControl_Impl::impl_ni_ScrollColumns( TableSize _nColumnDelta )
2081 // compute new left column
2082 const ColPos nNewLeftColumn =
2083 ::std::max(
2084 ::std::min( static_cast<ColPos>( m_nLeftColumn + _nColumnDelta ), static_cast<ColPos>( m_nColumnCount - 1 ) ),
2085 ColPos(0)
2088 const ColPos nOldLeftColumn = m_nLeftColumn;
2089 m_nLeftColumn = nNewLeftColumn;
2091 // if updates are enabled currently, scroll the viewport
2092 if ( m_nLeftColumn != nOldLeftColumn )
2094 SuppressCursor aHideCursor( *this );
2095 // TODO: call an onStartScroll at our listener (or better an own onStartScroll,
2096 // which hides the cursor and then calls the listener)
2097 // Same for onEndScroll
2099 // scroll the view port, if possible
2100 const tools::Rectangle aDataArea( Point( m_nRowHeaderWidthPixel, 0 ), m_pDataWindow->GetOutputSizePixel() );
2102 tools::Long nPixelDelta =
2103 m_aColumnWidths[ nOldLeftColumn ].getStart()
2104 - m_aColumnWidths[ m_nLeftColumn ].getStart();
2106 // update our column positions
2107 // Do this *before* scrolling, as ScrollFlags::Update will trigger a paint, which already needs the correct
2108 // information in m_aColumnWidths
2109 for (auto & columnWidth : m_aColumnWidths)
2111 columnWidth.move(nPixelDelta);
2114 // scroll the window content (if supported and possible), or invalidate the complete window
2115 if ( m_pDataWindow->GetBackground().IsScrollable()
2116 && std::abs( nPixelDelta ) < aDataArea.GetWidth()
2119 m_pDataWindow->Scroll( nPixelDelta, 0, aDataArea, ScrollFlags::Clip | ScrollFlags::Update );
2121 else
2123 m_pDataWindow->Invalidate( InvalidateFlags::Update );
2124 m_pDataWindow->GetParent()->Invalidate( InvalidateFlags::Transparent );
2127 // update the position at the horizontal scrollbar
2128 if ( m_pHScroll != nullptr )
2129 m_pHScroll->SetThumbPos( m_nLeftColumn );
2132 // The scroll bar availability might change when we scrolled. This is because we do not hide
2133 // the scrollbar when it is, in theory, unnecessary, but currently at a position > 0. In this case, it will
2134 // be auto-hidden when it's scrolled back to pos 0.
2135 if ( m_nLeftColumn == 0 )
2136 m_rAntiImpl.PostUserEvent( LINK( this, TableControl_Impl, OnUpdateScrollbars ) );
2138 return static_cast<TableSize>( m_nLeftColumn - nOldLeftColumn );
2142 TableSize TableControl_Impl::impl_scrollColumns( TableSize const i_columnDelta )
2144 return impl_ni_ScrollColumns( i_columnDelta );
2148 SelectionEngine* TableControl_Impl::getSelEngine()
2150 return m_pSelEngine.get();
2153 bool TableControl_Impl::isRowSelected( RowPos i_row ) const
2155 return ::std::find( m_aSelectedRows.begin(), m_aSelectedRows.end(), i_row ) != m_aSelectedRows.end();
2159 RowPos TableControl_Impl::getSelectedRowIndex( size_t const i_selectionIndex ) const
2161 if ( i_selectionIndex < m_aSelectedRows.size() )
2162 return m_aSelectedRows[ i_selectionIndex ];
2163 return ROW_INVALID;
2167 int TableControl_Impl::getRowSelectedNumber(const ::std::vector<RowPos>& selectedRows, RowPos current)
2169 std::vector<RowPos>::const_iterator it = ::std::find(selectedRows.begin(),selectedRows.end(),current);
2170 if ( it != selectedRows.end() )
2172 return it - selectedRows.begin();
2174 return -1;
2178 ColPos TableControl_Impl::impl_getColumnForOrdinate( tools::Long const i_ordinate ) const
2180 if ( ( m_aColumnWidths.empty() ) || ( i_ordinate < 0 ) )
2181 return COL_INVALID;
2183 if ( i_ordinate < m_nRowHeaderWidthPixel )
2184 return COL_ROW_HEADERS;
2186 ColumnPositions::const_iterator lowerBound = ::std::lower_bound(
2187 m_aColumnWidths.begin(),
2188 m_aColumnWidths.end(),
2189 MutableColumnMetrics(i_ordinate+1, i_ordinate+1),
2190 ColumnInfoPositionLess()
2192 if ( lowerBound == m_aColumnWidths.end() )
2194 // point is *behind* the start of the last column ...
2195 if ( i_ordinate < m_aColumnWidths.rbegin()->getEnd() )
2196 // ... but still before its end
2197 return m_nColumnCount - 1;
2198 return COL_INVALID;
2200 return lowerBound - m_aColumnWidths.begin();
2204 RowPos TableControl_Impl::impl_getRowForAbscissa( tools::Long const i_abscissa ) const
2206 if ( i_abscissa < 0 )
2207 return ROW_INVALID;
2209 if ( i_abscissa < m_nColHeaderHeightPixel )
2210 return ROW_COL_HEADERS;
2212 tools::Long const abscissa = i_abscissa - m_nColHeaderHeightPixel;
2213 tools::Long const row = m_nTopRow + abscissa / m_nRowHeightPixel;
2214 return row < m_pModel->getRowCount() ? row : ROW_INVALID;
2218 bool TableControl_Impl::markRowAsDeselected( RowPos const i_rowIndex )
2220 ::std::vector< RowPos >::iterator selPos = ::std::find( m_aSelectedRows.begin(), m_aSelectedRows.end(), i_rowIndex );
2221 if ( selPos == m_aSelectedRows.end() )
2222 return false;
2224 m_aSelectedRows.erase( selPos );
2225 return true;
2229 bool TableControl_Impl::markRowAsSelected( RowPos const i_rowIndex )
2231 if ( isRowSelected( i_rowIndex ) )
2232 return false;
2234 SelectionMode const eSelMode = getSelEngine()->GetSelectionMode();
2235 switch ( eSelMode )
2237 case SelectionMode::Single:
2238 if ( !m_aSelectedRows.empty() )
2240 OSL_ENSURE( m_aSelectedRows.size() == 1, "TableControl::markRowAsSelected: SingleSelection with more than one selected element?" );
2241 m_aSelectedRows[0] = i_rowIndex;
2242 break;
2244 [[fallthrough]];
2246 case SelectionMode::Multiple:
2247 m_aSelectedRows.push_back( i_rowIndex );
2248 break;
2250 default:
2251 OSL_ENSURE( false, "TableControl_Impl::markRowAsSelected: unsupported selection mode!" );
2252 return false;
2255 return true;
2259 bool TableControl_Impl::markAllRowsAsDeselected()
2261 if ( m_aSelectedRows.empty() )
2262 return false;
2264 m_aSelectedRows.clear();
2265 return true;
2269 bool TableControl_Impl::markAllRowsAsSelected()
2271 SelectionMode const eSelMode = getSelEngine()->GetSelectionMode();
2272 ENSURE_OR_RETURN_FALSE( eSelMode == SelectionMode::Multiple, "TableControl_Impl::markAllRowsAsSelected: unsupported selection mode!" );
2274 if ( m_aSelectedRows.size() == size_t( m_pModel->getRowCount() ) )
2276 #if OSL_DEBUG_LEVEL > 0
2277 for ( TableSize row = 0; row < m_pModel->getRowCount(); ++row )
2279 OSL_ENSURE( isRowSelected( row ), "TableControl_Impl::markAllRowsAsSelected: inconsistency in the selected rows!" );
2281 #endif
2282 // already all rows marked as selected
2283 return false;
2286 m_aSelectedRows.clear();
2287 for ( RowPos i=0; i < m_pModel->getRowCount(); ++i )
2288 m_aSelectedRows.push_back(i);
2290 return true;
2294 void TableControl_Impl::commitAccessibleEvent( sal_Int16 const i_eventID )
2296 impl_commitAccessibleEvent( i_eventID, Any() );
2300 void TableControl_Impl::commitCellEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue )
2302 if ( impl_isAccessibleAlive() )
2303 m_pAccessibleTable->commitCellEvent( i_eventID, i_newValue, i_oldValue );
2307 void TableControl_Impl::commitTableEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue )
2309 if ( impl_isAccessibleAlive() )
2310 m_pAccessibleTable->commitTableEvent( i_eventID, i_newValue, i_oldValue );
2314 tools::Rectangle TableControl_Impl::calcHeaderRect(bool bColHeader)
2316 tools::Rectangle const aRectTableWithHeaders( impl_getAllVisibleCellsArea() );
2317 Size const aSizeTableWithHeaders( aRectTableWithHeaders.GetSize() );
2318 if ( bColHeader )
2319 return tools::Rectangle( aRectTableWithHeaders.TopLeft(), Size( aSizeTableWithHeaders.Width(), m_nColHeaderHeightPixel ) );
2320 else
2321 return tools::Rectangle( aRectTableWithHeaders.TopLeft(), Size( m_nRowHeaderWidthPixel, aSizeTableWithHeaders.Height() ) );
2325 tools::Rectangle TableControl_Impl::calcHeaderCellRect( bool bColHeader, sal_Int32 nPos )
2327 tools::Rectangle const aHeaderRect = calcHeaderRect( bColHeader );
2328 TableCellGeometry const aGeometry(
2329 *this, aHeaderRect,
2330 bColHeader ? nPos : COL_ROW_HEADERS,
2331 bColHeader ? ROW_COL_HEADERS : nPos
2333 return aGeometry.getRect();
2337 tools::Rectangle TableControl_Impl::calcTableRect() const
2339 return impl_getAllVisibleDataCellArea();
2343 tools::Rectangle TableControl_Impl::calcCellRect( sal_Int32 nRow, sal_Int32 nCol ) const
2345 tools::Rectangle aCellRect;
2346 impl_getCellRect( nRow, nCol, aCellRect );
2347 return aCellRect;
2351 IMPL_LINK_NOARG( TableControl_Impl, OnUpdateScrollbars, void*, void )
2353 // TODO: can't we simply use lcl_updateScrollbar here, so the scrollbars ranges are updated, instead of
2354 // doing a complete re-layout?
2355 impl_ni_relayout();
2359 IMPL_LINK( TableControl_Impl, OnScroll, ScrollBar*, _pScrollbar, void )
2361 DBG_ASSERT( ( _pScrollbar == m_pVScroll ) || ( _pScrollbar == m_pHScroll ),
2362 "TableControl_Impl::OnScroll: where did this come from?" );
2364 if ( _pScrollbar == m_pVScroll )
2365 impl_ni_ScrollRows( _pScrollbar->GetDelta() );
2366 else
2367 impl_ni_ScrollColumns( _pScrollbar->GetDelta() );
2371 rtl::Reference<vcl::table::IAccessibleTableControl> TableControl_Impl::getAccessible( vcl::Window& i_parentWindow )
2373 if (m_pAccessibleTable)
2374 return m_pAccessibleTable;
2376 DBG_TESTSOLARMUTEX();
2377 if ( m_pAccessibleTable == nullptr )
2379 Reference< XAccessible > const xAccParent = i_parentWindow.GetAccessible();
2380 if ( xAccParent.is() )
2382 m_pAccessibleTable = m_aFactoryAccess.getFactory().createAccessibleTableControl(
2383 xAccParent, m_rAntiImpl
2388 return m_pAccessibleTable;
2392 void TableControl_Impl::disposeAccessible()
2394 if ( m_pAccessibleTable )
2395 m_pAccessibleTable->DisposeAccessImpl();
2396 m_pAccessibleTable = nullptr;
2400 bool TableControl_Impl::impl_isAccessibleAlive() const
2402 return m_pAccessibleTable && m_pAccessibleTable->isAlive();
2406 void TableControl_Impl::impl_commitAccessibleEvent( sal_Int16 const i_eventID, Any const & i_newValue )
2408 if ( impl_isAccessibleAlive() )
2409 m_pAccessibleTable->commitEvent( i_eventID, i_newValue );
2413 //= TableFunctionSet
2416 TableFunctionSet::TableFunctionSet(TableControl_Impl* _pTableControl)
2417 :m_pTableControl( _pTableControl)
2418 ,m_nCurrentRow( ROW_INVALID )
2422 TableFunctionSet::~TableFunctionSet()
2426 void TableFunctionSet::BeginDrag()
2430 void TableFunctionSet::CreateAnchor()
2432 m_pTableControl->setAnchor( m_pTableControl->getCurRow() );
2436 void TableFunctionSet::DestroyAnchor()
2438 m_pTableControl->setAnchor( ROW_INVALID );
2442 void TableFunctionSet::SetCursorAtPoint(const Point& rPoint, bool bDontSelectAtCursor)
2444 // newRow is the row which includes the point, getCurRow() is the last selected row, before the mouse click
2445 RowPos newRow = m_pTableControl->getRowAtPoint( rPoint );
2446 if ( newRow == ROW_COL_HEADERS )
2447 newRow = m_pTableControl->getTopRow();
2449 ColPos newCol = m_pTableControl->getColAtPoint( rPoint );
2450 if ( newCol == COL_ROW_HEADERS )
2451 newCol = m_pTableControl->getLeftColumn();
2453 if ( ( newRow == ROW_INVALID ) || ( newCol == COL_INVALID ) )
2454 return;
2456 if ( bDontSelectAtCursor )
2458 if ( m_pTableControl->getSelectedRowCount() > 1 )
2459 m_pTableControl->getSelEngine()->AddAlways(true);
2461 else if ( m_pTableControl->getAnchor() == m_pTableControl->getCurRow() )
2463 //selected region lies above the last selection
2464 if( m_pTableControl->getCurRow() >= newRow)
2466 //put selected rows in vector
2467 while ( m_pTableControl->getAnchor() >= newRow )
2469 m_pTableControl->markRowAsSelected( m_pTableControl->getAnchor() );
2470 m_pTableControl->setAnchor( m_pTableControl->getAnchor() - 1 );
2472 m_pTableControl->setAnchor( m_pTableControl->getAnchor() + 1 );
2474 //selected region lies beneath the last selected row
2475 else
2477 while ( m_pTableControl->getAnchor() <= newRow )
2479 m_pTableControl->markRowAsSelected( m_pTableControl->getAnchor() );
2480 m_pTableControl->setAnchor( m_pTableControl->getAnchor() + 1 );
2482 m_pTableControl->setAnchor( m_pTableControl->getAnchor() - 1 );
2484 m_pTableControl->invalidateSelectedRegion( m_pTableControl->getCurRow(), newRow );
2486 //no region selected
2487 else
2489 if ( !m_pTableControl->hasRowSelection() )
2490 m_pTableControl->markRowAsSelected( newRow );
2491 else
2493 if ( m_pTableControl->getSelEngine()->GetSelectionMode() == SelectionMode::Single )
2495 DeselectAll();
2496 m_pTableControl->markRowAsSelected( newRow );
2498 else
2500 m_pTableControl->markRowAsSelected( newRow );
2503 if ( m_pTableControl->getSelectedRowCount() > 1 && m_pTableControl->getSelEngine()->GetSelectionMode() != SelectionMode::Single )
2504 m_pTableControl->getSelEngine()->AddAlways(true);
2506 m_pTableControl->invalidateRow( newRow );
2508 m_pTableControl->goTo( newCol, newRow );
2511 bool TableFunctionSet::IsSelectionAtPoint( const Point& rPoint )
2513 m_pTableControl->getSelEngine()->AddAlways(false);
2514 if ( !m_pTableControl->hasRowSelection() )
2515 return false;
2516 else
2518 RowPos curRow = m_pTableControl->getRowAtPoint( rPoint );
2519 m_pTableControl->setAnchor( ROW_INVALID );
2520 bool selected = m_pTableControl->isRowSelected( curRow );
2521 m_nCurrentRow = curRow;
2522 return selected;
2526 void TableFunctionSet::DeselectAtPoint( const Point& )
2528 m_pTableControl->invalidateRow( m_nCurrentRow );
2529 m_pTableControl->markRowAsDeselected( m_nCurrentRow );
2533 void TableFunctionSet::DeselectAll()
2535 if ( m_pTableControl->hasRowSelection() )
2537 for ( size_t i=0; i<m_pTableControl->getSelectedRowCount(); ++i )
2539 RowPos const rowIndex = m_pTableControl->getSelectedRowIndex(i);
2540 m_pTableControl->invalidateRow( rowIndex );
2543 m_pTableControl->markAllRowsAsDeselected();
2548 } // namespace svt::table
2551 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */