1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
46 #define MIN_COLUMN_WIDTH_PIXEL 4
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
;
69 ITableControl
& m_rTable
;
72 explicit SuppressCursor( ITableControl
& _rTable
)
75 m_rTable
.hideCursor();
79 m_rTable
.showCursor();
86 /** default implementation of an ->ITableModel, used as fallback when no
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
99 // ITableModel overridables
100 virtual TableSize
getColumnCount() const override
104 virtual TableSize
getRowCount() const override
108 virtual bool hasColumnHeaders() const override
112 virtual bool hasRowHeaders() const override
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
133 virtual TableMetrics
getColumnHeaderHeight() const override
137 virtual TableMetrics
getRowHeaderWidth() const override
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
199 virtual bool isEnabled() const override
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
218 TableControl_Impl::TableControl_Impl( TableControl
& _rAntiImpl
)
219 :m_rAntiImpl ( _rAntiImpl
)
220 ,m_pModel ( std::make_shared
<EmptyTableModel
>() )
222 ,m_nRowHeightPixel ( 15 )
223 ,m_nColHeaderHeightPixel( 0 )
224 ,m_nRowHeaderWidthPixel ( 0 )
225 ,m_nColumnCount ( 0 )
227 ,m_nCurColumn ( COL_INVALID
)
228 ,m_nCurRow ( ROW_INVALID
)
231 ,m_nCursorHidden ( 1 )
232 ,m_pDataWindow ( VclPtr
<TableDataWindow
>::Create( *this ) )
233 ,m_pVScroll ( nullptr )
234 ,m_pHScroll ( nullptr )
235 ,m_pScrollCorner ( nullptr )
237 ,m_pTableFunctionSet ( new TableFunctionSet( this ) )
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 );
263 m_pModel
->removeTableModelListener( shared_from_this() );
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();
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;
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
)
296 selectionIndex
+= i_offset
;
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
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 ) )
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;
349 selectionChanged
= markAllRowsAsDeselected();
352 lastRemovedRow
= m_nRowCount
- 1;
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 );
378 m_nCurRow
= ROW_INVALID
;
382 else if ( m_nRowCount
== 0 )
388 // relayout, since the scrollbar need might have changed
391 // notify A11Y events
392 if ( impl_isAccessibleAlive() )
395 AccessibleEventId::TABLE_MODEL_CHANGED
,
396 Any( AccessibleTableModelChange(
397 AccessibleTableModelChangeType::ROWS_REMOVED
,
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();
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
);
435 m_nCurColumn
= COL_INVALID
;
440 m_rAntiImpl
.Invalidate();
444 void TableControl_Impl::allColumnsRemoved()
446 m_nColumnCount
= m_pModel
->getColumnCount();
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();
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
);
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
530 m_nColHeaderHeightPixel
531 + impl_getVisibleRows( true ) * m_nRowHeightPixel
538 tools::Rectangle
TableControl_Impl::impl_getAllVisibleDataCellArea() const
540 tools::Rectangle
aArea( impl_getAllVisibleCellsArea() );
541 aArea
.SetLeft( m_nRowHeaderWidthPixel
);
542 aArea
.SetTop( m_nColHeaderHeightPixel
);
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();
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
)
588 if ( i_visibility
== ScrollbarShowAlways
)
590 if ( i_position
> 0 )
592 if ( i_availableSpace
>= i_neededSpace
)
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(
630 WB_DRAG
| ( _bHorizontal
? WB_HSCROLL
: WB_VSCROLL
)
632 _rpBar
->SetScrollHdl( _rScrollHandler
);
633 // get some speed into the scrolling...
634 lcl_setButtonRepeat( *_rpBar
);
639 _rpBar
->SetRange( Range( 0, _nRange
) );
640 _rpBar
->SetVisibleSize( _nVisibleUnits
);
641 _rpBar
->SetPageSize( _nVisibleUnits
);
642 _rpBar
->SetLineSize( 1 );
643 _rpBar
->SetThumbPos( _nPosition
);
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
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();
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!" );
731 tools::Long
const currentWidth
= appFontWidthToPixel( pColumn
->getWidth() );
732 currentColWidths
.push_back( currentWidth
);
735 accumulatedCurrentWidth
+= currentWidth
;
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
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() );
753 effectiveMin
= minWidth
;
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
;
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
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
;
798 bool 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 )
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
830 o_newColWidthsPixel
[i
] = newColWidth
;
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 )
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
;
855 ++o_newColWidthsPixel
[i
];
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
;
875 bool 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 )
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
905 o_newColWidthsPixel
[i
] = newColWidth
;
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 )
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
;
930 --o_newColWidthsPixel
[i
];
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 );
949 ::comphelper::FlagRestorationGuard
const aWidthUpdateFlag( m_bUpdatingColWidths
, true );
950 SuppressCursor
aHideCursor( *this );
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
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
)
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(
1081 i_verticalScrollbar
,
1082 lcl_getRowsFittingInto( i_dataCellPlayground
.GetHeight(), m_nRowHeightPixel
, false ),
1084 m_nTopRow
, // current position
1085 m_nRowCount
, // range
1087 LINK( this, TableControl_Impl
, OnScroll
) // scroll handler
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(
1105 i_horizontalScrollbar
,
1106 lcl_getColumnsVisibleWithin( i_dataCellPlayground
, m_nLeftColumn
, *this, false ),
1108 m_nLeftColumn
, // current position
1109 m_nColumnCount
, // range
1111 LINK( this, TableControl_Impl
, OnScroll
) // scroll handler
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
1163 void TableControl_Impl::onResize()
1166 checkCursorPosition();
1170 void TableControl_Impl::doPaintContent(vcl::RenderContext
& rRenderContext
, const tools::Rectangle
& _rUpdateRect
)
1174 PTableRenderer pRenderer
= getModel()->getRenderer();
1175 DBG_ASSERT(!!pRenderer
, "TableDataWindow::doPaintContent: invalid renderer!");
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
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())
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();
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())
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
);
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;
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 )
1302 m_aSelectedRows
.push_back(m_nCurRow
);
1305 m_aSelectedRows
.push_back(m_nCurRow
);
1306 invalidateRow( m_nCurRow
);
1307 ensureVisible(m_nCurColumn
,m_nCurRow
);
1308 selectionChanged
= true;
1313 if ( m_nCurRow
< m_nRowCount
- 1 )
1314 bSuccess
= goTo( m_nCurColumn
, m_nCurRow
+ 1 );
1319 if(m_pSelEngine
->GetSelectionMode() == SelectionMode::Single
)
1321 if(!m_aSelectedRows
.empty())
1323 invalidateSelectedRows();
1324 m_aSelectedRows
.clear();
1329 m_aSelectedRows
.push_back(m_nCurRow
);
1330 invalidateRow( m_nCurRow
);
1334 m_aSelectedRows
.push_back(m_nCurRow
);
1335 invalidateRow( m_nCurRow
);
1337 ensureVisible(m_nCurColumn
,m_nCurRow
);
1338 selectionChanged
= true;
1343 if ( m_nCurRow
> 0 )
1344 bSuccess
= goTo( m_nCurColumn
, m_nCurRow
- 1 );
1348 if ( m_nCurColumn
> 0 )
1349 bSuccess
= goTo( m_nCurColumn
- 1, m_nCurRow
);
1351 if ( ( m_nCurColumn
== 0) && ( m_nCurRow
> 0 ) )
1352 bSuccess
= goTo( m_nColumnCount
- 1, m_nCurRow
- 1 );
1356 if ( m_nCurColumn
< m_nColumnCount
- 1 )
1357 bSuccess
= goTo( m_nCurColumn
+ 1, m_nCurRow
);
1359 if ( ( m_nCurColumn
== m_nColumnCount
- 1 ) && ( m_nCurRow
< m_nRowCount
- 1 ) )
1360 bSuccess
= goTo( 0, m_nCurRow
+ 1 );
1363 case cursorToLineStart
:
1364 bSuccess
= goTo( 0, m_nCurRow
);
1367 case cursorToLineEnd
:
1368 bSuccess
= goTo( m_nColumnCount
- 1, m_nCurRow
);
1371 case cursorToFirstLine
:
1372 bSuccess
= goTo( m_nCurColumn
, 0 );
1375 case cursorToLastLine
:
1376 bSuccess
= goTo( m_nCurColumn
, m_nRowCount
- 1 );
1381 RowPos nNewRow
= ::std::max( RowPos(0), m_nCurRow
- impl_getVisibleRows( false ) );
1382 bSuccess
= goTo( m_nCurColumn
, nNewRow
);
1386 case cursorPageDown
:
1388 RowPos nNewRow
= ::std::min( m_nRowCount
- 1, m_nCurRow
+ impl_getVisibleRows( false ) );
1389 bSuccess
= goTo( m_nCurColumn
, nNewRow
);
1394 bSuccess
= goTo( 0, 0 );
1397 case cursorBottomRight
:
1398 bSuccess
= goTo( m_nColumnCount
- 1, m_nRowCount
- 1 );
1401 case cursorSelectRow
:
1403 if(m_pSelEngine
->GetSelectionMode() == SelectionMode::NONE
)
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
1410 m_aSelectedRows
.erase(m_aSelectedRows
.begin()+pos
);
1411 if(m_aSelectedRows
.empty() && m_nAnchor
!= -1)
1414 //else select the row->put it in the vector
1416 m_aSelectedRows
.push_back(m_nCurRow
);
1417 invalidateRow( m_nCurRow
);
1418 selectionChanged
= true;
1422 case cursorSelectRowUp
:
1424 if(m_pSelEngine
->GetSelectionMode() == SelectionMode::NONE
)
1426 else if(m_pSelEngine
->GetSelectionMode() == SelectionMode::Single
)
1428 //if there are other selected rows, deselect them
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
1440 invalidateSelectedRows();
1441 m_aSelectedRows
.clear();
1442 m_aSelectedRows
.push_back(m_nCurRow
);
1443 invalidateRow( m_nCurRow
);
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);
1452 //if m_nCurRow isn't the upper one, can move up, otherwise not
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 );
1465 m_aSelectedRows
.push_back(m_nCurRow
);
1466 invalidateRow( m_nCurRow
);
1473 m_aSelectedRows
.push_back(m_nCurRow
);
1475 m_aSelectedRows
.push_back(m_nCurRow
);
1476 invalidateSelectedRegion( m_nCurRow
+1, m_nCurRow
);
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
1488 m_aSelectedRows
.push_back(m_nCurRow
);
1490 m_aSelectedRows
.push_back(m_nCurRow
);
1491 invalidateSelectedRegion( m_nCurRow
+1, m_nCurRow
);
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;
1507 case cursorSelectRowDown
:
1509 if(m_pSelEngine
->GetSelectionMode() == SelectionMode::NONE
)
1511 else if(m_pSelEngine
->GetSelectionMode() == SelectionMode::Single
)
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
1523 invalidateSelectedRows();
1524 m_aSelectedRows
.clear();
1525 m_aSelectedRows
.push_back(m_nCurRow
);
1526 invalidateRow( m_nCurRow
);
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);
1535 //if m_nCurRow isn't the last one, can move down, otherwise not
1536 if(m_nCurRow
<m_nRowCount
-1)
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 );
1548 m_aSelectedRows
.push_back(m_nCurRow
);
1549 invalidateRow( m_nCurRow
);
1554 if(m_nCurRow
<m_nRowCount
-1)
1556 m_aSelectedRows
.push_back(m_nCurRow
);
1558 m_aSelectedRows
.push_back(m_nCurRow
);
1559 invalidateSelectedRegion( m_nCurRow
-1, m_nCurRow
);
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
);
1571 m_aSelectedRows
.push_back(m_nCurRow
);
1572 invalidateSelectedRegion( m_nCurRow
-1, m_nCurRow
);
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;
1589 case cursorSelectRowAreaTop
:
1591 if(m_pSelEngine
->GetSelectionMode() == SelectionMode::NONE
)
1593 else if(m_pSelEngine
->GetSelectionMode() == SelectionMode::Single
)
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
1603 if ( !isRowSelected( iter
) )
1604 m_aSelectedRows
.push_back(iter
);
1608 m_nAnchor
= m_nCurRow
;
1609 m_pSelEngine
->SetAnchor(true);
1610 ensureVisible(m_nCurColumn
, 0);
1611 selectionChanged
= true;
1617 case cursorSelectRowAreaBottom
:
1619 if(m_pSelEngine
->GetSelectionMode() == SelectionMode::NONE
)
1621 else if(m_pSelEngine
->GetSelectionMode() == SelectionMode::Single
)
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
);
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;
1642 OSL_FAIL( "TableControl_Impl::dispatchAction: unsupported action!" );
1646 if ( bSuccess
&& selectionChanged
)
1648 m_rAntiImpl
.Select();
1655 void TableControl_Impl::impl_ni_doSwitchCursor( bool _bShow
)
1657 PTableRenderer pRenderer
= m_pModel
? m_pModel
->getRenderer() : PTableRenderer();
1660 tools::Rectangle aCellRect
;
1661 impl_getCellRect( m_nCurColumn
, m_nCurRow
, aCellRect
);
1663 pRenderer
->ShowCellCursor( *m_pDataWindow
, aCellRect
);
1665 pRenderer
->HideCellCursor( *m_pDataWindow
);
1670 void TableControl_Impl::impl_getCellRect( ColPos _nColumn
, RowPos _nRow
, tools::Rectangle
& _rCellRect
) const
1673 || ( COL_INVALID
== _nColumn
)
1674 || ( ROW_INVALID
== _nRow
)
1677 _rCellRect
.SetEmpty();
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
;
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
1731 ColPos
TableControl_Impl::getCurrentColumn() const
1733 return m_nCurColumn
;
1737 RowPos
TableControl_Impl::getCurrentRow() const
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
)
1771 case TableArea::ColumnHeaders
:
1772 m_pDataWindow
->Invalidate( calcHeaderRect( true ) );
1775 case TableArea::RowHeaders
:
1776 m_pDataWindow
->Invalidate( calcHeaderRect( false ) );
1779 case TableArea::All
:
1780 m_pDataWindow
->Invalidate();
1781 m_pDataWindow
->GetParent()->Invalidate( InvalidateFlags::Transparent
);
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
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() );
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
)
1910 if ( ( m_nLeftColumn
+ nVisibleCols
> m_nColumnCount
)
1911 && ( m_nColumnCount
>= nVisibleCols
)
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
,
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() ),
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!" );
1962 SuppressCursor
aHideCursor( *this );
1963 m_nCurColumn
= _nColumn
;
1966 // ensure that the new cell is visible
1967 ensureVisible( m_nCurColumn
, m_nCurRow
);
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
);
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
);
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
)
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
2021 ::std::min( static_cast<RowPos
>( m_nTopRow
+ _nRowDelta
), static_cast<RowPos
>( m_nRowCount
- 1 ) ),
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
);
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.
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
=
2085 ::std::min( static_cast<ColPos
>( m_nLeftColumn
+ _nColumnDelta
), static_cast<ColPos
>( m_nColumnCount
- 1 ) ),
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
);
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
];
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();
2179 ColPos
TableControl_Impl::impl_getColumnForOrdinate( tools::Long
const i_ordinate
) const
2181 if ( ( m_aColumnWidths
.empty() ) || ( i_ordinate
< 0 ) )
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;
2201 return lowerBound
- m_aColumnWidths
.begin();
2205 RowPos
TableControl_Impl::impl_getRowForAbscissa( tools::Long
const i_abscissa
) const
2207 if ( i_abscissa
< 0 )
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() )
2225 m_aSelectedRows
.erase( selPos
);
2230 bool TableControl_Impl::markRowAsSelected( RowPos
const i_rowIndex
)
2232 if ( isRowSelected( i_rowIndex
) )
2235 SelectionMode
const eSelMode
= getSelEngine()->GetSelectionMode();
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
;
2247 case SelectionMode::Multiple
:
2248 m_aSelectedRows
.push_back( i_rowIndex
);
2252 OSL_ENSURE( false, "TableControl_Impl::markRowAsSelected: unsupported selection mode!" );
2260 bool TableControl_Impl::markAllRowsAsDeselected()
2262 if ( m_aSelectedRows
.empty() )
2265 m_aSelectedRows
.clear();
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!" );
2283 // already all rows marked as selected
2287 m_aSelectedRows
.clear();
2288 for ( RowPos i
=0; i
< m_pModel
->getRowCount(); ++i
)
2289 m_aSelectedRows
.push_back(i
);
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() );
2320 return tools::Rectangle( aRectTableWithHeaders
.TopLeft(), Size( aSizeTableWithHeaders
.Width(), m_nColHeaderHeightPixel
) );
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(
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
);
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?
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() );
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();
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
) )
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
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
2490 if ( !m_pTableControl
->hasRowSelection() )
2491 m_pTableControl
->markRowAsSelected( newRow
);
2494 if ( m_pTableControl
->getSelEngine()->GetSelectionMode() == SelectionMode::Single
)
2497 m_pTableControl
->markRowAsSelected( newRow
);
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() )
2519 RowPos curRow
= m_pTableControl
->getRowAtPoint( rPoint
);
2520 m_pTableControl
->setAnchor( ROW_INVALID
);
2521 bool selected
= m_pTableControl
->isRowSelected( curRow
);
2522 m_nCurrentRow
= curRow
;
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: */