android: Update app icon to new startcenter icon
[LibreOffice.git] / toolkit / source / controls / table / tablecontrol_impl.cxx
blobc7306a07914721d0c5d1d5594ed92ba8f2cb43f4
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 )
240 ,m_pAccessibleTable ( nullptr )
242 m_pSelEngine.reset( new SelectionEngine( m_pDataWindow.get(), m_pTableFunctionSet.get() ) );
243 m_pSelEngine->SetSelectionMode(SelectionMode::Single);
244 m_pDataWindow->SetPosPixel( Point( 0, 0 ) );
245 m_pDataWindow->Show();
248 TableControl_Impl::~TableControl_Impl()
250 m_pVScroll.disposeAndClear();
251 m_pHScroll.disposeAndClear();
252 m_pScrollCorner.disposeAndClear();
253 m_pDataWindow.disposeAndClear();
254 m_pTableFunctionSet.reset();
255 m_pSelEngine.reset();
258 void TableControl_Impl::setModel( const PTableModel& _pModel )
260 SuppressCursor aHideCursor( *this );
262 if ( m_pModel )
263 m_pModel->removeTableModelListener( shared_from_this() );
265 m_pModel = _pModel;
266 if ( !m_pModel)
267 m_pModel = std::make_shared<EmptyTableModel>();
269 m_pModel->addTableModelListener( shared_from_this() );
271 m_nCurRow = ROW_INVALID;
272 m_nCurColumn = COL_INVALID;
274 // recalc some model-dependent cached info
275 impl_ni_updateCachedModelValues();
276 impl_ni_relayout();
278 // completely invalidate
279 m_rAntiImpl.Invalidate();
281 // reset cursor to (0,0)
282 if ( m_nRowCount ) m_nCurRow = 0;
283 if ( m_nColumnCount ) m_nCurColumn = 0;
287 namespace
289 bool lcl_adjustSelectedRows( ::std::vector< RowPos >& io_selectionIndexes, RowPos const i_firstAffectedRowIndex, TableSize const i_offset )
291 bool didChanges = false;
292 for (auto & selectionIndex : io_selectionIndexes)
294 if ( selectionIndex < i_firstAffectedRowIndex )
295 continue;
296 selectionIndex += i_offset;
297 didChanges = true;
299 return didChanges;
304 void TableControl_Impl::rowsInserted( RowPos i_first, RowPos i_last )
306 OSL_PRECOND( i_last >= i_first, "TableControl_Impl::rowsInserted: invalid row indexes!" );
308 TableSize const insertedRows = i_last - i_first + 1;
310 // adjust selection, if necessary
311 bool const selectionChanged = lcl_adjustSelectedRows( m_aSelectedRows, i_first, insertedRows );
313 // adjust our cached row count
314 m_nRowCount = m_pModel->getRowCount();
316 // if the rows have been inserted before the current row, adjust this
317 if ( i_first <= m_nCurRow )
318 goTo( m_nCurColumn, m_nCurRow + insertedRows );
320 // relayout, since the scrollbar need might have changed
321 impl_ni_relayout();
323 // notify A1YY events
324 if ( impl_isAccessibleAlive() )
326 impl_commitAccessibleEvent( AccessibleEventId::TABLE_MODEL_CHANGED,
327 Any( AccessibleTableModelChange( AccessibleTableModelChangeType::ROWS_INSERTED, i_first, i_last, -1, -1 ) )
331 // schedule repaint
332 invalidateRowRange( i_first, ROW_INVALID );
334 // call selection handlers, if necessary
335 if ( selectionChanged )
336 m_rAntiImpl.Select();
340 void TableControl_Impl::rowsRemoved( RowPos i_first, RowPos i_last )
342 sal_Int32 firstRemovedRow = i_first;
343 sal_Int32 lastRemovedRow = i_last;
345 // adjust selection, if necessary
346 bool selectionChanged = false;
347 if ( i_first == -1 )
349 selectionChanged = markAllRowsAsDeselected();
351 firstRemovedRow = 0;
352 lastRemovedRow = m_nRowCount - 1;
354 else
356 ENSURE_OR_RETURN_VOID( i_last >= i_first, "TableControl_Impl::rowsRemoved: illegal indexes!" );
358 for ( sal_Int32 row = i_first; row <= i_last; ++row )
360 if ( markRowAsDeselected( row ) )
361 selectionChanged = true;
364 if ( lcl_adjustSelectedRows( m_aSelectedRows, i_last + 1, i_first - i_last - 1 ) )
365 selectionChanged = true;
368 // adjust cached row count
369 m_nRowCount = m_pModel->getRowCount();
371 // adjust the current row, if it is larger than the row count now
372 if ( m_nCurRow >= m_nRowCount )
374 if ( m_nRowCount > 0 )
375 goTo( m_nCurColumn, m_nRowCount - 1 );
376 else
378 m_nCurRow = ROW_INVALID;
379 m_nTopRow = 0;
382 else if ( m_nRowCount == 0 )
384 m_nTopRow = 0;
388 // relayout, since the scrollbar need might have changed
389 impl_ni_relayout();
391 // notify A11Y events
392 if ( impl_isAccessibleAlive() )
394 commitTableEvent(
395 AccessibleEventId::TABLE_MODEL_CHANGED,
396 Any( AccessibleTableModelChange(
397 AccessibleTableModelChangeType::ROWS_REMOVED,
398 firstRemovedRow,
399 lastRemovedRow,
402 ) ),
403 Any()
407 // schedule a repaint
408 invalidateRowRange( firstRemovedRow, ROW_INVALID );
410 // call selection handlers, if necessary
411 if ( selectionChanged )
412 m_rAntiImpl.Select();
416 void TableControl_Impl::columnInserted()
418 m_nColumnCount = m_pModel->getColumnCount();
419 impl_ni_relayout();
421 m_rAntiImpl.Invalidate();
425 void TableControl_Impl::columnRemoved()
427 m_nColumnCount = m_pModel->getColumnCount();
429 // adjust the current column, if it is larger than the column count now
430 if ( m_nCurColumn >= m_nColumnCount )
432 if ( m_nColumnCount > 0 )
433 goTo( m_nCurColumn - 1, m_nCurRow );
434 else
435 m_nCurColumn = COL_INVALID;
438 impl_ni_relayout();
440 m_rAntiImpl.Invalidate();
444 void TableControl_Impl::allColumnsRemoved()
446 m_nColumnCount = m_pModel->getColumnCount();
447 impl_ni_relayout();
449 m_rAntiImpl.Invalidate();
453 void TableControl_Impl::cellsUpdated( RowPos const i_firstRow, RowPos const i_lastRow )
455 invalidateRowRange( i_firstRow, i_lastRow );
459 void TableControl_Impl::tableMetricsChanged()
461 impl_ni_updateCachedTableMetrics();
462 impl_ni_relayout();
463 m_rAntiImpl.Invalidate();
467 void TableControl_Impl::impl_invalidateColumn( ColPos const i_column )
469 tools::Rectangle const aAllCellsArea( impl_getAllVisibleCellsArea() );
471 const TableColumnGeometry aColumn( *this, aAllCellsArea, i_column );
472 if ( aColumn.isValid() )
473 m_rAntiImpl.Invalidate( aColumn.getRect() );
477 void TableControl_Impl::columnChanged( ColPos const i_column, ColumnAttributeGroup const i_attributeGroup )
479 ColumnAttributeGroup nGroup( i_attributeGroup );
480 if ( nGroup & ColumnAttributeGroup::APPEARANCE )
482 impl_invalidateColumn( i_column );
483 nGroup &= ~ColumnAttributeGroup::APPEARANCE;
486 if ( nGroup & ColumnAttributeGroup::WIDTH )
488 if ( !m_bUpdatingColWidths )
490 impl_ni_relayout( i_column );
491 invalidate( TableArea::All );
494 nGroup &= ~ColumnAttributeGroup::WIDTH;
497 OSL_ENSURE( ( nGroup == ColumnAttributeGroup::NONE ) || ( i_attributeGroup == ColumnAttributeGroup::ALL ),
498 "TableControl_Impl::columnChanged: don't know how to handle this change!" );
502 tools::Rectangle TableControl_Impl::impl_getAllVisibleCellsArea() const
504 tools::Rectangle aArea( Point( 0, 0 ), Size( 0, 0 ) );
506 // determine the right-most border of the last column which is
507 // at least partially visible
508 aArea.SetRight( m_nRowHeaderWidthPixel );
509 if ( !m_aColumnWidths.empty() )
511 // the number of pixels which are scrolled out of the left hand
512 // side of the window
513 const tools::Long nScrolledOutLeft = m_nLeftColumn == 0 ? 0 : m_aColumnWidths[ m_nLeftColumn - 1 ].getEnd();
515 ColumnPositions::const_reverse_iterator loop = m_aColumnWidths.rbegin();
518 aArea.SetRight(loop->getEnd() - nScrolledOutLeft);
519 ++loop;
521 while ( ( loop != m_aColumnWidths.rend() )
522 && ( loop->getEnd() - nScrolledOutLeft >= aArea.Right() )
525 // so far, aArea.Right() denotes the first pixel *after* the cell area
526 aArea.AdjustRight( -1 );
528 // determine the last row which is at least partially visible
529 aArea.SetBottom(
530 m_nColHeaderHeightPixel
531 + impl_getVisibleRows( true ) * m_nRowHeightPixel
532 - 1 );
534 return aArea;
538 tools::Rectangle TableControl_Impl::impl_getAllVisibleDataCellArea() const
540 tools::Rectangle aArea( impl_getAllVisibleCellsArea() );
541 aArea.SetLeft( m_nRowHeaderWidthPixel );
542 aArea.SetTop( m_nColHeaderHeightPixel );
543 return aArea;
547 void TableControl_Impl::impl_ni_updateCachedTableMetrics()
549 m_nRowHeightPixel = m_rAntiImpl.LogicToPixel(Size(0, m_pModel->getRowHeight()), MapMode(MapUnit::MapAppFont)).Height();
551 m_nColHeaderHeightPixel = 0;
552 if ( m_pModel->hasColumnHeaders() )
553 m_nColHeaderHeightPixel = m_rAntiImpl.LogicToPixel(Size(0, m_pModel->getColumnHeaderHeight()), MapMode(MapUnit::MapAppFont)).Height();
555 m_nRowHeaderWidthPixel = 0;
556 if ( m_pModel->hasRowHeaders() )
557 m_nRowHeaderWidthPixel = m_rAntiImpl.LogicToPixel(Size(m_pModel->getRowHeaderWidth(), 0), MapMode(MapUnit::MapAppFont)).Width();
561 void TableControl_Impl::impl_ni_updateCachedModelValues()
563 m_pInputHandler = m_pModel->getInputHandler();
564 if ( !m_pInputHandler )
565 m_pInputHandler = std::make_shared<DefaultInputHandler>();
567 m_nColumnCount = m_pModel->getColumnCount();
568 if ( m_nLeftColumn >= m_nColumnCount )
569 m_nLeftColumn = ( m_nColumnCount > 0 ) ? m_nColumnCount - 1 : 0;
571 m_nRowCount = m_pModel->getRowCount();
572 if ( m_nTopRow >= m_nRowCount )
573 m_nTopRow = ( m_nRowCount > 0 ) ? m_nRowCount - 1 : 0;
575 impl_ni_updateCachedTableMetrics();
579 namespace
582 /// determines whether a scrollbar is needed for the given values
583 bool lcl_determineScrollbarNeed( tools::Long const i_position, ScrollbarVisibility const i_visibility,
584 tools::Long const i_availableSpace, tools::Long const i_neededSpace )
586 if ( i_visibility == ScrollbarShowNever )
587 return false;
588 if ( i_visibility == ScrollbarShowAlways )
589 return true;
590 if ( i_position > 0 )
591 return true;
592 if ( i_availableSpace >= i_neededSpace )
593 return false;
594 return true;
598 void lcl_setButtonRepeat( vcl::Window& _rWindow )
600 AllSettings aSettings = _rWindow.GetSettings();
601 MouseSettings aMouseSettings = aSettings.GetMouseSettings();
603 aMouseSettings.SetButtonRepeat( 0 );
604 aSettings.SetMouseSettings( aMouseSettings );
606 _rWindow.SetSettings( aSettings, true );
610 bool lcl_updateScrollbar( vcl::Window& _rParent, VclPtr<ScrollBar>& _rpBar,
611 bool const i_needBar, tools::Long _nVisibleUnits,
612 tools::Long _nPosition, tools::Long _nRange,
613 bool _bHorizontal, const Link<ScrollBar*,void>& _rScrollHandler )
615 // do we currently have the scrollbar?
616 bool bHaveBar = _rpBar != nullptr;
618 // do we need to correct the scrollbar visibility?
619 if ( bHaveBar && !i_needBar )
621 if ( _rpBar->IsTracking() )
622 _rpBar->EndTracking();
623 _rpBar.disposeAndClear();
625 else if ( !bHaveBar && i_needBar )
627 _rpBar = VclPtr<ScrollBar>::Create(
629 &_rParent,
630 WB_DRAG | ( _bHorizontal ? WB_HSCROLL : WB_VSCROLL )
632 _rpBar->SetScrollHdl( _rScrollHandler );
633 // get some speed into the scrolling...
634 lcl_setButtonRepeat( *_rpBar );
637 if ( _rpBar )
639 _rpBar->SetRange( Range( 0, _nRange ) );
640 _rpBar->SetVisibleSize( _nVisibleUnits );
641 _rpBar->SetPageSize( _nVisibleUnits );
642 _rpBar->SetLineSize( 1 );
643 _rpBar->SetThumbPos( _nPosition );
644 _rpBar->Show();
647 return ( bHaveBar != i_needBar );
651 /** returns the number of rows fitting into the given range,
652 for the given row height. Partially fitting rows are counted, too, if the
653 respective parameter says so.
655 TableSize lcl_getRowsFittingInto( tools::Long _nOverallHeight, tools::Long _nRowHeightPixel, bool _bAcceptPartialRow )
657 return _bAcceptPartialRow
658 ? ( _nOverallHeight + ( _nRowHeightPixel - 1 ) ) / _nRowHeightPixel
659 : _nOverallHeight / _nRowHeightPixel;
663 /** returns the number of columns fitting into the given area,
664 with the first visible column as given. Partially fitting columns are counted, too,
665 if the respective parameter says so.
667 TableSize lcl_getColumnsVisibleWithin( const tools::Rectangle& _rArea, ColPos _nFirstVisibleColumn,
668 const TableControl_Impl& _rControl, bool _bAcceptPartialRow )
670 TableSize visibleColumns = 0;
671 TableColumnGeometry aColumn( _rControl, _rArea, _nFirstVisibleColumn );
672 while ( aColumn.isValid() )
674 if ( !_bAcceptPartialRow )
675 if ( aColumn.getRect().Right() > _rArea.Right() )
676 // this column is only partially visible, and this is not allowed
677 break;
679 aColumn.moveRight();
680 ++visibleColumns;
682 return visibleColumns;
688 tools::Long TableControl_Impl::impl_ni_calculateColumnWidths( ColPos const i_assumeInflexibleColumnsUpToIncluding,
689 bool const i_assumeVerticalScrollbar, ::std::vector< tools::Long >& o_newColWidthsPixel ) const
691 // the available horizontal space
692 tools::Long gridWidthPixel = m_rAntiImpl.GetOutputSizePixel().Width();
693 ENSURE_OR_RETURN( !!m_pModel, "TableControl_Impl::impl_ni_calculateColumnWidths: not allowed without a model!", gridWidthPixel );
694 if ( m_pModel->hasRowHeaders() && ( gridWidthPixel != 0 ) )
696 gridWidthPixel -= m_nRowHeaderWidthPixel;
699 if ( i_assumeVerticalScrollbar && ( m_pModel->getVerticalScrollbarVisibility() != ScrollbarShowNever ) )
701 tools::Long nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
702 gridWidthPixel -= nScrollbarMetrics;
705 // no need to do anything without columns
706 TableSize const colCount = m_pModel->getColumnCount();
707 if ( colCount == 0 )
708 return gridWidthPixel;
710 // collect some meta data for our columns:
711 // - their current (pixel) metrics
712 tools::Long accumulatedCurrentWidth = 0;
713 ::std::vector< tools::Long > currentColWidths;
714 currentColWidths.reserve( colCount );
715 typedef ::std::vector< ::std::pair< tools::Long, long > > ColumnLimits;
716 ColumnLimits effectiveColumnLimits;
717 effectiveColumnLimits.reserve( colCount );
718 tools::Long accumulatedMinWidth = 0;
719 tools::Long accumulatedMaxWidth = 0;
720 // - their relative flexibility
721 ::std::vector< ::sal_Int32 > columnFlexibilities;
722 columnFlexibilities.reserve( colCount );
723 tools::Long flexibilityDenominator = 0;
724 size_t flexibleColumnCount = 0;
725 for ( ColPos col = 0; col < colCount; ++col )
727 PColumnModel const pColumn = m_pModel->getColumnModel( col );
728 ENSURE_OR_THROW( !!pColumn, "invalid column returned by the model!" );
730 // current width
731 tools::Long const currentWidth = appFontWidthToPixel( pColumn->getWidth() );
732 currentColWidths.push_back( currentWidth );
734 // accumulated width
735 accumulatedCurrentWidth += currentWidth;
737 // flexibility
738 ::sal_Int32 flexibility = pColumn->getFlexibility();
739 OSL_ENSURE( flexibility >= 0, "TableControl_Impl::impl_ni_calculateColumnWidths: a column's flexibility should be non-negative." );
740 if ( ( flexibility < 0 ) // normalization
741 || ( !pColumn->isResizable() ) // column not resizable => no auto-resize
742 || ( col <= i_assumeInflexibleColumnsUpToIncluding ) // column shall be treated as inflexible => respect this
744 flexibility = 0;
746 // min/max width
747 tools::Long effectiveMin = currentWidth, effectiveMax = currentWidth;
748 // if the column is not flexible, it will not be asked for min/max, but we assume the current width as limit then
749 if ( flexibility > 0 )
751 tools::Long const minWidth = appFontWidthToPixel( pColumn->getMinWidth() );
752 if ( minWidth > 0 )
753 effectiveMin = minWidth;
754 else
755 effectiveMin = MIN_COLUMN_WIDTH_PIXEL;
757 tools::Long const maxWidth = appFontWidthToPixel( pColumn->getMaxWidth() );
758 OSL_ENSURE( minWidth <= maxWidth, "TableControl_Impl::impl_ni_calculateColumnWidths: pretty undecided 'bout its width limits, this column!" );
759 if ( ( maxWidth > 0 ) && ( maxWidth >= minWidth ) )
760 effectiveMax = maxWidth;
761 else
762 effectiveMax = gridWidthPixel; // TODO: any better guess here?
764 if ( effectiveMin == effectiveMax )
765 // if the min and the max are identical, this implies no flexibility at all
766 flexibility = 0;
769 columnFlexibilities.push_back( flexibility );
770 flexibilityDenominator += flexibility;
771 if ( flexibility > 0 )
772 ++flexibleColumnCount;
774 effectiveColumnLimits.emplace_back( effectiveMin, effectiveMax );
775 accumulatedMinWidth += effectiveMin;
776 accumulatedMaxWidth += effectiveMax;
779 o_newColWidthsPixel = currentColWidths;
780 if ( flexibilityDenominator == 0 )
782 // no column is flexible => don't adjust anything
784 else if ( gridWidthPixel > accumulatedCurrentWidth )
785 { // we have space to give away ...
786 tools::Long distributePixel = gridWidthPixel - accumulatedCurrentWidth;
787 if ( gridWidthPixel > accumulatedMaxWidth )
789 // ... but the column's maximal widths are still less than we have
790 // => set them all to max
791 for ( svt::table::TableSize i = 0; i < colCount; ++i )
793 o_newColWidthsPixel[i] = effectiveColumnLimits[i].second;
796 else
798 bool startOver = false;
801 startOver = false;
802 // distribute the remaining space amongst all columns with a positive flexibility
803 for ( size_t i=0; i<o_newColWidthsPixel.size() && !startOver; ++i )
805 tools::Long const columnFlexibility = columnFlexibilities[i];
806 if ( columnFlexibility == 0 )
807 continue;
809 tools::Long newColWidth = currentColWidths[i] + columnFlexibility * distributePixel / flexibilityDenominator;
811 if ( newColWidth > effectiveColumnLimits[i].second )
812 { // that was too much, we hit the col's maximum
813 // set the new width to exactly this maximum
814 newColWidth = effectiveColumnLimits[i].second;
815 // adjust the flexibility denominator ...
816 flexibilityDenominator -= columnFlexibility;
817 columnFlexibilities[i] = 0;
818 --flexibleColumnCount;
819 // ... and the remaining width ...
820 tools::Long const difference = newColWidth - currentColWidths[i];
821 distributePixel -= difference;
822 // ... this way, we ensure that the width not taken up by this column is consumed by the other
823 // flexible ones (if there are some)
825 // and start over with the first column, since there might be earlier columns which need
826 // to be recalculated now
827 startOver = true;
830 o_newColWidthsPixel[i] = newColWidth;
833 while ( startOver );
835 // are there pixels left (might be caused by rounding errors)?
836 distributePixel = gridWidthPixel - ::std::accumulate( o_newColWidthsPixel.begin(), o_newColWidthsPixel.end(), 0 );
837 while ( ( distributePixel > 0 ) && ( flexibleColumnCount > 0 ) )
839 // yes => ignore relative flexibilities, and subsequently distribute single pixels to all flexible
840 // columns which did not yet reach their maximum.
841 for ( size_t i=0; ( i < o_newColWidthsPixel.size() ) && ( distributePixel > 0 ); ++i )
843 if ( columnFlexibilities[i] == 0 )
844 continue;
846 OSL_ENSURE( o_newColWidthsPixel[i] <= effectiveColumnLimits[i].second,
847 "TableControl_Impl::impl_ni_calculateColumnWidths: inconsistency!" );
848 if ( o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first )
850 columnFlexibilities[i] = 0;
851 --flexibleColumnCount;
852 continue;
855 ++o_newColWidthsPixel[i];
856 --distributePixel;
861 else if ( gridWidthPixel < accumulatedCurrentWidth )
862 { // we need to take away some space from the columns which allow it ...
863 tools::Long takeAwayPixel = accumulatedCurrentWidth - gridWidthPixel;
864 if ( gridWidthPixel < accumulatedMinWidth )
866 // ... but the column's minimal widths are still more than we have
867 // => set them all to min
868 for ( svt::table::TableSize i = 0; i < colCount; ++i )
870 o_newColWidthsPixel[i] = effectiveColumnLimits[i].first;
873 else
875 bool startOver = false;
878 startOver = false;
879 // take away the space we need from the columns with a positive flexibility
880 for ( size_t i=0; i<o_newColWidthsPixel.size() && !startOver; ++i )
882 tools::Long const columnFlexibility = columnFlexibilities[i];
883 if ( columnFlexibility == 0 )
884 continue;
886 tools::Long newColWidth = currentColWidths[i] - columnFlexibility * takeAwayPixel / flexibilityDenominator;
888 if ( newColWidth < effectiveColumnLimits[i].first )
889 { // that was too much, we hit the col's minimum
890 // set the new width to exactly this minimum
891 newColWidth = effectiveColumnLimits[i].first;
892 // adjust the flexibility denominator ...
893 flexibilityDenominator -= columnFlexibility;
894 columnFlexibilities[i] = 0;
895 --flexibleColumnCount;
896 // ... and the remaining width ...
897 tools::Long const difference = currentColWidths[i] - newColWidth;
898 takeAwayPixel -= difference;
900 // and start over with the first column, since there might be earlier columns which need
901 // to be recalculated now
902 startOver = true;
905 o_newColWidthsPixel[i] = newColWidth;
908 while ( startOver );
910 // are there pixels left (might be caused by rounding errors)?
911 takeAwayPixel = ::std::accumulate( o_newColWidthsPixel.begin(), o_newColWidthsPixel.end(), 0 ) - gridWidthPixel;
912 while ( ( takeAwayPixel > 0 ) && ( flexibleColumnCount > 0 ) )
914 // yes => ignore relative flexibilities, and subsequently take away pixels from all flexible
915 // columns which did not yet reach their minimum.
916 for ( size_t i=0; ( i < o_newColWidthsPixel.size() ) && ( takeAwayPixel > 0 ); ++i )
918 if ( columnFlexibilities[i] == 0 )
919 continue;
921 OSL_ENSURE( o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first,
922 "TableControl_Impl::impl_ni_calculateColumnWidths: inconsistency!" );
923 if ( o_newColWidthsPixel[i] <= effectiveColumnLimits[i].first )
925 columnFlexibilities[i] = 0;
926 --flexibleColumnCount;
927 continue;
930 --o_newColWidthsPixel[i];
931 --takeAwayPixel;
937 return gridWidthPixel;
941 void TableControl_Impl::impl_ni_relayout( ColPos const i_assumeInflexibleColumnsUpToIncluding )
943 ENSURE_OR_RETURN_VOID( !m_bUpdatingColWidths, "TableControl_Impl::impl_ni_relayout: recursive call detected!" );
945 m_aColumnWidths.resize( 0 );
946 if ( !m_pModel )
947 return;
949 ::comphelper::FlagRestorationGuard const aWidthUpdateFlag( m_bUpdatingColWidths, true );
950 SuppressCursor aHideCursor( *this );
952 // layouting steps:
954 // 1. adjust column widths, leaving space for a vertical scrollbar
955 // 2. determine need for a vertical scrollbar
956 // - V-YES: all fine, result from 1. is still valid
957 // - V-NO: result from 1. is still under consideration
959 // 3. determine need for a horizontal scrollbar
960 // - H-NO: all fine, result from 2. is still valid
961 // - H-YES: reconsider need for a vertical scrollbar, if result of 2. was V-NO
962 // - V-YES: all fine, result from 1. is still valid
963 // - V-NO: redistribute the remaining space (if any) amongst all columns which allow it
965 ::std::vector< tools::Long > newWidthsPixel;
966 tools::Long gridWidthPixel = impl_ni_calculateColumnWidths( i_assumeInflexibleColumnsUpToIncluding, true, newWidthsPixel );
968 // the width/height of a scrollbar, needed several times below
969 tools::Long const nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
971 // determine the playground for the data cells (excluding headers)
972 // TODO: what if the control is smaller than needed for the headers/scrollbars?
973 tools::Rectangle aDataCellPlayground( Point( 0, 0 ), m_rAntiImpl.GetOutputSizePixel() );
974 aDataCellPlayground.SetLeft( m_nRowHeaderWidthPixel );
975 aDataCellPlayground.SetTop( m_nColHeaderHeightPixel );
977 OSL_ENSURE( ( m_nRowCount == m_pModel->getRowCount() ) && ( m_nColumnCount == m_pModel->getColumnCount() ),
978 "TableControl_Impl::impl_ni_relayout: how is this expected to work with invalid data?" );
979 tools::Long const nAllColumnsWidth = ::std::accumulate( newWidthsPixel.begin(), newWidthsPixel.end(), 0 );
981 ScrollbarVisibility const eVertScrollbar = m_pModel->getVerticalScrollbarVisibility();
982 ScrollbarVisibility const eHorzScrollbar = m_pModel->getHorizontalScrollbarVisibility();
984 // do we need a vertical scrollbar?
985 bool bNeedVerticalScrollbar = lcl_determineScrollbarNeed(
986 m_nTopRow, eVertScrollbar, aDataCellPlayground.GetHeight(), m_nRowHeightPixel * m_nRowCount );
987 bool bFirstRoundVScrollNeed = false;
988 if ( bNeedVerticalScrollbar )
990 aDataCellPlayground.AdjustRight( -nScrollbarMetrics );
991 bFirstRoundVScrollNeed = true;
994 // do we need a horizontal scrollbar?
995 bool const bNeedHorizontalScrollbar = lcl_determineScrollbarNeed(
996 m_nLeftColumn, eHorzScrollbar, aDataCellPlayground.GetWidth(), nAllColumnsWidth );
997 if ( bNeedHorizontalScrollbar )
999 aDataCellPlayground.AdjustBottom( -nScrollbarMetrics );
1001 // now that we just found that we need a horizontal scrollbar,
1002 // the need for a vertical one may have changed, since the horizontal
1003 // SB might just occupy enough space so that not all rows do fit
1004 // anymore
1005 if ( !bFirstRoundVScrollNeed )
1007 bNeedVerticalScrollbar = lcl_determineScrollbarNeed(
1008 m_nTopRow, eVertScrollbar, aDataCellPlayground.GetHeight(), m_nRowHeightPixel * m_nRowCount );
1009 if ( bNeedVerticalScrollbar )
1011 aDataCellPlayground.AdjustRight( -nScrollbarMetrics );
1016 // the initial call to impl_ni_calculateColumnWidths assumed that we need a vertical scrollbar. If, by now,
1017 // we know that this is not the case, re-calculate the column widths.
1018 if ( !bNeedVerticalScrollbar )
1019 gridWidthPixel = impl_ni_calculateColumnWidths( i_assumeInflexibleColumnsUpToIncluding, false, newWidthsPixel );
1021 // update the column objects with the new widths we finally calculated
1022 TableSize const colCount = m_pModel->getColumnCount();
1023 m_aColumnWidths.reserve( colCount );
1024 tools::Long accumulatedWidthPixel = m_nRowHeaderWidthPixel;
1025 bool anyColumnWidthChanged = false;
1026 for ( ColPos col = 0; col < colCount; ++col )
1028 const tools::Long columnStart = accumulatedWidthPixel;
1029 const tools::Long columnEnd = columnStart + newWidthsPixel[col];
1030 m_aColumnWidths.emplace_back( columnStart, columnEnd );
1031 accumulatedWidthPixel = columnEnd;
1033 // and don't forget to forward this to the column models
1034 PColumnModel const pColumn = m_pModel->getColumnModel( col );
1035 ENSURE_OR_THROW( !!pColumn, "invalid column returned by the model!" );
1037 tools::Long const oldColumnWidthAppFont = pColumn->getWidth();
1038 tools::Long const newColumnWidthAppFont = pixelWidthToAppFont( newWidthsPixel[col] );
1039 pColumn->setWidth( newColumnWidthAppFont );
1041 anyColumnWidthChanged |= ( oldColumnWidthAppFont != newColumnWidthAppFont );
1044 // if the column widths changed, ensure everything is repainted
1045 if ( anyColumnWidthChanged )
1046 invalidate( TableArea::All );
1048 // if the column resizing happened to leave some space at the right, but there are columns
1049 // scrolled out to the left, scroll them in
1050 while ( ( m_nLeftColumn > 0 )
1051 && ( accumulatedWidthPixel - m_aColumnWidths[ m_nLeftColumn - 1 ].getStart() <= gridWidthPixel )
1054 --m_nLeftColumn;
1057 // now adjust the column metrics, since they currently ignore the horizontal scroll position
1058 if ( m_nLeftColumn > 0 )
1060 const tools::Long offsetPixel = m_aColumnWidths[ 0 ].getStart() - m_aColumnWidths[ m_nLeftColumn ].getStart();
1061 for (auto & columnWidth : m_aColumnWidths)
1063 columnWidth.move( offsetPixel );
1067 // show or hide the scrollbars as needed, and position the data window
1068 impl_ni_positionChildWindows( aDataCellPlayground, bNeedVerticalScrollbar, bNeedHorizontalScrollbar );
1072 void TableControl_Impl::impl_ni_positionChildWindows( tools::Rectangle const & i_dataCellPlayground,
1073 bool const i_verticalScrollbar, bool const i_horizontalScrollbar )
1075 tools::Long const nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
1077 // create or destroy the vertical scrollbar, as needed
1078 lcl_updateScrollbar(
1079 m_rAntiImpl,
1080 m_pVScroll,
1081 i_verticalScrollbar,
1082 lcl_getRowsFittingInto( i_dataCellPlayground.GetHeight(), m_nRowHeightPixel, false ),
1083 // visible units
1084 m_nTopRow, // current position
1085 m_nRowCount, // range
1086 false, // vertical
1087 LINK( this, TableControl_Impl, OnScroll ) // scroll handler
1090 // position it
1091 if ( m_pVScroll )
1093 tools::Rectangle aScrollbarArea(
1094 Point( i_dataCellPlayground.Right() + 1, 0 ),
1095 Size( nScrollbarMetrics, i_dataCellPlayground.Bottom() + 1 )
1097 m_pVScroll->SetPosSizePixel(
1098 aScrollbarArea.TopLeft(), aScrollbarArea.GetSize() );
1101 // create or destroy the horizontal scrollbar, as needed
1102 lcl_updateScrollbar(
1103 m_rAntiImpl,
1104 m_pHScroll,
1105 i_horizontalScrollbar,
1106 lcl_getColumnsVisibleWithin( i_dataCellPlayground, m_nLeftColumn, *this, false ),
1107 // visible units
1108 m_nLeftColumn, // current position
1109 m_nColumnCount, // range
1110 true, // horizontal
1111 LINK( this, TableControl_Impl, OnScroll ) // scroll handler
1114 // position it
1115 if ( m_pHScroll )
1117 TableSize const nVisibleUnits = lcl_getColumnsVisibleWithin( i_dataCellPlayground, m_nLeftColumn, *this, false );
1118 TableMetrics const nRange = m_nColumnCount;
1119 if( m_nLeftColumn + nVisibleUnits == nRange - 1 )
1121 if ( m_aColumnWidths[ nRange - 1 ].getStart() - m_aColumnWidths[ m_nLeftColumn ].getEnd() + m_aColumnWidths[ nRange-1 ].getWidth() > i_dataCellPlayground.GetWidth() )
1123 m_pHScroll->SetVisibleSize( nVisibleUnits -1 );
1124 m_pHScroll->SetPageSize( nVisibleUnits - 1 );
1127 tools::Rectangle aScrollbarArea(
1128 Point( 0, i_dataCellPlayground.Bottom() + 1 ),
1129 Size( i_dataCellPlayground.Right() + 1, nScrollbarMetrics )
1131 m_pHScroll->SetPosSizePixel(
1132 aScrollbarArea.TopLeft(), aScrollbarArea.GetSize() );
1135 // the corner window connecting the two scrollbars in the lower right corner
1136 bool bHaveScrollCorner = nullptr != m_pScrollCorner;
1137 bool bNeedScrollCorner = ( nullptr != m_pHScroll ) && ( nullptr != m_pVScroll );
1138 if ( bHaveScrollCorner && !bNeedScrollCorner )
1140 m_pScrollCorner.disposeAndClear();
1142 else if ( !bHaveScrollCorner && bNeedScrollCorner )
1144 m_pScrollCorner = VclPtr<ScrollBarBox>::Create( &m_rAntiImpl );
1145 m_pScrollCorner->SetSizePixel( Size( nScrollbarMetrics, nScrollbarMetrics ) );
1146 m_pScrollCorner->SetPosPixel( Point( i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1 ) );
1147 m_pScrollCorner->Show();
1149 else if(bHaveScrollCorner && bNeedScrollCorner)
1151 m_pScrollCorner->SetPosPixel( Point( i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1 ) );
1152 m_pScrollCorner->Show();
1155 // resize the data window
1156 m_pDataWindow->SetSizePixel( Size(
1157 i_dataCellPlayground.GetWidth() + m_nRowHeaderWidthPixel,
1158 i_dataCellPlayground.GetHeight() + m_nColHeaderHeightPixel
1159 ) );
1163 void TableControl_Impl::onResize()
1165 impl_ni_relayout();
1166 checkCursorPosition();
1170 void TableControl_Impl::doPaintContent(vcl::RenderContext& rRenderContext, const tools::Rectangle& _rUpdateRect)
1172 if (!getModel())
1173 return;
1174 PTableRenderer pRenderer = getModel()->getRenderer();
1175 DBG_ASSERT(!!pRenderer, "TableDataWindow::doPaintContent: invalid renderer!");
1176 if (!pRenderer)
1177 return;
1179 // our current style settings, to be passed to the renderer
1180 const StyleSettings& rStyle = rRenderContext.GetSettings().GetStyleSettings();
1181 m_nRowCount = m_pModel->getRowCount();
1182 // the area occupied by all (at least partially) visible cells, including
1183 // headers
1184 tools::Rectangle const aAllCellsWithHeaders( impl_getAllVisibleCellsArea() );
1186 // draw the header column area
1187 if (m_pModel->hasColumnHeaders())
1189 TableRowGeometry const aHeaderRow(*this, tools::Rectangle(Point(0, 0), aAllCellsWithHeaders.BottomRight()), ROW_COL_HEADERS);
1190 tools::Rectangle const aColRect(aHeaderRow.getRect());
1191 pRenderer->PaintHeaderArea(rRenderContext, aColRect, true, false, rStyle);
1192 // Note that strictly, aHeaderRow.getRect() also contains the intersection between column
1193 // and row header area. However, below we go to paint this intersection, again,
1194 // so this hopefully doesn't hurt if we already paint it here.
1196 for (TableCellGeometry aCell(aHeaderRow, m_nLeftColumn); aCell.isValid(); aCell.moveRight())
1198 if (_rUpdateRect.GetIntersection(aCell.getRect()).IsEmpty())
1199 continue;
1201 pRenderer->PaintColumnHeader(aCell.getColumn(), rRenderContext, aCell.getRect(), rStyle);
1204 // the area occupied by the row header, if any
1205 tools::Rectangle aRowHeaderArea;
1206 if (m_pModel->hasRowHeaders())
1208 aRowHeaderArea = aAllCellsWithHeaders;
1209 aRowHeaderArea.SetRight( m_nRowHeaderWidthPixel - 1 );
1211 TableSize const nVisibleRows = impl_getVisibleRows(true);
1212 TableSize nActualRows = nVisibleRows;
1213 if (m_nTopRow + nActualRows > m_nRowCount)
1214 nActualRows = m_nRowCount - m_nTopRow;
1215 aRowHeaderArea.SetBottom( m_nColHeaderHeightPixel + m_nRowHeightPixel * nActualRows - 1 );
1217 pRenderer->PaintHeaderArea(rRenderContext, aRowHeaderArea, false, true, rStyle);
1218 // Note that strictly, aRowHeaderArea also contains the intersection between column
1219 // and row header area. However, below we go to paint this intersection, again,
1220 // so this hopefully doesn't hurt if we already paint it here.
1222 if (m_pModel->hasColumnHeaders())
1224 TableCellGeometry const aIntersection(*this, tools::Rectangle(Point(0, 0), aAllCellsWithHeaders.BottomRight()),
1225 COL_ROW_HEADERS, ROW_COL_HEADERS);
1226 tools::Rectangle const aInters(aIntersection.getRect());
1227 pRenderer->PaintHeaderArea(rRenderContext, aInters, true, true, rStyle);
1231 // draw the table content row by row
1232 TableSize colCount = getModel()->getColumnCount();
1234 // paint all rows
1235 tools::Rectangle const aAllDataCellsArea(impl_getAllVisibleDataCellArea());
1236 for (TableRowGeometry aRowIterator(*this, aAllCellsWithHeaders, getTopRow()); aRowIterator.isValid(); aRowIterator.moveDown())
1238 if (_rUpdateRect.GetIntersection(aRowIterator.getRect() ).IsEmpty())
1239 continue;
1241 bool const isControlFocused = m_rAntiImpl.HasControlFocus();
1242 bool const isSelectedRow = isRowSelected(aRowIterator.getRow());
1244 tools::Rectangle const aRect = aRowIterator.getRect().GetIntersection(aAllDataCellsArea);
1246 // give the renderer a chance to prepare the row
1247 pRenderer->PrepareRow(aRowIterator.getRow(), isControlFocused, isSelectedRow, rRenderContext, aRect, rStyle);
1249 // paint the row header
1250 if (m_pModel->hasRowHeaders())
1252 const tools::Rectangle aCurrentRowHeader(aRowHeaderArea.GetIntersection(aRowIterator.getRect()));
1253 pRenderer->PaintRowHeader(rRenderContext, aCurrentRowHeader, rStyle);
1256 if (!colCount)
1257 continue;
1259 // paint all cells in this row
1260 for (TableCellGeometry aCell(aRowIterator, m_nLeftColumn); aCell.isValid(); aCell.moveRight())
1262 pRenderer->PaintCell(aCell.getColumn(), isSelectedRow, isControlFocused,
1263 rRenderContext, aCell.getRect(), rStyle);
1268 void TableControl_Impl::hideCursor()
1270 if ( ++m_nCursorHidden == 1 )
1271 impl_ni_doSwitchCursor( false );
1275 void TableControl_Impl::showCursor()
1277 DBG_ASSERT( m_nCursorHidden > 0, "TableControl_Impl::showCursor: cursor not hidden!" );
1278 if ( --m_nCursorHidden == 0 )
1279 impl_ni_doSwitchCursor( true );
1283 bool TableControl_Impl::dispatchAction( TableControlAction _eAction )
1285 bool bSuccess = false;
1286 bool selectionChanged = false;
1288 switch ( _eAction )
1290 case cursorDown:
1291 if ( m_pSelEngine->GetSelectionMode() == SelectionMode::Single )
1293 //if other rows already selected, deselect them
1294 if(!m_aSelectedRows.empty())
1296 invalidateSelectedRows();
1297 m_aSelectedRows.clear();
1299 if ( m_nCurRow < m_nRowCount-1 )
1301 ++m_nCurRow;
1302 m_aSelectedRows.push_back(m_nCurRow);
1304 else
1305 m_aSelectedRows.push_back(m_nCurRow);
1306 invalidateRow( m_nCurRow );
1307 ensureVisible(m_nCurColumn,m_nCurRow);
1308 selectionChanged = true;
1309 bSuccess = true;
1311 else
1313 if ( m_nCurRow < m_nRowCount - 1 )
1314 bSuccess = goTo( m_nCurColumn, m_nCurRow + 1 );
1316 break;
1318 case cursorUp:
1319 if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
1321 if(!m_aSelectedRows.empty())
1323 invalidateSelectedRows();
1324 m_aSelectedRows.clear();
1326 if(m_nCurRow>0)
1328 --m_nCurRow;
1329 m_aSelectedRows.push_back(m_nCurRow);
1330 invalidateRow( m_nCurRow );
1332 else
1334 m_aSelectedRows.push_back(m_nCurRow);
1335 invalidateRow( m_nCurRow );
1337 ensureVisible(m_nCurColumn,m_nCurRow);
1338 selectionChanged = true;
1339 bSuccess = true;
1341 else
1343 if ( m_nCurRow > 0 )
1344 bSuccess = goTo( m_nCurColumn, m_nCurRow - 1 );
1346 break;
1347 case cursorLeft:
1348 if ( m_nCurColumn > 0 )
1349 bSuccess = goTo( m_nCurColumn - 1, m_nCurRow );
1350 else
1351 if ( ( m_nCurColumn == 0) && ( m_nCurRow > 0 ) )
1352 bSuccess = goTo( m_nColumnCount - 1, m_nCurRow - 1 );
1353 break;
1355 case cursorRight:
1356 if ( m_nCurColumn < m_nColumnCount - 1 )
1357 bSuccess = goTo( m_nCurColumn + 1, m_nCurRow );
1358 else
1359 if ( ( m_nCurColumn == m_nColumnCount - 1 ) && ( m_nCurRow < m_nRowCount - 1 ) )
1360 bSuccess = goTo( 0, m_nCurRow + 1 );
1361 break;
1363 case cursorToLineStart:
1364 bSuccess = goTo( 0, m_nCurRow );
1365 break;
1367 case cursorToLineEnd:
1368 bSuccess = goTo( m_nColumnCount - 1, m_nCurRow );
1369 break;
1371 case cursorToFirstLine:
1372 bSuccess = goTo( m_nCurColumn, 0 );
1373 break;
1375 case cursorToLastLine:
1376 bSuccess = goTo( m_nCurColumn, m_nRowCount - 1 );
1377 break;
1379 case cursorPageUp:
1381 RowPos nNewRow = ::std::max( RowPos(0), m_nCurRow - impl_getVisibleRows( false ) );
1382 bSuccess = goTo( m_nCurColumn, nNewRow );
1384 break;
1386 case cursorPageDown:
1388 RowPos nNewRow = ::std::min( m_nRowCount - 1, m_nCurRow + impl_getVisibleRows( false ) );
1389 bSuccess = goTo( m_nCurColumn, nNewRow );
1391 break;
1393 case cursorTopLeft:
1394 bSuccess = goTo( 0, 0 );
1395 break;
1397 case cursorBottomRight:
1398 bSuccess = goTo( m_nColumnCount - 1, m_nRowCount - 1 );
1399 break;
1401 case cursorSelectRow:
1403 if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
1404 return false;
1405 //pos is the position of the current row in the vector of selected rows, if current row is selected
1406 int pos = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
1407 //if current row is selected, it should be deselected, when ALT+SPACE are pressed
1408 if(pos>-1)
1410 m_aSelectedRows.erase(m_aSelectedRows.begin()+pos);
1411 if(m_aSelectedRows.empty() && m_nAnchor != -1)
1412 m_nAnchor = -1;
1414 //else select the row->put it in the vector
1415 else
1416 m_aSelectedRows.push_back(m_nCurRow);
1417 invalidateRow( m_nCurRow );
1418 selectionChanged = true;
1419 bSuccess = true;
1421 break;
1422 case cursorSelectRowUp:
1424 if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
1425 return false;
1426 else if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
1428 //if there are other selected rows, deselect them
1429 return false;
1431 else
1433 //there are other selected rows
1434 if(!m_aSelectedRows.empty())
1436 //the anchor wasn't set -> a region is not selected, that's why clear all selection
1437 //and select the current row
1438 if(m_nAnchor==-1)
1440 invalidateSelectedRows();
1441 m_aSelectedRows.clear();
1442 m_aSelectedRows.push_back(m_nCurRow);
1443 invalidateRow( m_nCurRow );
1445 else
1447 //a region is already selected, prevRow is last selected row and the row above - nextRow - should be selected
1448 int prevRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
1449 int nextRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow-1);
1450 if(prevRow>-1)
1452 //if m_nCurRow isn't the upper one, can move up, otherwise not
1453 if(m_nCurRow>0)
1454 m_nCurRow--;
1455 else
1456 return true;
1457 //if nextRow already selected, deselect it, otherwise select it
1458 if(nextRow>-1 && m_aSelectedRows[nextRow] == m_nCurRow)
1460 m_aSelectedRows.erase(m_aSelectedRows.begin()+prevRow);
1461 invalidateRow( m_nCurRow + 1 );
1463 else
1465 m_aSelectedRows.push_back(m_nCurRow);
1466 invalidateRow( m_nCurRow );
1469 else
1471 if(m_nCurRow>0)
1473 m_aSelectedRows.push_back(m_nCurRow);
1474 m_nCurRow--;
1475 m_aSelectedRows.push_back(m_nCurRow);
1476 invalidateSelectedRegion( m_nCurRow+1, m_nCurRow );
1481 else
1483 //if nothing is selected and the current row isn't the upper one
1484 //select the current and one row above
1485 //otherwise select only the upper row
1486 if(m_nCurRow>0)
1488 m_aSelectedRows.push_back(m_nCurRow);
1489 m_nCurRow--;
1490 m_aSelectedRows.push_back(m_nCurRow);
1491 invalidateSelectedRegion( m_nCurRow+1, m_nCurRow );
1493 else
1495 m_aSelectedRows.push_back(m_nCurRow);
1496 invalidateRow( m_nCurRow );
1499 m_pSelEngine->SetAnchor(true);
1500 m_nAnchor = m_nCurRow;
1501 ensureVisible(m_nCurColumn, m_nCurRow);
1502 selectionChanged = true;
1503 bSuccess = true;
1506 break;
1507 case cursorSelectRowDown:
1509 if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
1510 bSuccess = false;
1511 else if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
1513 bSuccess = false;
1515 else
1517 if(!m_aSelectedRows.empty())
1519 //the anchor wasn't set -> a region is not selected, that's why clear all selection
1520 //and select the current row
1521 if(m_nAnchor==-1)
1523 invalidateSelectedRows();
1524 m_aSelectedRows.clear();
1525 m_aSelectedRows.push_back(m_nCurRow);
1526 invalidateRow( m_nCurRow );
1528 else
1530 //a region is already selected, prevRow is last selected row and the row beneath - nextRow - should be selected
1531 int prevRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
1532 int nextRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow+1);
1533 if(prevRow>-1)
1535 //if m_nCurRow isn't the last one, can move down, otherwise not
1536 if(m_nCurRow<m_nRowCount-1)
1537 m_nCurRow++;
1538 else
1539 return true;
1540 //if next row already selected, deselect it, otherwise select it
1541 if(nextRow>-1 && m_aSelectedRows[nextRow] == m_nCurRow)
1543 m_aSelectedRows.erase(m_aSelectedRows.begin()+prevRow);
1544 invalidateRow( m_nCurRow - 1 );
1546 else
1548 m_aSelectedRows.push_back(m_nCurRow);
1549 invalidateRow( m_nCurRow );
1552 else
1554 if(m_nCurRow<m_nRowCount-1)
1556 m_aSelectedRows.push_back(m_nCurRow);
1557 m_nCurRow++;
1558 m_aSelectedRows.push_back(m_nCurRow);
1559 invalidateSelectedRegion( m_nCurRow-1, m_nCurRow );
1564 else
1566 //there wasn't any selection, select current and row beneath, otherwise only row beneath
1567 if(m_nCurRow<m_nRowCount-1)
1569 m_aSelectedRows.push_back(m_nCurRow);
1570 m_nCurRow++;
1571 m_aSelectedRows.push_back(m_nCurRow);
1572 invalidateSelectedRegion( m_nCurRow-1, m_nCurRow );
1574 else
1576 m_aSelectedRows.push_back(m_nCurRow);
1577 invalidateRow( m_nCurRow );
1580 m_pSelEngine->SetAnchor(true);
1581 m_nAnchor = m_nCurRow;
1582 ensureVisible(m_nCurColumn, m_nCurRow);
1583 selectionChanged = true;
1584 bSuccess = true;
1587 break;
1589 case cursorSelectRowAreaTop:
1591 if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
1592 bSuccess = false;
1593 else if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
1594 bSuccess = false;
1595 else
1597 //select the region between the current and the upper row
1598 RowPos iter = m_nCurRow;
1599 invalidateSelectedRegion( m_nCurRow, 0 );
1600 //put the rows in vector
1601 while(iter>=0)
1603 if ( !isRowSelected( iter ) )
1604 m_aSelectedRows.push_back(iter);
1605 --iter;
1607 m_nCurRow = 0;
1608 m_nAnchor = m_nCurRow;
1609 m_pSelEngine->SetAnchor(true);
1610 ensureVisible(m_nCurColumn, 0);
1611 selectionChanged = true;
1612 bSuccess = true;
1615 break;
1617 case cursorSelectRowAreaBottom:
1619 if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE)
1620 return false;
1621 else if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single)
1622 return false;
1623 //select the region between the current and the last row
1624 RowPos iter = m_nCurRow;
1625 invalidateSelectedRegion( m_nCurRow, m_nRowCount-1 );
1626 //put the rows in the vector
1627 while(iter<=m_nRowCount)
1629 if ( !isRowSelected( iter ) )
1630 m_aSelectedRows.push_back(iter);
1631 ++iter;
1633 m_nCurRow = m_nRowCount-1;
1634 m_nAnchor = m_nCurRow;
1635 m_pSelEngine->SetAnchor(true);
1636 ensureVisible(m_nCurColumn, m_nRowCount-1);
1637 selectionChanged = true;
1638 bSuccess = true;
1640 break;
1641 default:
1642 OSL_FAIL( "TableControl_Impl::dispatchAction: unsupported action!" );
1643 break;
1646 if ( bSuccess && selectionChanged )
1648 m_rAntiImpl.Select();
1651 return bSuccess;
1655 void TableControl_Impl::impl_ni_doSwitchCursor( bool _bShow )
1657 PTableRenderer pRenderer = m_pModel ? m_pModel->getRenderer() : PTableRenderer();
1658 if ( pRenderer )
1660 tools::Rectangle aCellRect;
1661 impl_getCellRect( m_nCurColumn, m_nCurRow, aCellRect );
1662 if ( _bShow )
1663 pRenderer->ShowCellCursor( *m_pDataWindow, aCellRect );
1664 else
1665 pRenderer->HideCellCursor( *m_pDataWindow );
1670 void TableControl_Impl::impl_getCellRect( ColPos _nColumn, RowPos _nRow, tools::Rectangle& _rCellRect ) const
1672 if ( !m_pModel
1673 || ( COL_INVALID == _nColumn )
1674 || ( ROW_INVALID == _nRow )
1677 _rCellRect.SetEmpty();
1678 return;
1681 TableCellGeometry aCell( *this, impl_getAllVisibleCellsArea(), _nColumn, _nRow );
1682 _rCellRect = aCell.getRect();
1686 RowPos TableControl_Impl::getRowAtPoint( const Point& rPoint ) const
1688 return impl_getRowForAbscissa( rPoint.Y() );
1692 ColPos TableControl_Impl::getColAtPoint( const Point& rPoint ) const
1694 return impl_getColumnForOrdinate( rPoint.X() );
1698 TableCell TableControl_Impl::hitTest( Point const & i_point ) const
1700 TableCell aCell( getColAtPoint( i_point ), getRowAtPoint( i_point ) );
1701 if ( aCell.nColumn > COL_ROW_HEADERS )
1703 PColumnModel const pColumn = m_pModel->getColumnModel( aCell.nColumn );
1704 MutableColumnMetrics const & rColInfo( m_aColumnWidths[ aCell.nColumn ] );
1705 if ( ( rColInfo.getEnd() - 3 <= i_point.X() )
1706 && ( rColInfo.getEnd() >= i_point.X() )
1707 && pColumn->isResizable()
1710 aCell.eArea = ColumnDivider;
1713 return aCell;
1717 ColumnMetrics TableControl_Impl::getColumnMetrics( ColPos const i_column ) const
1719 ENSURE_OR_RETURN( ( i_column >= 0 ) && ( i_column < m_pModel->getColumnCount() ),
1720 "TableControl_Impl::getColumnMetrics: illegal column index!", ColumnMetrics() );
1721 return m_aColumnWidths[ i_column ];
1725 PTableModel TableControl_Impl::getModel() const
1727 return m_pModel;
1731 ColPos TableControl_Impl::getCurrentColumn() const
1733 return m_nCurColumn;
1737 RowPos TableControl_Impl::getCurrentRow() const
1739 return m_nCurRow;
1743 ::Size TableControl_Impl::getTableSizePixel() const
1745 return m_pDataWindow->GetOutputSizePixel();
1749 void TableControl_Impl::setPointer( PointerStyle i_pointer )
1751 m_pDataWindow->SetPointer( i_pointer );
1755 void TableControl_Impl::captureMouse()
1757 m_pDataWindow->CaptureMouse();
1761 void TableControl_Impl::releaseMouse()
1763 m_pDataWindow->ReleaseMouse();
1767 void TableControl_Impl::invalidate( TableArea const i_what )
1769 switch ( i_what )
1771 case TableArea::ColumnHeaders:
1772 m_pDataWindow->Invalidate( calcHeaderRect( true ) );
1773 break;
1775 case TableArea::RowHeaders:
1776 m_pDataWindow->Invalidate( calcHeaderRect( false ) );
1777 break;
1779 case TableArea::All:
1780 m_pDataWindow->Invalidate();
1781 m_pDataWindow->GetParent()->Invalidate( InvalidateFlags::Transparent );
1782 break;
1787 tools::Long TableControl_Impl::pixelWidthToAppFont( tools::Long const i_pixels ) const
1789 return m_pDataWindow->PixelToLogic(Size(i_pixels, 0), MapMode(MapUnit::MapAppFont)).Width();
1793 tools::Long TableControl_Impl::appFontWidthToPixel( tools::Long const i_appFontUnits ) const
1795 return m_pDataWindow->LogicToPixel(Size(i_appFontUnits, 0), MapMode(MapUnit::MapAppFont)).Width();
1799 void TableControl_Impl::hideTracking()
1801 m_pDataWindow->HideTracking();
1805 void TableControl_Impl::showTracking( tools::Rectangle const & i_location, ShowTrackFlags const i_flags )
1807 m_pDataWindow->ShowTracking( i_location, i_flags );
1811 void TableControl_Impl::activateCell( ColPos const i_col, RowPos const i_row )
1813 goTo( i_col, i_row );
1817 void TableControl_Impl::invalidateSelectedRegion( RowPos _nPrevRow, RowPos _nCurRow )
1819 // get the visible area of the table control and set the Left and right border of the region to be repainted
1820 tools::Rectangle const aAllCells( impl_getAllVisibleCellsArea() );
1822 tools::Rectangle aInvalidateRect;
1823 aInvalidateRect.SetLeft( aAllCells.Left() );
1824 aInvalidateRect.SetRight( aAllCells.Right() );
1825 // if only one row is selected
1826 if ( _nPrevRow == _nCurRow )
1828 tools::Rectangle aCellRect;
1829 impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
1830 aInvalidateRect.SetTop( aCellRect.Top() );
1831 aInvalidateRect.SetBottom( aCellRect.Bottom() );
1833 //if the region is above the current row
1834 else if(_nPrevRow < _nCurRow )
1836 tools::Rectangle aCellRect;
1837 impl_getCellRect( m_nCurColumn, _nPrevRow, aCellRect );
1838 aInvalidateRect.SetTop( aCellRect.Top() );
1839 impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
1840 aInvalidateRect.SetBottom( aCellRect.Bottom() );
1842 //if the region is beneath the current row
1843 else
1845 tools::Rectangle aCellRect;
1846 impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
1847 aInvalidateRect.SetTop( aCellRect.Top() );
1848 impl_getCellRect( m_nCurColumn, _nPrevRow, aCellRect );
1849 aInvalidateRect.SetBottom( aCellRect.Bottom() );
1852 invalidateRect(aInvalidateRect);
1855 void TableControl_Impl::invalidateRect(const tools::Rectangle &rInvalidateRect)
1857 m_pDataWindow->Invalidate( rInvalidateRect,
1858 m_pDataWindow->GetControlBackground().IsTransparent() ? InvalidateFlags::Transparent : InvalidateFlags::NONE );
1862 void TableControl_Impl::invalidateSelectedRows()
1864 for (auto const& selectedRow : m_aSelectedRows)
1866 invalidateRow(selectedRow);
1871 void TableControl_Impl::invalidateRowRange( RowPos const i_firstRow, RowPos const i_lastRow )
1873 RowPos const firstRow = i_firstRow < m_nTopRow ? m_nTopRow : i_firstRow;
1874 RowPos const lastVisibleRow = m_nTopRow + impl_getVisibleRows( true ) - 1;
1875 RowPos const lastRow = ( ( i_lastRow == ROW_INVALID ) || ( i_lastRow > lastVisibleRow ) ) ? lastVisibleRow : i_lastRow;
1877 tools::Rectangle aInvalidateRect;
1879 tools::Rectangle const aVisibleCellsArea( impl_getAllVisibleCellsArea() );
1880 TableRowGeometry aRow( *this, aVisibleCellsArea, firstRow, true );
1881 while ( aRow.isValid() && ( aRow.getRow() <= lastRow ) )
1883 aInvalidateRect.Union( aRow.getRect() );
1884 aRow.moveDown();
1887 if ( i_lastRow == ROW_INVALID )
1888 aInvalidateRect.SetBottom( m_pDataWindow->GetOutputSizePixel().Height() );
1890 invalidateRect(aInvalidateRect);
1894 void TableControl_Impl::checkCursorPosition()
1897 TableSize nVisibleRows = impl_getVisibleRows(true);
1898 TableSize nVisibleCols = impl_getVisibleColumns(true);
1899 if ( ( m_nTopRow + nVisibleRows > m_nRowCount )
1900 && ( m_nRowCount >= nVisibleRows )
1903 --m_nTopRow;
1905 else
1907 m_nTopRow = 0;
1910 if ( ( m_nLeftColumn + nVisibleCols > m_nColumnCount )
1911 && ( m_nColumnCount >= nVisibleCols )
1914 --m_nLeftColumn;
1916 else
1918 m_nLeftColumn = 0;
1921 m_pDataWindow->Invalidate();
1925 TableSize TableControl_Impl::impl_getVisibleRows( bool _bAcceptPartialRow ) const
1927 DBG_ASSERT( m_pDataWindow, "TableControl_Impl::impl_getVisibleRows: no data window!" );
1929 return lcl_getRowsFittingInto(
1930 m_pDataWindow->GetOutputSizePixel().Height() - m_nColHeaderHeightPixel,
1931 m_nRowHeightPixel,
1932 _bAcceptPartialRow
1937 TableSize TableControl_Impl::impl_getVisibleColumns( bool _bAcceptPartialCol ) const
1939 DBG_ASSERT( m_pDataWindow, "TableControl_Impl::impl_getVisibleColumns: no data window!" );
1941 return lcl_getColumnsVisibleWithin(
1942 tools::Rectangle( Point( 0, 0 ), m_pDataWindow->GetOutputSizePixel() ),
1943 m_nLeftColumn,
1944 *this,
1945 _bAcceptPartialCol
1950 bool TableControl_Impl::goTo( ColPos _nColumn, RowPos _nRow )
1952 // TODO: give veto listeners a chance
1954 if ( ( _nColumn < 0 ) || ( _nColumn >= m_nColumnCount )
1955 || ( _nRow < 0 ) || ( _nRow >= m_nRowCount )
1958 OSL_ENSURE( false, "TableControl_Impl::goTo: invalid row or column index!" );
1959 return false;
1962 SuppressCursor aHideCursor( *this );
1963 m_nCurColumn = _nColumn;
1964 m_nCurRow = _nRow;
1966 // ensure that the new cell is visible
1967 ensureVisible( m_nCurColumn, m_nCurRow );
1968 return true;
1972 void TableControl_Impl::ensureVisible( ColPos _nColumn, RowPos _nRow )
1974 DBG_ASSERT( ( _nColumn >= 0 ) && ( _nColumn < m_nColumnCount )
1975 && ( _nRow >= 0 ) && ( _nRow < m_nRowCount ),
1976 "TableControl_Impl::ensureVisible: invalid coordinates!" );
1978 SuppressCursor aHideCursor( *this );
1980 if ( _nColumn < m_nLeftColumn )
1981 impl_scrollColumns( _nColumn - m_nLeftColumn );
1982 else
1984 TableSize nVisibleColumns = impl_getVisibleColumns( false/*bAcceptPartialVisibility*/ );
1985 if ( _nColumn > m_nLeftColumn + nVisibleColumns - 1 )
1987 impl_scrollColumns( _nColumn - ( m_nLeftColumn + nVisibleColumns - 1 ) );
1988 // TODO: since not all columns have the same width, this might in theory result
1989 // in the column still not being visible.
1993 if ( _nRow < m_nTopRow )
1994 impl_scrollRows( _nRow - m_nTopRow );
1995 else
1997 TableSize nVisibleRows = impl_getVisibleRows( false/*_bAcceptPartialVisibility*/ );
1998 if ( _nRow > m_nTopRow + nVisibleRows - 1 )
1999 impl_scrollRows( _nRow - ( m_nTopRow + nVisibleRows - 1 ) );
2004 OUString TableControl_Impl::getCellContentAsString( RowPos const i_row, ColPos const i_col )
2006 Any aCellValue;
2007 m_pModel->getCellContent( i_col, i_row, aCellValue );
2009 OUString sCellStringContent;
2010 m_pModel->getRenderer()->GetFormattedCellString( aCellValue, sCellStringContent );
2012 return sCellStringContent;
2016 TableSize TableControl_Impl::impl_ni_ScrollRows( TableSize _nRowDelta )
2018 // compute new top row
2019 RowPos nNewTopRow =
2020 ::std::max(
2021 ::std::min( static_cast<RowPos>( m_nTopRow + _nRowDelta ), static_cast<RowPos>( m_nRowCount - 1 ) ),
2022 RowPos(0)
2025 RowPos nOldTopRow = m_nTopRow;
2026 m_nTopRow = nNewTopRow;
2028 // if updates are enabled currently, scroll the viewport
2029 if ( m_nTopRow != nOldTopRow )
2031 SuppressCursor aHideCursor( *this );
2032 // TODO: call an onStartScroll at our listener (or better an own onStartScroll,
2033 // which hides the cursor and then calls the listener)
2034 // Same for onEndScroll
2036 // scroll the view port, if possible
2037 tools::Long nPixelDelta = m_nRowHeightPixel * ( m_nTopRow - nOldTopRow );
2039 tools::Rectangle aDataArea( Point( 0, m_nColHeaderHeightPixel ), m_pDataWindow->GetOutputSizePixel() );
2041 if ( m_pDataWindow->GetBackground().IsScrollable()
2042 && std::abs( nPixelDelta ) < aDataArea.GetHeight()
2045 m_pDataWindow->Scroll( 0, static_cast<tools::Long>(-nPixelDelta), aDataArea, ScrollFlags::Clip | ScrollFlags::Update | ScrollFlags::Children);
2047 else
2049 m_pDataWindow->Invalidate( InvalidateFlags::Update );
2050 m_pDataWindow->GetParent()->Invalidate( InvalidateFlags::Transparent );
2053 // update the position at the vertical scrollbar
2054 if ( m_pVScroll != nullptr )
2055 m_pVScroll->SetThumbPos( m_nTopRow );
2058 // The scroll bar availability might change when we scrolled.
2059 // For instance, imagine a view with 10 rows, if which 5 fit into the window, numbered 1 to 10.
2060 // Now let
2061 // - the user scroll to row number 6, so the last 5 rows are visible
2062 // - somebody remove the last 4 rows
2063 // - the user scroll to row number 5 being the top row, so the last two rows are visible
2064 // - somebody remove row number 6
2065 // - the user scroll to row number 1
2066 // => in this case, the need for the scrollbar vanishes immediately.
2067 if ( m_nTopRow == 0 )
2068 m_rAntiImpl.PostUserEvent( LINK( this, TableControl_Impl, OnUpdateScrollbars ) );
2070 return static_cast<TableSize>( m_nTopRow - nOldTopRow );
2074 TableSize TableControl_Impl::impl_scrollRows( TableSize const i_rowDelta )
2076 return impl_ni_ScrollRows( i_rowDelta );
2080 TableSize TableControl_Impl::impl_ni_ScrollColumns( TableSize _nColumnDelta )
2082 // compute new left column
2083 const ColPos nNewLeftColumn =
2084 ::std::max(
2085 ::std::min( static_cast<ColPos>( m_nLeftColumn + _nColumnDelta ), static_cast<ColPos>( m_nColumnCount - 1 ) ),
2086 ColPos(0)
2089 const ColPos nOldLeftColumn = m_nLeftColumn;
2090 m_nLeftColumn = nNewLeftColumn;
2092 // if updates are enabled currently, scroll the viewport
2093 if ( m_nLeftColumn != nOldLeftColumn )
2095 SuppressCursor aHideCursor( *this );
2096 // TODO: call an onStartScroll at our listener (or better an own onStartScroll,
2097 // which hides the cursor and then calls the listener)
2098 // Same for onEndScroll
2100 // scroll the view port, if possible
2101 const tools::Rectangle aDataArea( Point( m_nRowHeaderWidthPixel, 0 ), m_pDataWindow->GetOutputSizePixel() );
2103 tools::Long nPixelDelta =
2104 m_aColumnWidths[ nOldLeftColumn ].getStart()
2105 - m_aColumnWidths[ m_nLeftColumn ].getStart();
2107 // update our column positions
2108 // Do this *before* scrolling, as ScrollFlags::Update will trigger a paint, which already needs the correct
2109 // information in m_aColumnWidths
2110 for (auto & columnWidth : m_aColumnWidths)
2112 columnWidth.move(nPixelDelta);
2115 // scroll the window content (if supported and possible), or invalidate the complete window
2116 if ( m_pDataWindow->GetBackground().IsScrollable()
2117 && std::abs( nPixelDelta ) < aDataArea.GetWidth()
2120 m_pDataWindow->Scroll( nPixelDelta, 0, aDataArea, ScrollFlags::Clip | ScrollFlags::Update );
2122 else
2124 m_pDataWindow->Invalidate( InvalidateFlags::Update );
2125 m_pDataWindow->GetParent()->Invalidate( InvalidateFlags::Transparent );
2128 // update the position at the horizontal scrollbar
2129 if ( m_pHScroll != nullptr )
2130 m_pHScroll->SetThumbPos( m_nLeftColumn );
2133 // The scroll bar availability might change when we scrolled. This is because we do not hide
2134 // the scrollbar when it is, in theory, unnecessary, but currently at a position > 0. In this case, it will
2135 // be auto-hidden when it's scrolled back to pos 0.
2136 if ( m_nLeftColumn == 0 )
2137 m_rAntiImpl.PostUserEvent( LINK( this, TableControl_Impl, OnUpdateScrollbars ) );
2139 return static_cast<TableSize>( m_nLeftColumn - nOldLeftColumn );
2143 TableSize TableControl_Impl::impl_scrollColumns( TableSize const i_columnDelta )
2145 return impl_ni_ScrollColumns( i_columnDelta );
2149 SelectionEngine* TableControl_Impl::getSelEngine()
2151 return m_pSelEngine.get();
2154 bool TableControl_Impl::isRowSelected( RowPos i_row ) const
2156 return ::std::find( m_aSelectedRows.begin(), m_aSelectedRows.end(), i_row ) != m_aSelectedRows.end();
2160 RowPos TableControl_Impl::getSelectedRowIndex( size_t const i_selectionIndex ) const
2162 if ( i_selectionIndex < m_aSelectedRows.size() )
2163 return m_aSelectedRows[ i_selectionIndex ];
2164 return ROW_INVALID;
2168 int TableControl_Impl::getRowSelectedNumber(const ::std::vector<RowPos>& selectedRows, RowPos current)
2170 std::vector<RowPos>::const_iterator it = ::std::find(selectedRows.begin(),selectedRows.end(),current);
2171 if ( it != selectedRows.end() )
2173 return it - selectedRows.begin();
2175 return -1;
2179 ColPos TableControl_Impl::impl_getColumnForOrdinate( tools::Long const i_ordinate ) const
2181 if ( ( m_aColumnWidths.empty() ) || ( i_ordinate < 0 ) )
2182 return COL_INVALID;
2184 if ( i_ordinate < m_nRowHeaderWidthPixel )
2185 return COL_ROW_HEADERS;
2187 ColumnPositions::const_iterator lowerBound = ::std::lower_bound(
2188 m_aColumnWidths.begin(),
2189 m_aColumnWidths.end(),
2190 MutableColumnMetrics(i_ordinate+1, i_ordinate+1),
2191 ColumnInfoPositionLess()
2193 if ( lowerBound == m_aColumnWidths.end() )
2195 // point is *behind* the start of the last column ...
2196 if ( i_ordinate < m_aColumnWidths.rbegin()->getEnd() )
2197 // ... but still before its end
2198 return m_nColumnCount - 1;
2199 return COL_INVALID;
2201 return lowerBound - m_aColumnWidths.begin();
2205 RowPos TableControl_Impl::impl_getRowForAbscissa( tools::Long const i_abscissa ) const
2207 if ( i_abscissa < 0 )
2208 return ROW_INVALID;
2210 if ( i_abscissa < m_nColHeaderHeightPixel )
2211 return ROW_COL_HEADERS;
2213 tools::Long const abscissa = i_abscissa - m_nColHeaderHeightPixel;
2214 tools::Long const row = m_nTopRow + abscissa / m_nRowHeightPixel;
2215 return row < m_pModel->getRowCount() ? row : ROW_INVALID;
2219 bool TableControl_Impl::markRowAsDeselected( RowPos const i_rowIndex )
2221 ::std::vector< RowPos >::iterator selPos = ::std::find( m_aSelectedRows.begin(), m_aSelectedRows.end(), i_rowIndex );
2222 if ( selPos == m_aSelectedRows.end() )
2223 return false;
2225 m_aSelectedRows.erase( selPos );
2226 return true;
2230 bool TableControl_Impl::markRowAsSelected( RowPos const i_rowIndex )
2232 if ( isRowSelected( i_rowIndex ) )
2233 return false;
2235 SelectionMode const eSelMode = getSelEngine()->GetSelectionMode();
2236 switch ( eSelMode )
2238 case SelectionMode::Single:
2239 if ( !m_aSelectedRows.empty() )
2241 OSL_ENSURE( m_aSelectedRows.size() == 1, "TableControl::markRowAsSelected: SingleSelection with more than one selected element?" );
2242 m_aSelectedRows[0] = i_rowIndex;
2243 break;
2245 [[fallthrough]];
2247 case SelectionMode::Multiple:
2248 m_aSelectedRows.push_back( i_rowIndex );
2249 break;
2251 default:
2252 OSL_ENSURE( false, "TableControl_Impl::markRowAsSelected: unsupported selection mode!" );
2253 return false;
2256 return true;
2260 bool TableControl_Impl::markAllRowsAsDeselected()
2262 if ( m_aSelectedRows.empty() )
2263 return false;
2265 m_aSelectedRows.clear();
2266 return true;
2270 bool TableControl_Impl::markAllRowsAsSelected()
2272 SelectionMode const eSelMode = getSelEngine()->GetSelectionMode();
2273 ENSURE_OR_RETURN_FALSE( eSelMode == SelectionMode::Multiple, "TableControl_Impl::markAllRowsAsSelected: unsupported selection mode!" );
2275 if ( m_aSelectedRows.size() == size_t( m_pModel->getRowCount() ) )
2277 #if OSL_DEBUG_LEVEL > 0
2278 for ( TableSize row = 0; row < m_pModel->getRowCount(); ++row )
2280 OSL_ENSURE( isRowSelected( row ), "TableControl_Impl::markAllRowsAsSelected: inconsistency in the selected rows!" );
2282 #endif
2283 // already all rows marked as selected
2284 return false;
2287 m_aSelectedRows.clear();
2288 for ( RowPos i=0; i < m_pModel->getRowCount(); ++i )
2289 m_aSelectedRows.push_back(i);
2291 return true;
2295 void TableControl_Impl::commitAccessibleEvent( sal_Int16 const i_eventID )
2297 impl_commitAccessibleEvent( i_eventID, Any() );
2301 void TableControl_Impl::commitCellEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue )
2303 if ( impl_isAccessibleAlive() )
2304 m_pAccessibleTable->commitCellEvent( i_eventID, i_newValue, i_oldValue );
2308 void TableControl_Impl::commitTableEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue )
2310 if ( impl_isAccessibleAlive() )
2311 m_pAccessibleTable->commitTableEvent( i_eventID, i_newValue, i_oldValue );
2315 tools::Rectangle TableControl_Impl::calcHeaderRect(bool bColHeader)
2317 tools::Rectangle const aRectTableWithHeaders( impl_getAllVisibleCellsArea() );
2318 Size const aSizeTableWithHeaders( aRectTableWithHeaders.GetSize() );
2319 if ( bColHeader )
2320 return tools::Rectangle( aRectTableWithHeaders.TopLeft(), Size( aSizeTableWithHeaders.Width(), m_nColHeaderHeightPixel ) );
2321 else
2322 return tools::Rectangle( aRectTableWithHeaders.TopLeft(), Size( m_nRowHeaderWidthPixel, aSizeTableWithHeaders.Height() ) );
2326 tools::Rectangle TableControl_Impl::calcHeaderCellRect( bool bColHeader, sal_Int32 nPos )
2328 tools::Rectangle const aHeaderRect = calcHeaderRect( bColHeader );
2329 TableCellGeometry const aGeometry(
2330 *this, aHeaderRect,
2331 bColHeader ? nPos : COL_ROW_HEADERS,
2332 bColHeader ? ROW_COL_HEADERS : nPos
2334 return aGeometry.getRect();
2338 tools::Rectangle TableControl_Impl::calcTableRect() const
2340 return impl_getAllVisibleDataCellArea();
2344 tools::Rectangle TableControl_Impl::calcCellRect( sal_Int32 nRow, sal_Int32 nCol ) const
2346 tools::Rectangle aCellRect;
2347 impl_getCellRect( nRow, nCol, aCellRect );
2348 return aCellRect;
2352 IMPL_LINK_NOARG( TableControl_Impl, OnUpdateScrollbars, void*, void )
2354 // TODO: can't we simply use lcl_updateScrollbar here, so the scrollbars ranges are updated, instead of
2355 // doing a complete re-layout?
2356 impl_ni_relayout();
2360 IMPL_LINK( TableControl_Impl, OnScroll, ScrollBar*, _pScrollbar, void )
2362 DBG_ASSERT( ( _pScrollbar == m_pVScroll ) || ( _pScrollbar == m_pHScroll ),
2363 "TableControl_Impl::OnScroll: where did this come from?" );
2365 if ( _pScrollbar == m_pVScroll )
2366 impl_ni_ScrollRows( _pScrollbar->GetDelta() );
2367 else
2368 impl_ni_ScrollColumns( _pScrollbar->GetDelta() );
2372 Reference< XAccessible > TableControl_Impl::getAccessible( vcl::Window& i_parentWindow )
2374 DBG_TESTSOLARMUTEX();
2375 if ( m_pAccessibleTable == nullptr )
2377 Reference< XAccessible > const xAccParent = i_parentWindow.GetAccessible();
2378 if ( xAccParent.is() )
2380 m_pAccessibleTable = m_aFactoryAccess.getFactory().createAccessibleTableControl(
2381 xAccParent, m_rAntiImpl
2386 Reference< XAccessible > xAccessible;
2387 if ( m_pAccessibleTable )
2388 xAccessible = m_pAccessibleTable->getMyself();
2389 return xAccessible;
2393 void TableControl_Impl::disposeAccessible()
2395 if ( m_pAccessibleTable )
2396 m_pAccessibleTable->DisposeAccessImpl();
2397 m_pAccessibleTable = nullptr;
2401 bool TableControl_Impl::impl_isAccessibleAlive() const
2403 return ( nullptr != m_pAccessibleTable ) && m_pAccessibleTable->isAlive();
2407 void TableControl_Impl::impl_commitAccessibleEvent( sal_Int16 const i_eventID, Any const & i_newValue )
2409 if ( impl_isAccessibleAlive() )
2410 m_pAccessibleTable->commitEvent( i_eventID, i_newValue );
2414 //= TableFunctionSet
2417 TableFunctionSet::TableFunctionSet(TableControl_Impl* _pTableControl)
2418 :m_pTableControl( _pTableControl)
2419 ,m_nCurrentRow( ROW_INVALID )
2423 TableFunctionSet::~TableFunctionSet()
2427 void TableFunctionSet::BeginDrag()
2431 void TableFunctionSet::CreateAnchor()
2433 m_pTableControl->setAnchor( m_pTableControl->getCurRow() );
2437 void TableFunctionSet::DestroyAnchor()
2439 m_pTableControl->setAnchor( ROW_INVALID );
2443 void TableFunctionSet::SetCursorAtPoint(const Point& rPoint, bool bDontSelectAtCursor)
2445 // newRow is the row which includes the point, getCurRow() is the last selected row, before the mouse click
2446 RowPos newRow = m_pTableControl->getRowAtPoint( rPoint );
2447 if ( newRow == ROW_COL_HEADERS )
2448 newRow = m_pTableControl->getTopRow();
2450 ColPos newCol = m_pTableControl->getColAtPoint( rPoint );
2451 if ( newCol == COL_ROW_HEADERS )
2452 newCol = m_pTableControl->getLeftColumn();
2454 if ( ( newRow == ROW_INVALID ) || ( newCol == COL_INVALID ) )
2455 return;
2457 if ( bDontSelectAtCursor )
2459 if ( m_pTableControl->getSelectedRowCount() > 1 )
2460 m_pTableControl->getSelEngine()->AddAlways(true);
2462 else if ( m_pTableControl->getAnchor() == m_pTableControl->getCurRow() )
2464 //selected region lies above the last selection
2465 if( m_pTableControl->getCurRow() >= newRow)
2467 //put selected rows in vector
2468 while ( m_pTableControl->getAnchor() >= newRow )
2470 m_pTableControl->markRowAsSelected( m_pTableControl->getAnchor() );
2471 m_pTableControl->setAnchor( m_pTableControl->getAnchor() - 1 );
2473 m_pTableControl->setAnchor( m_pTableControl->getAnchor() + 1 );
2475 //selected region lies beneath the last selected row
2476 else
2478 while ( m_pTableControl->getAnchor() <= newRow )
2480 m_pTableControl->markRowAsSelected( m_pTableControl->getAnchor() );
2481 m_pTableControl->setAnchor( m_pTableControl->getAnchor() + 1 );
2483 m_pTableControl->setAnchor( m_pTableControl->getAnchor() - 1 );
2485 m_pTableControl->invalidateSelectedRegion( m_pTableControl->getCurRow(), newRow );
2487 //no region selected
2488 else
2490 if ( !m_pTableControl->hasRowSelection() )
2491 m_pTableControl->markRowAsSelected( newRow );
2492 else
2494 if ( m_pTableControl->getSelEngine()->GetSelectionMode() == SelectionMode::Single )
2496 DeselectAll();
2497 m_pTableControl->markRowAsSelected( newRow );
2499 else
2501 m_pTableControl->markRowAsSelected( newRow );
2504 if ( m_pTableControl->getSelectedRowCount() > 1 && m_pTableControl->getSelEngine()->GetSelectionMode() != SelectionMode::Single )
2505 m_pTableControl->getSelEngine()->AddAlways(true);
2507 m_pTableControl->invalidateRow( newRow );
2509 m_pTableControl->goTo( newCol, newRow );
2512 bool TableFunctionSet::IsSelectionAtPoint( const Point& rPoint )
2514 m_pTableControl->getSelEngine()->AddAlways(false);
2515 if ( !m_pTableControl->hasRowSelection() )
2516 return false;
2517 else
2519 RowPos curRow = m_pTableControl->getRowAtPoint( rPoint );
2520 m_pTableControl->setAnchor( ROW_INVALID );
2521 bool selected = m_pTableControl->isRowSelected( curRow );
2522 m_nCurrentRow = curRow;
2523 return selected;
2527 void TableFunctionSet::DeselectAtPoint( const Point& )
2529 m_pTableControl->invalidateRow( m_nCurrentRow );
2530 m_pTableControl->markRowAsDeselected( m_nCurrentRow );
2534 void TableFunctionSet::DeselectAll()
2536 if ( m_pTableControl->hasRowSelection() )
2538 for ( size_t i=0; i<m_pTableControl->getSelectedRowCount(); ++i )
2540 RowPos const rowIndex = m_pTableControl->getSelectedRowIndex(i);
2541 m_pTableControl->invalidateRow( rowIndex );
2544 m_pTableControl->markAllRowsAsDeselected();
2549 } // namespace svt::table
2552 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */