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 )
241 m_pSelEngine
.reset( new SelectionEngine( m_pDataWindow
.get(), m_pTableFunctionSet
.get() ) );
242 m_pSelEngine
->SetSelectionMode(SelectionMode::Single
);
243 m_pDataWindow
->SetPosPixel( Point( 0, 0 ) );
244 m_pDataWindow
->Show();
247 TableControl_Impl::~TableControl_Impl()
249 m_pVScroll
.disposeAndClear();
250 m_pHScroll
.disposeAndClear();
251 m_pScrollCorner
.disposeAndClear();
252 m_pDataWindow
.disposeAndClear();
253 m_pTableFunctionSet
.reset();
254 m_pSelEngine
.reset();
257 void TableControl_Impl::setModel( const PTableModel
& _pModel
)
259 SuppressCursor
aHideCursor( *this );
262 m_pModel
->removeTableModelListener( shared_from_this() );
266 m_pModel
= std::make_shared
<EmptyTableModel
>();
268 m_pModel
->addTableModelListener( shared_from_this() );
270 m_nCurRow
= ROW_INVALID
;
271 m_nCurColumn
= COL_INVALID
;
273 // recalc some model-dependent cached info
274 impl_ni_updateCachedModelValues();
277 // completely invalidate
278 m_rAntiImpl
.Invalidate();
280 // reset cursor to (0,0)
281 if ( m_nRowCount
) m_nCurRow
= 0;
282 if ( m_nColumnCount
) m_nCurColumn
= 0;
288 bool lcl_adjustSelectedRows( ::std::vector
< RowPos
>& io_selectionIndexes
, RowPos
const i_firstAffectedRowIndex
, TableSize
const i_offset
)
290 bool didChanges
= false;
291 for (auto & selectionIndex
: io_selectionIndexes
)
293 if ( selectionIndex
< i_firstAffectedRowIndex
)
295 selectionIndex
+= i_offset
;
303 void TableControl_Impl::rowsInserted( RowPos i_first
, RowPos i_last
)
305 OSL_PRECOND( i_last
>= i_first
, "TableControl_Impl::rowsInserted: invalid row indexes!" );
307 TableSize
const insertedRows
= i_last
- i_first
+ 1;
309 // adjust selection, if necessary
310 bool const selectionChanged
= lcl_adjustSelectedRows( m_aSelectedRows
, i_first
, insertedRows
);
312 // adjust our cached row count
313 m_nRowCount
= m_pModel
->getRowCount();
315 // if the rows have been inserted before the current row, adjust this
316 if ( i_first
<= m_nCurRow
)
317 goTo( m_nCurColumn
, m_nCurRow
+ insertedRows
);
319 // relayout, since the scrollbar need might have changed
322 // notify A1YY events
323 if ( impl_isAccessibleAlive() )
325 impl_commitAccessibleEvent( AccessibleEventId::TABLE_MODEL_CHANGED
,
326 Any( AccessibleTableModelChange( AccessibleTableModelChangeType::ROWS_INSERTED
, i_first
, i_last
, -1, -1 ) )
331 invalidateRowRange( i_first
, ROW_INVALID
);
333 // call selection handlers, if necessary
334 if ( selectionChanged
)
335 m_rAntiImpl
.Select();
339 void TableControl_Impl::rowsRemoved( RowPos i_first
, RowPos i_last
)
341 sal_Int32 firstRemovedRow
= i_first
;
342 sal_Int32 lastRemovedRow
= i_last
;
344 // adjust selection, if necessary
345 bool selectionChanged
= false;
348 selectionChanged
= markAllRowsAsDeselected();
351 lastRemovedRow
= m_nRowCount
- 1;
355 ENSURE_OR_RETURN_VOID( i_last
>= i_first
, "TableControl_Impl::rowsRemoved: illegal indexes!" );
357 for ( sal_Int32 row
= i_first
; row
<= i_last
; ++row
)
359 if ( markRowAsDeselected( row
) )
360 selectionChanged
= true;
363 if ( lcl_adjustSelectedRows( m_aSelectedRows
, i_last
+ 1, i_first
- i_last
- 1 ) )
364 selectionChanged
= true;
367 // adjust cached row count
368 m_nRowCount
= m_pModel
->getRowCount();
370 // adjust the current row, if it is larger than the row count now
371 if ( m_nCurRow
>= m_nRowCount
)
373 if ( m_nRowCount
> 0 )
374 goTo( m_nCurColumn
, m_nRowCount
- 1 );
377 m_nCurRow
= ROW_INVALID
;
381 else if ( m_nRowCount
== 0 )
387 // relayout, since the scrollbar need might have changed
390 // notify A11Y events
391 if ( impl_isAccessibleAlive() )
394 AccessibleEventId::TABLE_MODEL_CHANGED
,
395 Any( AccessibleTableModelChange(
396 AccessibleTableModelChangeType::ROWS_REMOVED
,
406 // schedule a repaint
407 invalidateRowRange( firstRemovedRow
, ROW_INVALID
);
409 // call selection handlers, if necessary
410 if ( selectionChanged
)
411 m_rAntiImpl
.Select();
415 void TableControl_Impl::columnInserted()
417 m_nColumnCount
= m_pModel
->getColumnCount();
420 m_rAntiImpl
.Invalidate();
424 void TableControl_Impl::columnRemoved()
426 m_nColumnCount
= m_pModel
->getColumnCount();
428 // adjust the current column, if it is larger than the column count now
429 if ( m_nCurColumn
>= m_nColumnCount
)
431 if ( m_nColumnCount
> 0 )
432 goTo( m_nCurColumn
- 1, m_nCurRow
);
434 m_nCurColumn
= COL_INVALID
;
439 m_rAntiImpl
.Invalidate();
443 void TableControl_Impl::allColumnsRemoved()
445 m_nColumnCount
= m_pModel
->getColumnCount();
448 m_rAntiImpl
.Invalidate();
452 void TableControl_Impl::cellsUpdated( RowPos
const i_firstRow
, RowPos
const i_lastRow
)
454 invalidateRowRange( i_firstRow
, i_lastRow
);
458 void TableControl_Impl::tableMetricsChanged()
460 impl_ni_updateCachedTableMetrics();
462 m_rAntiImpl
.Invalidate();
466 void TableControl_Impl::impl_invalidateColumn( ColPos
const i_column
)
468 tools::Rectangle
const aAllCellsArea( impl_getAllVisibleCellsArea() );
470 const TableColumnGeometry
aColumn( *this, aAllCellsArea
, i_column
);
471 if ( aColumn
.isValid() )
472 m_rAntiImpl
.Invalidate( aColumn
.getRect() );
476 void TableControl_Impl::columnChanged( ColPos
const i_column
, ColumnAttributeGroup
const i_attributeGroup
)
478 ColumnAttributeGroup
nGroup( i_attributeGroup
);
479 if ( nGroup
& ColumnAttributeGroup::APPEARANCE
)
481 impl_invalidateColumn( i_column
);
482 nGroup
&= ~ColumnAttributeGroup::APPEARANCE
;
485 if ( nGroup
& ColumnAttributeGroup::WIDTH
)
487 if ( !m_bUpdatingColWidths
)
489 impl_ni_relayout( i_column
);
490 invalidate( TableArea::All
);
493 nGroup
&= ~ColumnAttributeGroup::WIDTH
;
496 OSL_ENSURE( ( nGroup
== ColumnAttributeGroup::NONE
) || ( i_attributeGroup
== ColumnAttributeGroup::ALL
),
497 "TableControl_Impl::columnChanged: don't know how to handle this change!" );
501 tools::Rectangle
TableControl_Impl::impl_getAllVisibleCellsArea() const
503 tools::Rectangle
aArea( Point( 0, 0 ), Size( 0, 0 ) );
505 // determine the right-most border of the last column which is
506 // at least partially visible
507 aArea
.SetRight( m_nRowHeaderWidthPixel
);
508 if ( !m_aColumnWidths
.empty() )
510 // the number of pixels which are scrolled out of the left hand
511 // side of the window
512 const tools::Long nScrolledOutLeft
= m_nLeftColumn
== 0 ? 0 : m_aColumnWidths
[ m_nLeftColumn
- 1 ].getEnd();
514 ColumnPositions::const_reverse_iterator loop
= m_aColumnWidths
.rbegin();
517 aArea
.SetRight(loop
->getEnd() - nScrolledOutLeft
);
520 while ( ( loop
!= m_aColumnWidths
.rend() )
521 && ( loop
->getEnd() - nScrolledOutLeft
>= aArea
.Right() )
524 // so far, aArea.Right() denotes the first pixel *after* the cell area
525 aArea
.AdjustRight( -1 );
527 // determine the last row which is at least partially visible
529 m_nColHeaderHeightPixel
530 + impl_getVisibleRows( true ) * m_nRowHeightPixel
537 tools::Rectangle
TableControl_Impl::impl_getAllVisibleDataCellArea() const
539 tools::Rectangle
aArea( impl_getAllVisibleCellsArea() );
540 aArea
.SetLeft( m_nRowHeaderWidthPixel
);
541 aArea
.SetTop( m_nColHeaderHeightPixel
);
546 void TableControl_Impl::impl_ni_updateCachedTableMetrics()
548 m_nRowHeightPixel
= m_rAntiImpl
.LogicToPixel(Size(0, m_pModel
->getRowHeight()), MapMode(MapUnit::MapAppFont
)).Height();
550 m_nColHeaderHeightPixel
= 0;
551 if ( m_pModel
->hasColumnHeaders() )
552 m_nColHeaderHeightPixel
= m_rAntiImpl
.LogicToPixel(Size(0, m_pModel
->getColumnHeaderHeight()), MapMode(MapUnit::MapAppFont
)).Height();
554 m_nRowHeaderWidthPixel
= 0;
555 if ( m_pModel
->hasRowHeaders() )
556 m_nRowHeaderWidthPixel
= m_rAntiImpl
.LogicToPixel(Size(m_pModel
->getRowHeaderWidth(), 0), MapMode(MapUnit::MapAppFont
)).Width();
560 void TableControl_Impl::impl_ni_updateCachedModelValues()
562 m_pInputHandler
= m_pModel
->getInputHandler();
563 if ( !m_pInputHandler
)
564 m_pInputHandler
= std::make_shared
<DefaultInputHandler
>();
566 m_nColumnCount
= m_pModel
->getColumnCount();
567 if ( m_nLeftColumn
>= m_nColumnCount
)
568 m_nLeftColumn
= ( m_nColumnCount
> 0 ) ? m_nColumnCount
- 1 : 0;
570 m_nRowCount
= m_pModel
->getRowCount();
571 if ( m_nTopRow
>= m_nRowCount
)
572 m_nTopRow
= ( m_nRowCount
> 0 ) ? m_nRowCount
- 1 : 0;
574 impl_ni_updateCachedTableMetrics();
581 /// determines whether a scrollbar is needed for the given values
582 bool lcl_determineScrollbarNeed( tools::Long
const i_position
, ScrollbarVisibility
const i_visibility
,
583 tools::Long
const i_availableSpace
, tools::Long
const i_neededSpace
)
585 if ( i_visibility
== ScrollbarShowNever
)
587 if ( i_visibility
== ScrollbarShowAlways
)
589 if ( i_position
> 0 )
591 if ( i_availableSpace
>= i_neededSpace
)
597 void lcl_setButtonRepeat( vcl::Window
& _rWindow
)
599 AllSettings aSettings
= _rWindow
.GetSettings();
600 MouseSettings aMouseSettings
= aSettings
.GetMouseSettings();
602 aMouseSettings
.SetButtonRepeat( 0 );
603 aSettings
.SetMouseSettings( aMouseSettings
);
605 _rWindow
.SetSettings( aSettings
, true );
609 bool lcl_updateScrollbar( vcl::Window
& _rParent
, VclPtr
<ScrollBar
>& _rpBar
,
610 bool const i_needBar
, tools::Long _nVisibleUnits
,
611 tools::Long _nPosition
, tools::Long _nRange
,
612 bool _bHorizontal
, const Link
<ScrollBar
*,void>& _rScrollHandler
)
614 // do we currently have the scrollbar?
615 bool bHaveBar
= _rpBar
!= nullptr;
617 // do we need to correct the scrollbar visibility?
618 if ( bHaveBar
&& !i_needBar
)
620 if ( _rpBar
->IsTracking() )
621 _rpBar
->EndTracking();
622 _rpBar
.disposeAndClear();
624 else if ( !bHaveBar
&& i_needBar
)
626 _rpBar
= VclPtr
<ScrollBar
>::Create(
629 WB_DRAG
| ( _bHorizontal
? WB_HSCROLL
: WB_VSCROLL
)
631 _rpBar
->SetScrollHdl( _rScrollHandler
);
632 // get some speed into the scrolling...
633 lcl_setButtonRepeat( *_rpBar
);
638 _rpBar
->SetRange( Range( 0, _nRange
) );
639 _rpBar
->SetVisibleSize( _nVisibleUnits
);
640 _rpBar
->SetPageSize( _nVisibleUnits
);
641 _rpBar
->SetLineSize( 1 );
642 _rpBar
->SetThumbPos( _nPosition
);
646 return ( bHaveBar
!= i_needBar
);
650 /** returns the number of rows fitting into the given range,
651 for the given row height. Partially fitting rows are counted, too, if the
652 respective parameter says so.
654 TableSize
lcl_getRowsFittingInto( tools::Long _nOverallHeight
, tools::Long _nRowHeightPixel
, bool _bAcceptPartialRow
)
656 return _bAcceptPartialRow
657 ? ( _nOverallHeight
+ ( _nRowHeightPixel
- 1 ) ) / _nRowHeightPixel
658 : _nOverallHeight
/ _nRowHeightPixel
;
662 /** returns the number of columns fitting into the given area,
663 with the first visible column as given. Partially fitting columns are counted, too,
664 if the respective parameter says so.
666 TableSize
lcl_getColumnsVisibleWithin( const tools::Rectangle
& _rArea
, ColPos _nFirstVisibleColumn
,
667 const TableControl_Impl
& _rControl
, bool _bAcceptPartialRow
)
669 TableSize visibleColumns
= 0;
670 TableColumnGeometry
aColumn( _rControl
, _rArea
, _nFirstVisibleColumn
);
671 while ( aColumn
.isValid() )
673 if ( !_bAcceptPartialRow
)
674 if ( aColumn
.getRect().Right() > _rArea
.Right() )
675 // this column is only partially visible, and this is not allowed
681 return visibleColumns
;
687 tools::Long
TableControl_Impl::impl_ni_calculateColumnWidths( ColPos
const i_assumeInflexibleColumnsUpToIncluding
,
688 bool const i_assumeVerticalScrollbar
, ::std::vector
< tools::Long
>& o_newColWidthsPixel
) const
690 // the available horizontal space
691 tools::Long gridWidthPixel
= m_rAntiImpl
.GetOutputSizePixel().Width();
692 ENSURE_OR_RETURN( !!m_pModel
, "TableControl_Impl::impl_ni_calculateColumnWidths: not allowed without a model!", gridWidthPixel
);
693 if ( m_pModel
->hasRowHeaders() && ( gridWidthPixel
!= 0 ) )
695 gridWidthPixel
-= m_nRowHeaderWidthPixel
;
698 if ( i_assumeVerticalScrollbar
&& ( m_pModel
->getVerticalScrollbarVisibility() != ScrollbarShowNever
) )
700 tools::Long nScrollbarMetrics
= m_rAntiImpl
.GetSettings().GetStyleSettings().GetScrollBarSize();
701 gridWidthPixel
-= nScrollbarMetrics
;
704 // no need to do anything without columns
705 TableSize
const colCount
= m_pModel
->getColumnCount();
707 return gridWidthPixel
;
709 // collect some meta data for our columns:
710 // - their current (pixel) metrics
711 tools::Long accumulatedCurrentWidth
= 0;
712 ::std::vector
< tools::Long
> currentColWidths
;
713 currentColWidths
.reserve( colCount
);
714 typedef ::std::vector
< ::std::pair
< tools::Long
, long > > ColumnLimits
;
715 ColumnLimits effectiveColumnLimits
;
716 effectiveColumnLimits
.reserve( colCount
);
717 tools::Long accumulatedMinWidth
= 0;
718 tools::Long accumulatedMaxWidth
= 0;
719 // - their relative flexibility
720 ::std::vector
< ::sal_Int32
> columnFlexibilities
;
721 columnFlexibilities
.reserve( colCount
);
722 tools::Long flexibilityDenominator
= 0;
723 size_t flexibleColumnCount
= 0;
724 for ( ColPos col
= 0; col
< colCount
; ++col
)
726 PColumnModel
const pColumn
= m_pModel
->getColumnModel( col
);
727 ENSURE_OR_THROW( !!pColumn
, "invalid column returned by the model!" );
730 tools::Long
const currentWidth
= appFontWidthToPixel( pColumn
->getWidth() );
731 currentColWidths
.push_back( currentWidth
);
734 accumulatedCurrentWidth
+= currentWidth
;
737 ::sal_Int32 flexibility
= pColumn
->getFlexibility();
738 OSL_ENSURE( flexibility
>= 0, "TableControl_Impl::impl_ni_calculateColumnWidths: a column's flexibility should be non-negative." );
739 if ( ( flexibility
< 0 ) // normalization
740 || ( !pColumn
->isResizable() ) // column not resizable => no auto-resize
741 || ( col
<= i_assumeInflexibleColumnsUpToIncluding
) // column shall be treated as inflexible => respect this
746 tools::Long effectiveMin
= currentWidth
, effectiveMax
= currentWidth
;
747 // if the column is not flexible, it will not be asked for min/max, but we assume the current width as limit then
748 if ( flexibility
> 0 )
750 tools::Long
const minWidth
= appFontWidthToPixel( pColumn
->getMinWidth() );
752 effectiveMin
= minWidth
;
754 effectiveMin
= MIN_COLUMN_WIDTH_PIXEL
;
756 tools::Long
const maxWidth
= appFontWidthToPixel( pColumn
->getMaxWidth() );
757 OSL_ENSURE( minWidth
<= maxWidth
, "TableControl_Impl::impl_ni_calculateColumnWidths: pretty undecided 'bout its width limits, this column!" );
758 if ( ( maxWidth
> 0 ) && ( maxWidth
>= minWidth
) )
759 effectiveMax
= maxWidth
;
761 effectiveMax
= gridWidthPixel
; // TODO: any better guess here?
763 if ( effectiveMin
== effectiveMax
)
764 // if the min and the max are identical, this implies no flexibility at all
768 columnFlexibilities
.push_back( flexibility
);
769 flexibilityDenominator
+= flexibility
;
770 if ( flexibility
> 0 )
771 ++flexibleColumnCount
;
773 effectiveColumnLimits
.emplace_back( effectiveMin
, effectiveMax
);
774 accumulatedMinWidth
+= effectiveMin
;
775 accumulatedMaxWidth
+= effectiveMax
;
778 o_newColWidthsPixel
= currentColWidths
;
779 if ( flexibilityDenominator
== 0 )
781 // no column is flexible => don't adjust anything
783 else if ( gridWidthPixel
> accumulatedCurrentWidth
)
784 { // we have space to give away ...
785 tools::Long distributePixel
= gridWidthPixel
- accumulatedCurrentWidth
;
786 if ( gridWidthPixel
> accumulatedMaxWidth
)
788 // ... but the column's maximal widths are still less than we have
789 // => set them all to max
790 for ( svt::table::TableSize i
= 0; i
< colCount
; ++i
)
792 o_newColWidthsPixel
[i
] = effectiveColumnLimits
[i
].second
;
797 bool startOver
= false;
801 // distribute the remaining space amongst all columns with a positive flexibility
802 for ( size_t i
=0; i
<o_newColWidthsPixel
.size() && !startOver
; ++i
)
804 tools::Long
const columnFlexibility
= columnFlexibilities
[i
];
805 if ( columnFlexibility
== 0 )
808 tools::Long newColWidth
= currentColWidths
[i
] + columnFlexibility
* distributePixel
/ flexibilityDenominator
;
810 if ( newColWidth
> effectiveColumnLimits
[i
].second
)
811 { // that was too much, we hit the col's maximum
812 // set the new width to exactly this maximum
813 newColWidth
= effectiveColumnLimits
[i
].second
;
814 // adjust the flexibility denominator ...
815 flexibilityDenominator
-= columnFlexibility
;
816 columnFlexibilities
[i
] = 0;
817 --flexibleColumnCount
;
818 // ... and the remaining width ...
819 tools::Long
const difference
= newColWidth
- currentColWidths
[i
];
820 distributePixel
-= difference
;
821 // ... this way, we ensure that the width not taken up by this column is consumed by the other
822 // flexible ones (if there are some)
824 // and start over with the first column, since there might be earlier columns which need
825 // to be recalculated now
829 o_newColWidthsPixel
[i
] = newColWidth
;
834 // are there pixels left (might be caused by rounding errors)?
835 distributePixel
= gridWidthPixel
- ::std::accumulate( o_newColWidthsPixel
.begin(), o_newColWidthsPixel
.end(), 0 );
836 while ( ( distributePixel
> 0 ) && ( flexibleColumnCount
> 0 ) )
838 // yes => ignore relative flexibilities, and subsequently distribute single pixels to all flexible
839 // columns which did not yet reach their maximum.
840 for ( size_t i
=0; ( i
< o_newColWidthsPixel
.size() ) && ( distributePixel
> 0 ); ++i
)
842 if ( columnFlexibilities
[i
] == 0 )
845 OSL_ENSURE( o_newColWidthsPixel
[i
] <= effectiveColumnLimits
[i
].second
,
846 "TableControl_Impl::impl_ni_calculateColumnWidths: inconsistency!" );
847 if ( o_newColWidthsPixel
[i
] >= effectiveColumnLimits
[i
].first
)
849 columnFlexibilities
[i
] = 0;
850 --flexibleColumnCount
;
854 ++o_newColWidthsPixel
[i
];
860 else if ( gridWidthPixel
< accumulatedCurrentWidth
)
861 { // we need to take away some space from the columns which allow it ...
862 tools::Long takeAwayPixel
= accumulatedCurrentWidth
- gridWidthPixel
;
863 if ( gridWidthPixel
< accumulatedMinWidth
)
865 // ... but the column's minimal widths are still more than we have
866 // => set them all to min
867 for ( svt::table::TableSize i
= 0; i
< colCount
; ++i
)
869 o_newColWidthsPixel
[i
] = effectiveColumnLimits
[i
].first
;
874 bool startOver
= false;
878 // take away the space we need from the columns with a positive flexibility
879 for ( size_t i
=0; i
<o_newColWidthsPixel
.size() && !startOver
; ++i
)
881 tools::Long
const columnFlexibility
= columnFlexibilities
[i
];
882 if ( columnFlexibility
== 0 )
885 tools::Long newColWidth
= currentColWidths
[i
] - columnFlexibility
* takeAwayPixel
/ flexibilityDenominator
;
887 if ( newColWidth
< effectiveColumnLimits
[i
].first
)
888 { // that was too much, we hit the col's minimum
889 // set the new width to exactly this minimum
890 newColWidth
= effectiveColumnLimits
[i
].first
;
891 // adjust the flexibility denominator ...
892 flexibilityDenominator
-= columnFlexibility
;
893 columnFlexibilities
[i
] = 0;
894 --flexibleColumnCount
;
895 // ... and the remaining width ...
896 tools::Long
const difference
= currentColWidths
[i
] - newColWidth
;
897 takeAwayPixel
-= difference
;
899 // and start over with the first column, since there might be earlier columns which need
900 // to be recalculated now
904 o_newColWidthsPixel
[i
] = newColWidth
;
909 // are there pixels left (might be caused by rounding errors)?
910 takeAwayPixel
= ::std::accumulate( o_newColWidthsPixel
.begin(), o_newColWidthsPixel
.end(), 0 ) - gridWidthPixel
;
911 while ( ( takeAwayPixel
> 0 ) && ( flexibleColumnCount
> 0 ) )
913 // yes => ignore relative flexibilities, and subsequently take away pixels from all flexible
914 // columns which did not yet reach their minimum.
915 for ( size_t i
=0; ( i
< o_newColWidthsPixel
.size() ) && ( takeAwayPixel
> 0 ); ++i
)
917 if ( columnFlexibilities
[i
] == 0 )
920 OSL_ENSURE( o_newColWidthsPixel
[i
] >= effectiveColumnLimits
[i
].first
,
921 "TableControl_Impl::impl_ni_calculateColumnWidths: inconsistency!" );
922 if ( o_newColWidthsPixel
[i
] <= effectiveColumnLimits
[i
].first
)
924 columnFlexibilities
[i
] = 0;
925 --flexibleColumnCount
;
929 --o_newColWidthsPixel
[i
];
936 return gridWidthPixel
;
940 void TableControl_Impl::impl_ni_relayout( ColPos
const i_assumeInflexibleColumnsUpToIncluding
)
942 ENSURE_OR_RETURN_VOID( !m_bUpdatingColWidths
, "TableControl_Impl::impl_ni_relayout: recursive call detected!" );
944 m_aColumnWidths
.resize( 0 );
948 ::comphelper::FlagRestorationGuard
const aWidthUpdateFlag( m_bUpdatingColWidths
, true );
949 SuppressCursor
aHideCursor( *this );
953 // 1. adjust column widths, leaving space for a vertical scrollbar
954 // 2. determine need for a vertical scrollbar
955 // - V-YES: all fine, result from 1. is still valid
956 // - V-NO: result from 1. is still under consideration
958 // 3. determine need for a horizontal scrollbar
959 // - H-NO: all fine, result from 2. is still valid
960 // - H-YES: reconsider need for a vertical scrollbar, if result of 2. was V-NO
961 // - V-YES: all fine, result from 1. is still valid
962 // - V-NO: redistribute the remaining space (if any) amongst all columns which allow it
964 ::std::vector
< tools::Long
> newWidthsPixel
;
965 tools::Long gridWidthPixel
= impl_ni_calculateColumnWidths( i_assumeInflexibleColumnsUpToIncluding
, true, newWidthsPixel
);
967 // the width/height of a scrollbar, needed several times below
968 tools::Long
const nScrollbarMetrics
= m_rAntiImpl
.GetSettings().GetStyleSettings().GetScrollBarSize();
970 // determine the playground for the data cells (excluding headers)
971 // TODO: what if the control is smaller than needed for the headers/scrollbars?
972 tools::Rectangle
aDataCellPlayground( Point( 0, 0 ), m_rAntiImpl
.GetOutputSizePixel() );
973 aDataCellPlayground
.SetLeft( m_nRowHeaderWidthPixel
);
974 aDataCellPlayground
.SetTop( m_nColHeaderHeightPixel
);
976 OSL_ENSURE( ( m_nRowCount
== m_pModel
->getRowCount() ) && ( m_nColumnCount
== m_pModel
->getColumnCount() ),
977 "TableControl_Impl::impl_ni_relayout: how is this expected to work with invalid data?" );
978 tools::Long
const nAllColumnsWidth
= ::std::accumulate( newWidthsPixel
.begin(), newWidthsPixel
.end(), 0 );
980 ScrollbarVisibility
const eVertScrollbar
= m_pModel
->getVerticalScrollbarVisibility();
981 ScrollbarVisibility
const eHorzScrollbar
= m_pModel
->getHorizontalScrollbarVisibility();
983 // do we need a vertical scrollbar?
984 bool bNeedVerticalScrollbar
= lcl_determineScrollbarNeed(
985 m_nTopRow
, eVertScrollbar
, aDataCellPlayground
.GetHeight(), m_nRowHeightPixel
* m_nRowCount
);
986 bool bFirstRoundVScrollNeed
= false;
987 if ( bNeedVerticalScrollbar
)
989 aDataCellPlayground
.AdjustRight( -nScrollbarMetrics
);
990 bFirstRoundVScrollNeed
= true;
993 // do we need a horizontal scrollbar?
994 bool const bNeedHorizontalScrollbar
= lcl_determineScrollbarNeed(
995 m_nLeftColumn
, eHorzScrollbar
, aDataCellPlayground
.GetWidth(), nAllColumnsWidth
);
996 if ( bNeedHorizontalScrollbar
)
998 aDataCellPlayground
.AdjustBottom( -nScrollbarMetrics
);
1000 // now that we just found that we need a horizontal scrollbar,
1001 // the need for a vertical one may have changed, since the horizontal
1002 // SB might just occupy enough space so that not all rows do fit
1004 if ( !bFirstRoundVScrollNeed
)
1006 bNeedVerticalScrollbar
= lcl_determineScrollbarNeed(
1007 m_nTopRow
, eVertScrollbar
, aDataCellPlayground
.GetHeight(), m_nRowHeightPixel
* m_nRowCount
);
1008 if ( bNeedVerticalScrollbar
)
1010 aDataCellPlayground
.AdjustRight( -nScrollbarMetrics
);
1015 // the initial call to impl_ni_calculateColumnWidths assumed that we need a vertical scrollbar. If, by now,
1016 // we know that this is not the case, re-calculate the column widths.
1017 if ( !bNeedVerticalScrollbar
)
1018 gridWidthPixel
= impl_ni_calculateColumnWidths( i_assumeInflexibleColumnsUpToIncluding
, false, newWidthsPixel
);
1020 // update the column objects with the new widths we finally calculated
1021 TableSize
const colCount
= m_pModel
->getColumnCount();
1022 m_aColumnWidths
.reserve( colCount
);
1023 tools::Long accumulatedWidthPixel
= m_nRowHeaderWidthPixel
;
1024 bool anyColumnWidthChanged
= false;
1025 for ( ColPos col
= 0; col
< colCount
; ++col
)
1027 const tools::Long columnStart
= accumulatedWidthPixel
;
1028 const tools::Long columnEnd
= columnStart
+ newWidthsPixel
[col
];
1029 m_aColumnWidths
.emplace_back( columnStart
, columnEnd
);
1030 accumulatedWidthPixel
= columnEnd
;
1032 // and don't forget to forward this to the column models
1033 PColumnModel
const pColumn
= m_pModel
->getColumnModel( col
);
1034 ENSURE_OR_THROW( !!pColumn
, "invalid column returned by the model!" );
1036 tools::Long
const oldColumnWidthAppFont
= pColumn
->getWidth();
1037 tools::Long
const newColumnWidthAppFont
= pixelWidthToAppFont( newWidthsPixel
[col
] );
1038 pColumn
->setWidth( newColumnWidthAppFont
);
1040 anyColumnWidthChanged
|= ( oldColumnWidthAppFont
!= newColumnWidthAppFont
);
1043 // if the column widths changed, ensure everything is repainted
1044 if ( anyColumnWidthChanged
)
1045 invalidate( TableArea::All
);
1047 // if the column resizing happened to leave some space at the right, but there are columns
1048 // scrolled out to the left, scroll them in
1049 while ( ( m_nLeftColumn
> 0 )
1050 && ( accumulatedWidthPixel
- m_aColumnWidths
[ m_nLeftColumn
- 1 ].getStart() <= gridWidthPixel
)
1056 // now adjust the column metrics, since they currently ignore the horizontal scroll position
1057 if ( m_nLeftColumn
> 0 )
1059 const tools::Long offsetPixel
= m_aColumnWidths
[ 0 ].getStart() - m_aColumnWidths
[ m_nLeftColumn
].getStart();
1060 for (auto & columnWidth
: m_aColumnWidths
)
1062 columnWidth
.move( offsetPixel
);
1066 // show or hide the scrollbars as needed, and position the data window
1067 impl_ni_positionChildWindows( aDataCellPlayground
, bNeedVerticalScrollbar
, bNeedHorizontalScrollbar
);
1071 void TableControl_Impl::impl_ni_positionChildWindows( tools::Rectangle
const & i_dataCellPlayground
,
1072 bool const i_verticalScrollbar
, bool const i_horizontalScrollbar
)
1074 tools::Long
const nScrollbarMetrics
= m_rAntiImpl
.GetSettings().GetStyleSettings().GetScrollBarSize();
1076 // create or destroy the vertical scrollbar, as needed
1077 lcl_updateScrollbar(
1080 i_verticalScrollbar
,
1081 lcl_getRowsFittingInto( i_dataCellPlayground
.GetHeight(), m_nRowHeightPixel
, false ),
1083 m_nTopRow
, // current position
1084 m_nRowCount
, // range
1086 LINK( this, TableControl_Impl
, OnScroll
) // scroll handler
1092 tools::Rectangle
aScrollbarArea(
1093 Point( i_dataCellPlayground
.Right() + 1, 0 ),
1094 Size( nScrollbarMetrics
, i_dataCellPlayground
.Bottom() + 1 )
1096 m_pVScroll
->SetPosSizePixel(
1097 aScrollbarArea
.TopLeft(), aScrollbarArea
.GetSize() );
1100 // create or destroy the horizontal scrollbar, as needed
1101 lcl_updateScrollbar(
1104 i_horizontalScrollbar
,
1105 lcl_getColumnsVisibleWithin( i_dataCellPlayground
, m_nLeftColumn
, *this, false ),
1107 m_nLeftColumn
, // current position
1108 m_nColumnCount
, // range
1110 LINK( this, TableControl_Impl
, OnScroll
) // scroll handler
1116 TableSize
const nVisibleUnits
= lcl_getColumnsVisibleWithin( i_dataCellPlayground
, m_nLeftColumn
, *this, false );
1117 TableMetrics
const nRange
= m_nColumnCount
;
1118 if( m_nLeftColumn
+ nVisibleUnits
== nRange
- 1 )
1120 if ( m_aColumnWidths
[ nRange
- 1 ].getStart() - m_aColumnWidths
[ m_nLeftColumn
].getEnd() + m_aColumnWidths
[ nRange
-1 ].getWidth() > i_dataCellPlayground
.GetWidth() )
1122 m_pHScroll
->SetVisibleSize( nVisibleUnits
-1 );
1123 m_pHScroll
->SetPageSize( nVisibleUnits
- 1 );
1126 tools::Rectangle
aScrollbarArea(
1127 Point( 0, i_dataCellPlayground
.Bottom() + 1 ),
1128 Size( i_dataCellPlayground
.Right() + 1, nScrollbarMetrics
)
1130 m_pHScroll
->SetPosSizePixel(
1131 aScrollbarArea
.TopLeft(), aScrollbarArea
.GetSize() );
1134 // the corner window connecting the two scrollbars in the lower right corner
1135 bool bHaveScrollCorner
= nullptr != m_pScrollCorner
;
1136 bool bNeedScrollCorner
= ( nullptr != m_pHScroll
) && ( nullptr != m_pVScroll
);
1137 if ( bHaveScrollCorner
&& !bNeedScrollCorner
)
1139 m_pScrollCorner
.disposeAndClear();
1141 else if ( !bHaveScrollCorner
&& bNeedScrollCorner
)
1143 m_pScrollCorner
= VclPtr
<ScrollBarBox
>::Create( &m_rAntiImpl
);
1144 m_pScrollCorner
->SetSizePixel( Size( nScrollbarMetrics
, nScrollbarMetrics
) );
1145 m_pScrollCorner
->SetPosPixel( Point( i_dataCellPlayground
.Right() + 1, i_dataCellPlayground
.Bottom() + 1 ) );
1146 m_pScrollCorner
->Show();
1148 else if(bHaveScrollCorner
&& bNeedScrollCorner
)
1150 m_pScrollCorner
->SetPosPixel( Point( i_dataCellPlayground
.Right() + 1, i_dataCellPlayground
.Bottom() + 1 ) );
1151 m_pScrollCorner
->Show();
1154 // resize the data window
1155 m_pDataWindow
->SetSizePixel( Size(
1156 i_dataCellPlayground
.GetWidth() + m_nRowHeaderWidthPixel
,
1157 i_dataCellPlayground
.GetHeight() + m_nColHeaderHeightPixel
1162 void TableControl_Impl::onResize()
1165 checkCursorPosition();
1169 void TableControl_Impl::doPaintContent(vcl::RenderContext
& rRenderContext
, const tools::Rectangle
& _rUpdateRect
)
1173 PTableRenderer pRenderer
= getModel()->getRenderer();
1174 DBG_ASSERT(!!pRenderer
, "TableDataWindow::doPaintContent: invalid renderer!");
1178 // our current style settings, to be passed to the renderer
1179 const StyleSettings
& rStyle
= rRenderContext
.GetSettings().GetStyleSettings();
1180 m_nRowCount
= m_pModel
->getRowCount();
1181 // the area occupied by all (at least partially) visible cells, including
1183 tools::Rectangle
const aAllCellsWithHeaders( impl_getAllVisibleCellsArea() );
1185 // draw the header column area
1186 if (m_pModel
->hasColumnHeaders())
1188 TableRowGeometry
const aHeaderRow(*this, tools::Rectangle(Point(0, 0), aAllCellsWithHeaders
.BottomRight()), ROW_COL_HEADERS
);
1189 tools::Rectangle
const aColRect(aHeaderRow
.getRect());
1190 pRenderer
->PaintHeaderArea(rRenderContext
, aColRect
, true, false, rStyle
);
1191 // Note that strictly, aHeaderRow.getRect() also contains the intersection between column
1192 // and row header area. However, below we go to paint this intersection, again,
1193 // so this hopefully doesn't hurt if we already paint it here.
1195 for (TableCellGeometry
aCell(aHeaderRow
, m_nLeftColumn
); aCell
.isValid(); aCell
.moveRight())
1197 if (_rUpdateRect
.GetIntersection(aCell
.getRect()).IsEmpty())
1200 pRenderer
->PaintColumnHeader(aCell
.getColumn(), rRenderContext
, aCell
.getRect(), rStyle
);
1203 // the area occupied by the row header, if any
1204 tools::Rectangle aRowHeaderArea
;
1205 if (m_pModel
->hasRowHeaders())
1207 aRowHeaderArea
= aAllCellsWithHeaders
;
1208 aRowHeaderArea
.SetRight( m_nRowHeaderWidthPixel
- 1 );
1210 TableSize
const nVisibleRows
= impl_getVisibleRows(true);
1211 TableSize nActualRows
= nVisibleRows
;
1212 if (m_nTopRow
+ nActualRows
> m_nRowCount
)
1213 nActualRows
= m_nRowCount
- m_nTopRow
;
1214 aRowHeaderArea
.SetBottom( m_nColHeaderHeightPixel
+ m_nRowHeightPixel
* nActualRows
- 1 );
1216 pRenderer
->PaintHeaderArea(rRenderContext
, aRowHeaderArea
, false, true, rStyle
);
1217 // Note that strictly, aRowHeaderArea also contains the intersection between column
1218 // and row header area. However, below we go to paint this intersection, again,
1219 // so this hopefully doesn't hurt if we already paint it here.
1221 if (m_pModel
->hasColumnHeaders())
1223 TableCellGeometry
const aIntersection(*this, tools::Rectangle(Point(0, 0), aAllCellsWithHeaders
.BottomRight()),
1224 COL_ROW_HEADERS
, ROW_COL_HEADERS
);
1225 tools::Rectangle
const aInters(aIntersection
.getRect());
1226 pRenderer
->PaintHeaderArea(rRenderContext
, aInters
, true, true, rStyle
);
1230 // draw the table content row by row
1231 TableSize colCount
= getModel()->getColumnCount();
1234 tools::Rectangle
const aAllDataCellsArea(impl_getAllVisibleDataCellArea());
1235 for (TableRowGeometry
aRowIterator(*this, aAllCellsWithHeaders
, getTopRow()); aRowIterator
.isValid(); aRowIterator
.moveDown())
1237 if (_rUpdateRect
.GetIntersection(aRowIterator
.getRect() ).IsEmpty())
1240 bool const isControlFocused
= m_rAntiImpl
.HasControlFocus();
1241 bool const isSelectedRow
= isRowSelected(aRowIterator
.getRow());
1243 tools::Rectangle
const aRect
= aRowIterator
.getRect().GetIntersection(aAllDataCellsArea
);
1245 // give the renderer a chance to prepare the row
1246 pRenderer
->PrepareRow(aRowIterator
.getRow(), isControlFocused
, isSelectedRow
, rRenderContext
, aRect
, rStyle
);
1248 // paint the row header
1249 if (m_pModel
->hasRowHeaders())
1251 const tools::Rectangle
aCurrentRowHeader(aRowHeaderArea
.GetIntersection(aRowIterator
.getRect()));
1252 pRenderer
->PaintRowHeader(rRenderContext
, aCurrentRowHeader
, rStyle
);
1258 // paint all cells in this row
1259 for (TableCellGeometry
aCell(aRowIterator
, m_nLeftColumn
); aCell
.isValid(); aCell
.moveRight())
1261 pRenderer
->PaintCell(aCell
.getColumn(), isSelectedRow
, isControlFocused
,
1262 rRenderContext
, aCell
.getRect(), rStyle
);
1267 void TableControl_Impl::hideCursor()
1269 if ( ++m_nCursorHidden
== 1 )
1270 impl_ni_doSwitchCursor( false );
1274 void TableControl_Impl::showCursor()
1276 DBG_ASSERT( m_nCursorHidden
> 0, "TableControl_Impl::showCursor: cursor not hidden!" );
1277 if ( --m_nCursorHidden
== 0 )
1278 impl_ni_doSwitchCursor( true );
1282 bool TableControl_Impl::dispatchAction( TableControlAction _eAction
)
1284 bool bSuccess
= false;
1285 bool selectionChanged
= false;
1290 if ( m_pSelEngine
->GetSelectionMode() == SelectionMode::Single
)
1292 //if other rows already selected, deselect them
1293 if(!m_aSelectedRows
.empty())
1295 invalidateSelectedRows();
1296 m_aSelectedRows
.clear();
1298 if ( m_nCurRow
< m_nRowCount
-1 )
1301 m_aSelectedRows
.push_back(m_nCurRow
);
1304 m_aSelectedRows
.push_back(m_nCurRow
);
1305 invalidateRow( m_nCurRow
);
1306 ensureVisible(m_nCurColumn
,m_nCurRow
);
1307 selectionChanged
= true;
1312 if ( m_nCurRow
< m_nRowCount
- 1 )
1313 bSuccess
= goTo( m_nCurColumn
, m_nCurRow
+ 1 );
1318 if(m_pSelEngine
->GetSelectionMode() == SelectionMode::Single
)
1320 if(!m_aSelectedRows
.empty())
1322 invalidateSelectedRows();
1323 m_aSelectedRows
.clear();
1328 m_aSelectedRows
.push_back(m_nCurRow
);
1329 invalidateRow( m_nCurRow
);
1333 m_aSelectedRows
.push_back(m_nCurRow
);
1334 invalidateRow( m_nCurRow
);
1336 ensureVisible(m_nCurColumn
,m_nCurRow
);
1337 selectionChanged
= true;
1342 if ( m_nCurRow
> 0 )
1343 bSuccess
= goTo( m_nCurColumn
, m_nCurRow
- 1 );
1347 if ( m_nCurColumn
> 0 )
1348 bSuccess
= goTo( m_nCurColumn
- 1, m_nCurRow
);
1350 if ( ( m_nCurColumn
== 0) && ( m_nCurRow
> 0 ) )
1351 bSuccess
= goTo( m_nColumnCount
- 1, m_nCurRow
- 1 );
1355 if ( m_nCurColumn
< m_nColumnCount
- 1 )
1356 bSuccess
= goTo( m_nCurColumn
+ 1, m_nCurRow
);
1358 if ( ( m_nCurColumn
== m_nColumnCount
- 1 ) && ( m_nCurRow
< m_nRowCount
- 1 ) )
1359 bSuccess
= goTo( 0, m_nCurRow
+ 1 );
1362 case cursorToLineStart
:
1363 bSuccess
= goTo( 0, m_nCurRow
);
1366 case cursorToLineEnd
:
1367 bSuccess
= goTo( m_nColumnCount
- 1, m_nCurRow
);
1370 case cursorToFirstLine
:
1371 bSuccess
= goTo( m_nCurColumn
, 0 );
1374 case cursorToLastLine
:
1375 bSuccess
= goTo( m_nCurColumn
, m_nRowCount
- 1 );
1380 RowPos nNewRow
= ::std::max( RowPos(0), m_nCurRow
- impl_getVisibleRows( false ) );
1381 bSuccess
= goTo( m_nCurColumn
, nNewRow
);
1385 case cursorPageDown
:
1387 RowPos nNewRow
= ::std::min( m_nRowCount
- 1, m_nCurRow
+ impl_getVisibleRows( false ) );
1388 bSuccess
= goTo( m_nCurColumn
, nNewRow
);
1393 bSuccess
= goTo( 0, 0 );
1396 case cursorBottomRight
:
1397 bSuccess
= goTo( m_nColumnCount
- 1, m_nRowCount
- 1 );
1400 case cursorSelectRow
:
1402 if(m_pSelEngine
->GetSelectionMode() == SelectionMode::NONE
)
1404 //pos is the position of the current row in the vector of selected rows, if current row is selected
1405 int pos
= getRowSelectedNumber(m_aSelectedRows
, m_nCurRow
);
1406 //if current row is selected, it should be deselected, when ALT+SPACE are pressed
1409 m_aSelectedRows
.erase(m_aSelectedRows
.begin()+pos
);
1410 if(m_aSelectedRows
.empty() && m_nAnchor
!= -1)
1413 //else select the row->put it in the vector
1415 m_aSelectedRows
.push_back(m_nCurRow
);
1416 invalidateRow( m_nCurRow
);
1417 selectionChanged
= true;
1421 case cursorSelectRowUp
:
1423 if(m_pSelEngine
->GetSelectionMode() == SelectionMode::NONE
)
1425 else if(m_pSelEngine
->GetSelectionMode() == SelectionMode::Single
)
1427 //if there are other selected rows, deselect them
1432 //there are other selected rows
1433 if(!m_aSelectedRows
.empty())
1435 //the anchor wasn't set -> a region is not selected, that's why clear all selection
1436 //and select the current row
1439 invalidateSelectedRows();
1440 m_aSelectedRows
.clear();
1441 m_aSelectedRows
.push_back(m_nCurRow
);
1442 invalidateRow( m_nCurRow
);
1446 //a region is already selected, prevRow is last selected row and the row above - nextRow - should be selected
1447 int prevRow
= getRowSelectedNumber(m_aSelectedRows
, m_nCurRow
);
1448 int nextRow
= getRowSelectedNumber(m_aSelectedRows
, m_nCurRow
-1);
1451 //if m_nCurRow isn't the upper one, can move up, otherwise not
1456 //if nextRow already selected, deselect it, otherwise select it
1457 if(nextRow
>-1 && m_aSelectedRows
[nextRow
] == m_nCurRow
)
1459 m_aSelectedRows
.erase(m_aSelectedRows
.begin()+prevRow
);
1460 invalidateRow( m_nCurRow
+ 1 );
1464 m_aSelectedRows
.push_back(m_nCurRow
);
1465 invalidateRow( m_nCurRow
);
1472 m_aSelectedRows
.push_back(m_nCurRow
);
1474 m_aSelectedRows
.push_back(m_nCurRow
);
1475 invalidateSelectedRegion( m_nCurRow
+1, m_nCurRow
);
1482 //if nothing is selected and the current row isn't the upper one
1483 //select the current and one row above
1484 //otherwise select only the upper row
1487 m_aSelectedRows
.push_back(m_nCurRow
);
1489 m_aSelectedRows
.push_back(m_nCurRow
);
1490 invalidateSelectedRegion( m_nCurRow
+1, m_nCurRow
);
1494 m_aSelectedRows
.push_back(m_nCurRow
);
1495 invalidateRow( m_nCurRow
);
1498 m_pSelEngine
->SetAnchor(true);
1499 m_nAnchor
= m_nCurRow
;
1500 ensureVisible(m_nCurColumn
, m_nCurRow
);
1501 selectionChanged
= true;
1506 case cursorSelectRowDown
:
1508 if(m_pSelEngine
->GetSelectionMode() == SelectionMode::NONE
)
1510 else if(m_pSelEngine
->GetSelectionMode() == SelectionMode::Single
)
1516 if(!m_aSelectedRows
.empty())
1518 //the anchor wasn't set -> a region is not selected, that's why clear all selection
1519 //and select the current row
1522 invalidateSelectedRows();
1523 m_aSelectedRows
.clear();
1524 m_aSelectedRows
.push_back(m_nCurRow
);
1525 invalidateRow( m_nCurRow
);
1529 //a region is already selected, prevRow is last selected row and the row beneath - nextRow - should be selected
1530 int prevRow
= getRowSelectedNumber(m_aSelectedRows
, m_nCurRow
);
1531 int nextRow
= getRowSelectedNumber(m_aSelectedRows
, m_nCurRow
+1);
1534 //if m_nCurRow isn't the last one, can move down, otherwise not
1535 if(m_nCurRow
<m_nRowCount
-1)
1539 //if next row already selected, deselect it, otherwise select it
1540 if(nextRow
>-1 && m_aSelectedRows
[nextRow
] == m_nCurRow
)
1542 m_aSelectedRows
.erase(m_aSelectedRows
.begin()+prevRow
);
1543 invalidateRow( m_nCurRow
- 1 );
1547 m_aSelectedRows
.push_back(m_nCurRow
);
1548 invalidateRow( m_nCurRow
);
1553 if(m_nCurRow
<m_nRowCount
-1)
1555 m_aSelectedRows
.push_back(m_nCurRow
);
1557 m_aSelectedRows
.push_back(m_nCurRow
);
1558 invalidateSelectedRegion( m_nCurRow
-1, m_nCurRow
);
1565 //there wasn't any selection, select current and row beneath, otherwise only row beneath
1566 if(m_nCurRow
<m_nRowCount
-1)
1568 m_aSelectedRows
.push_back(m_nCurRow
);
1570 m_aSelectedRows
.push_back(m_nCurRow
);
1571 invalidateSelectedRegion( m_nCurRow
-1, m_nCurRow
);
1575 m_aSelectedRows
.push_back(m_nCurRow
);
1576 invalidateRow( m_nCurRow
);
1579 m_pSelEngine
->SetAnchor(true);
1580 m_nAnchor
= m_nCurRow
;
1581 ensureVisible(m_nCurColumn
, m_nCurRow
);
1582 selectionChanged
= true;
1588 case cursorSelectRowAreaTop
:
1590 if(m_pSelEngine
->GetSelectionMode() == SelectionMode::NONE
)
1592 else if(m_pSelEngine
->GetSelectionMode() == SelectionMode::Single
)
1596 //select the region between the current and the upper row
1597 RowPos iter
= m_nCurRow
;
1598 invalidateSelectedRegion( m_nCurRow
, 0 );
1599 //put the rows in vector
1602 if ( !isRowSelected( iter
) )
1603 m_aSelectedRows
.push_back(iter
);
1607 m_nAnchor
= m_nCurRow
;
1608 m_pSelEngine
->SetAnchor(true);
1609 ensureVisible(m_nCurColumn
, 0);
1610 selectionChanged
= true;
1616 case cursorSelectRowAreaBottom
:
1618 if(m_pSelEngine
->GetSelectionMode() == SelectionMode::NONE
)
1620 else if(m_pSelEngine
->GetSelectionMode() == SelectionMode::Single
)
1622 //select the region between the current and the last row
1623 RowPos iter
= m_nCurRow
;
1624 invalidateSelectedRegion( m_nCurRow
, m_nRowCount
-1 );
1625 //put the rows in the vector
1626 while(iter
<=m_nRowCount
)
1628 if ( !isRowSelected( iter
) )
1629 m_aSelectedRows
.push_back(iter
);
1632 m_nCurRow
= m_nRowCount
-1;
1633 m_nAnchor
= m_nCurRow
;
1634 m_pSelEngine
->SetAnchor(true);
1635 ensureVisible(m_nCurColumn
, m_nRowCount
-1);
1636 selectionChanged
= true;
1641 OSL_FAIL( "TableControl_Impl::dispatchAction: unsupported action!" );
1645 if ( bSuccess
&& selectionChanged
)
1647 m_rAntiImpl
.Select();
1654 void TableControl_Impl::impl_ni_doSwitchCursor( bool _bShow
)
1656 PTableRenderer pRenderer
= m_pModel
? m_pModel
->getRenderer() : PTableRenderer();
1659 tools::Rectangle aCellRect
;
1660 impl_getCellRect( m_nCurColumn
, m_nCurRow
, aCellRect
);
1662 pRenderer
->ShowCellCursor( *m_pDataWindow
, aCellRect
);
1664 pRenderer
->HideCellCursor( *m_pDataWindow
);
1669 void TableControl_Impl::impl_getCellRect( ColPos _nColumn
, RowPos _nRow
, tools::Rectangle
& _rCellRect
) const
1672 || ( COL_INVALID
== _nColumn
)
1673 || ( ROW_INVALID
== _nRow
)
1676 _rCellRect
.SetEmpty();
1680 TableCellGeometry
aCell( *this, impl_getAllVisibleCellsArea(), _nColumn
, _nRow
);
1681 _rCellRect
= aCell
.getRect();
1685 RowPos
TableControl_Impl::getRowAtPoint( const Point
& rPoint
) const
1687 return impl_getRowForAbscissa( rPoint
.Y() );
1691 ColPos
TableControl_Impl::getColAtPoint( const Point
& rPoint
) const
1693 return impl_getColumnForOrdinate( rPoint
.X() );
1697 TableCell
TableControl_Impl::hitTest( Point
const & i_point
) const
1699 TableCell
aCell( getColAtPoint( i_point
), getRowAtPoint( i_point
) );
1700 if ( aCell
.nColumn
> COL_ROW_HEADERS
)
1702 PColumnModel
const pColumn
= m_pModel
->getColumnModel( aCell
.nColumn
);
1703 MutableColumnMetrics
const & rColInfo( m_aColumnWidths
[ aCell
.nColumn
] );
1704 if ( ( rColInfo
.getEnd() - 3 <= i_point
.X() )
1705 && ( rColInfo
.getEnd() >= i_point
.X() )
1706 && pColumn
->isResizable()
1709 aCell
.eArea
= ColumnDivider
;
1716 ColumnMetrics
TableControl_Impl::getColumnMetrics( ColPos
const i_column
) const
1718 ENSURE_OR_RETURN( ( i_column
>= 0 ) && ( i_column
< m_pModel
->getColumnCount() ),
1719 "TableControl_Impl::getColumnMetrics: illegal column index!", ColumnMetrics() );
1720 return m_aColumnWidths
[ i_column
];
1724 PTableModel
TableControl_Impl::getModel() const
1730 ColPos
TableControl_Impl::getCurrentColumn() const
1732 return m_nCurColumn
;
1736 RowPos
TableControl_Impl::getCurrentRow() const
1742 ::Size
TableControl_Impl::getTableSizePixel() const
1744 return m_pDataWindow
->GetOutputSizePixel();
1748 void TableControl_Impl::setPointer( PointerStyle i_pointer
)
1750 m_pDataWindow
->SetPointer( i_pointer
);
1754 void TableControl_Impl::captureMouse()
1756 m_pDataWindow
->CaptureMouse();
1760 void TableControl_Impl::releaseMouse()
1762 m_pDataWindow
->ReleaseMouse();
1766 void TableControl_Impl::invalidate( TableArea
const i_what
)
1770 case TableArea::ColumnHeaders
:
1771 m_pDataWindow
->Invalidate( calcHeaderRect( true ) );
1774 case TableArea::RowHeaders
:
1775 m_pDataWindow
->Invalidate( calcHeaderRect( false ) );
1778 case TableArea::All
:
1779 m_pDataWindow
->Invalidate();
1780 m_pDataWindow
->GetParent()->Invalidate( InvalidateFlags::Transparent
);
1786 tools::Long
TableControl_Impl::pixelWidthToAppFont( tools::Long
const i_pixels
) const
1788 return m_pDataWindow
->PixelToLogic(Size(i_pixels
, 0), MapMode(MapUnit::MapAppFont
)).Width();
1792 tools::Long
TableControl_Impl::appFontWidthToPixel( tools::Long
const i_appFontUnits
) const
1794 return m_pDataWindow
->LogicToPixel(Size(i_appFontUnits
, 0), MapMode(MapUnit::MapAppFont
)).Width();
1798 void TableControl_Impl::hideTracking()
1800 m_pDataWindow
->HideTracking();
1804 void TableControl_Impl::showTracking( tools::Rectangle
const & i_location
, ShowTrackFlags
const i_flags
)
1806 m_pDataWindow
->ShowTracking( i_location
, i_flags
);
1810 void TableControl_Impl::activateCell( ColPos
const i_col
, RowPos
const i_row
)
1812 goTo( i_col
, i_row
);
1816 void TableControl_Impl::invalidateSelectedRegion( RowPos _nPrevRow
, RowPos _nCurRow
)
1818 // get the visible area of the table control and set the Left and right border of the region to be repainted
1819 tools::Rectangle
const aAllCells( impl_getAllVisibleCellsArea() );
1821 tools::Rectangle aInvalidateRect
;
1822 aInvalidateRect
.SetLeft( aAllCells
.Left() );
1823 aInvalidateRect
.SetRight( aAllCells
.Right() );
1824 // if only one row is selected
1825 if ( _nPrevRow
== _nCurRow
)
1827 tools::Rectangle aCellRect
;
1828 impl_getCellRect( m_nCurColumn
, _nCurRow
, aCellRect
);
1829 aInvalidateRect
.SetTop( aCellRect
.Top() );
1830 aInvalidateRect
.SetBottom( aCellRect
.Bottom() );
1832 //if the region is above the current row
1833 else if(_nPrevRow
< _nCurRow
)
1835 tools::Rectangle aCellRect
;
1836 impl_getCellRect( m_nCurColumn
, _nPrevRow
, aCellRect
);
1837 aInvalidateRect
.SetTop( aCellRect
.Top() );
1838 impl_getCellRect( m_nCurColumn
, _nCurRow
, aCellRect
);
1839 aInvalidateRect
.SetBottom( aCellRect
.Bottom() );
1841 //if the region is beneath the current row
1844 tools::Rectangle aCellRect
;
1845 impl_getCellRect( m_nCurColumn
, _nCurRow
, aCellRect
);
1846 aInvalidateRect
.SetTop( aCellRect
.Top() );
1847 impl_getCellRect( m_nCurColumn
, _nPrevRow
, aCellRect
);
1848 aInvalidateRect
.SetBottom( aCellRect
.Bottom() );
1851 invalidateRect(aInvalidateRect
);
1854 void TableControl_Impl::invalidateRect(const tools::Rectangle
&rInvalidateRect
)
1856 m_pDataWindow
->Invalidate( rInvalidateRect
,
1857 m_pDataWindow
->GetControlBackground().IsTransparent() ? InvalidateFlags::Transparent
: InvalidateFlags::NONE
);
1861 void TableControl_Impl::invalidateSelectedRows()
1863 for (auto const& selectedRow
: m_aSelectedRows
)
1865 invalidateRow(selectedRow
);
1870 void TableControl_Impl::invalidateRowRange( RowPos
const i_firstRow
, RowPos
const i_lastRow
)
1872 RowPos
const firstRow
= i_firstRow
< m_nTopRow
? m_nTopRow
: i_firstRow
;
1873 RowPos
const lastVisibleRow
= m_nTopRow
+ impl_getVisibleRows( true ) - 1;
1874 RowPos
const lastRow
= ( ( i_lastRow
== ROW_INVALID
) || ( i_lastRow
> lastVisibleRow
) ) ? lastVisibleRow
: i_lastRow
;
1876 tools::Rectangle aInvalidateRect
;
1878 tools::Rectangle
const aVisibleCellsArea( impl_getAllVisibleCellsArea() );
1879 TableRowGeometry
aRow( *this, aVisibleCellsArea
, firstRow
, true );
1880 while ( aRow
.isValid() && ( aRow
.getRow() <= lastRow
) )
1882 aInvalidateRect
.Union( aRow
.getRect() );
1886 if ( i_lastRow
== ROW_INVALID
)
1887 aInvalidateRect
.SetBottom( m_pDataWindow
->GetOutputSizePixel().Height() );
1889 invalidateRect(aInvalidateRect
);
1893 void TableControl_Impl::checkCursorPosition()
1896 TableSize nVisibleRows
= impl_getVisibleRows(true);
1897 TableSize nVisibleCols
= impl_getVisibleColumns(true);
1898 if ( ( m_nTopRow
+ nVisibleRows
> m_nRowCount
)
1899 && ( m_nRowCount
>= nVisibleRows
)
1909 if ( ( m_nLeftColumn
+ nVisibleCols
> m_nColumnCount
)
1910 && ( m_nColumnCount
>= nVisibleCols
)
1920 m_pDataWindow
->Invalidate();
1924 TableSize
TableControl_Impl::impl_getVisibleRows( bool _bAcceptPartialRow
) const
1926 DBG_ASSERT( m_pDataWindow
, "TableControl_Impl::impl_getVisibleRows: no data window!" );
1928 return lcl_getRowsFittingInto(
1929 m_pDataWindow
->GetOutputSizePixel().Height() - m_nColHeaderHeightPixel
,
1936 TableSize
TableControl_Impl::impl_getVisibleColumns( bool _bAcceptPartialCol
) const
1938 DBG_ASSERT( m_pDataWindow
, "TableControl_Impl::impl_getVisibleColumns: no data window!" );
1940 return lcl_getColumnsVisibleWithin(
1941 tools::Rectangle( Point( 0, 0 ), m_pDataWindow
->GetOutputSizePixel() ),
1949 bool TableControl_Impl::goTo( ColPos _nColumn
, RowPos _nRow
)
1951 // TODO: give veto listeners a chance
1953 if ( ( _nColumn
< 0 ) || ( _nColumn
>= m_nColumnCount
)
1954 || ( _nRow
< 0 ) || ( _nRow
>= m_nRowCount
)
1957 OSL_ENSURE( false, "TableControl_Impl::goTo: invalid row or column index!" );
1961 SuppressCursor
aHideCursor( *this );
1962 m_nCurColumn
= _nColumn
;
1965 // ensure that the new cell is visible
1966 ensureVisible( m_nCurColumn
, m_nCurRow
);
1971 void TableControl_Impl::ensureVisible( ColPos _nColumn
, RowPos _nRow
)
1973 DBG_ASSERT( ( _nColumn
>= 0 ) && ( _nColumn
< m_nColumnCount
)
1974 && ( _nRow
>= 0 ) && ( _nRow
< m_nRowCount
),
1975 "TableControl_Impl::ensureVisible: invalid coordinates!" );
1977 SuppressCursor
aHideCursor( *this );
1979 if ( _nColumn
< m_nLeftColumn
)
1980 impl_scrollColumns( _nColumn
- m_nLeftColumn
);
1983 TableSize nVisibleColumns
= impl_getVisibleColumns( false/*bAcceptPartialVisibility*/ );
1984 if ( _nColumn
> m_nLeftColumn
+ nVisibleColumns
- 1 )
1986 impl_scrollColumns( _nColumn
- ( m_nLeftColumn
+ nVisibleColumns
- 1 ) );
1987 // TODO: since not all columns have the same width, this might in theory result
1988 // in the column still not being visible.
1992 if ( _nRow
< m_nTopRow
)
1993 impl_scrollRows( _nRow
- m_nTopRow
);
1996 TableSize nVisibleRows
= impl_getVisibleRows( false/*_bAcceptPartialVisibility*/ );
1997 if ( _nRow
> m_nTopRow
+ nVisibleRows
- 1 )
1998 impl_scrollRows( _nRow
- ( m_nTopRow
+ nVisibleRows
- 1 ) );
2003 OUString
TableControl_Impl::getCellContentAsString( RowPos
const i_row
, ColPos
const i_col
)
2006 m_pModel
->getCellContent( i_col
, i_row
, aCellValue
);
2008 OUString sCellStringContent
;
2009 m_pModel
->getRenderer()->GetFormattedCellString( aCellValue
, sCellStringContent
);
2011 return sCellStringContent
;
2015 TableSize
TableControl_Impl::impl_ni_ScrollRows( TableSize _nRowDelta
)
2017 // compute new top row
2020 ::std::min( static_cast<RowPos
>( m_nTopRow
+ _nRowDelta
), static_cast<RowPos
>( m_nRowCount
- 1 ) ),
2024 RowPos nOldTopRow
= m_nTopRow
;
2025 m_nTopRow
= nNewTopRow
;
2027 // if updates are enabled currently, scroll the viewport
2028 if ( m_nTopRow
!= nOldTopRow
)
2030 SuppressCursor
aHideCursor( *this );
2031 // TODO: call an onStartScroll at our listener (or better an own onStartScroll,
2032 // which hides the cursor and then calls the listener)
2033 // Same for onEndScroll
2035 // scroll the view port, if possible
2036 tools::Long nPixelDelta
= m_nRowHeightPixel
* ( m_nTopRow
- nOldTopRow
);
2038 tools::Rectangle
aDataArea( Point( 0, m_nColHeaderHeightPixel
), m_pDataWindow
->GetOutputSizePixel() );
2040 if ( m_pDataWindow
->GetBackground().IsScrollable()
2041 && std::abs( nPixelDelta
) < aDataArea
.GetHeight()
2044 m_pDataWindow
->Scroll( 0, static_cast<tools::Long
>(-nPixelDelta
), aDataArea
, ScrollFlags::Clip
| ScrollFlags::Update
| ScrollFlags::Children
);
2048 m_pDataWindow
->Invalidate( InvalidateFlags::Update
);
2049 m_pDataWindow
->GetParent()->Invalidate( InvalidateFlags::Transparent
);
2052 // update the position at the vertical scrollbar
2053 if ( m_pVScroll
!= nullptr )
2054 m_pVScroll
->SetThumbPos( m_nTopRow
);
2057 // The scroll bar availability might change when we scrolled.
2058 // For instance, imagine a view with 10 rows, if which 5 fit into the window, numbered 1 to 10.
2060 // - the user scroll to row number 6, so the last 5 rows are visible
2061 // - somebody remove the last 4 rows
2062 // - the user scroll to row number 5 being the top row, so the last two rows are visible
2063 // - somebody remove row number 6
2064 // - the user scroll to row number 1
2065 // => in this case, the need for the scrollbar vanishes immediately.
2066 if ( m_nTopRow
== 0 )
2067 m_rAntiImpl
.PostUserEvent( LINK( this, TableControl_Impl
, OnUpdateScrollbars
) );
2069 return static_cast<TableSize
>( m_nTopRow
- nOldTopRow
);
2073 TableSize
TableControl_Impl::impl_scrollRows( TableSize
const i_rowDelta
)
2075 return impl_ni_ScrollRows( i_rowDelta
);
2079 TableSize
TableControl_Impl::impl_ni_ScrollColumns( TableSize _nColumnDelta
)
2081 // compute new left column
2082 const ColPos nNewLeftColumn
=
2084 ::std::min( static_cast<ColPos
>( m_nLeftColumn
+ _nColumnDelta
), static_cast<ColPos
>( m_nColumnCount
- 1 ) ),
2088 const ColPos nOldLeftColumn
= m_nLeftColumn
;
2089 m_nLeftColumn
= nNewLeftColumn
;
2091 // if updates are enabled currently, scroll the viewport
2092 if ( m_nLeftColumn
!= nOldLeftColumn
)
2094 SuppressCursor
aHideCursor( *this );
2095 // TODO: call an onStartScroll at our listener (or better an own onStartScroll,
2096 // which hides the cursor and then calls the listener)
2097 // Same for onEndScroll
2099 // scroll the view port, if possible
2100 const tools::Rectangle
aDataArea( Point( m_nRowHeaderWidthPixel
, 0 ), m_pDataWindow
->GetOutputSizePixel() );
2102 tools::Long nPixelDelta
=
2103 m_aColumnWidths
[ nOldLeftColumn
].getStart()
2104 - m_aColumnWidths
[ m_nLeftColumn
].getStart();
2106 // update our column positions
2107 // Do this *before* scrolling, as ScrollFlags::Update will trigger a paint, which already needs the correct
2108 // information in m_aColumnWidths
2109 for (auto & columnWidth
: m_aColumnWidths
)
2111 columnWidth
.move(nPixelDelta
);
2114 // scroll the window content (if supported and possible), or invalidate the complete window
2115 if ( m_pDataWindow
->GetBackground().IsScrollable()
2116 && std::abs( nPixelDelta
) < aDataArea
.GetWidth()
2119 m_pDataWindow
->Scroll( nPixelDelta
, 0, aDataArea
, ScrollFlags::Clip
| ScrollFlags::Update
);
2123 m_pDataWindow
->Invalidate( InvalidateFlags::Update
);
2124 m_pDataWindow
->GetParent()->Invalidate( InvalidateFlags::Transparent
);
2127 // update the position at the horizontal scrollbar
2128 if ( m_pHScroll
!= nullptr )
2129 m_pHScroll
->SetThumbPos( m_nLeftColumn
);
2132 // The scroll bar availability might change when we scrolled. This is because we do not hide
2133 // the scrollbar when it is, in theory, unnecessary, but currently at a position > 0. In this case, it will
2134 // be auto-hidden when it's scrolled back to pos 0.
2135 if ( m_nLeftColumn
== 0 )
2136 m_rAntiImpl
.PostUserEvent( LINK( this, TableControl_Impl
, OnUpdateScrollbars
) );
2138 return static_cast<TableSize
>( m_nLeftColumn
- nOldLeftColumn
);
2142 TableSize
TableControl_Impl::impl_scrollColumns( TableSize
const i_columnDelta
)
2144 return impl_ni_ScrollColumns( i_columnDelta
);
2148 SelectionEngine
* TableControl_Impl::getSelEngine()
2150 return m_pSelEngine
.get();
2153 bool TableControl_Impl::isRowSelected( RowPos i_row
) const
2155 return ::std::find( m_aSelectedRows
.begin(), m_aSelectedRows
.end(), i_row
) != m_aSelectedRows
.end();
2159 RowPos
TableControl_Impl::getSelectedRowIndex( size_t const i_selectionIndex
) const
2161 if ( i_selectionIndex
< m_aSelectedRows
.size() )
2162 return m_aSelectedRows
[ i_selectionIndex
];
2167 int TableControl_Impl::getRowSelectedNumber(const ::std::vector
<RowPos
>& selectedRows
, RowPos current
)
2169 std::vector
<RowPos
>::const_iterator it
= ::std::find(selectedRows
.begin(),selectedRows
.end(),current
);
2170 if ( it
!= selectedRows
.end() )
2172 return it
- selectedRows
.begin();
2178 ColPos
TableControl_Impl::impl_getColumnForOrdinate( tools::Long
const i_ordinate
) const
2180 if ( ( m_aColumnWidths
.empty() ) || ( i_ordinate
< 0 ) )
2183 if ( i_ordinate
< m_nRowHeaderWidthPixel
)
2184 return COL_ROW_HEADERS
;
2186 ColumnPositions::const_iterator lowerBound
= ::std::lower_bound(
2187 m_aColumnWidths
.begin(),
2188 m_aColumnWidths
.end(),
2189 MutableColumnMetrics(i_ordinate
+1, i_ordinate
+1),
2190 ColumnInfoPositionLess()
2192 if ( lowerBound
== m_aColumnWidths
.end() )
2194 // point is *behind* the start of the last column ...
2195 if ( i_ordinate
< m_aColumnWidths
.rbegin()->getEnd() )
2196 // ... but still before its end
2197 return m_nColumnCount
- 1;
2200 return lowerBound
- m_aColumnWidths
.begin();
2204 RowPos
TableControl_Impl::impl_getRowForAbscissa( tools::Long
const i_abscissa
) const
2206 if ( i_abscissa
< 0 )
2209 if ( i_abscissa
< m_nColHeaderHeightPixel
)
2210 return ROW_COL_HEADERS
;
2212 tools::Long
const abscissa
= i_abscissa
- m_nColHeaderHeightPixel
;
2213 tools::Long
const row
= m_nTopRow
+ abscissa
/ m_nRowHeightPixel
;
2214 return row
< m_pModel
->getRowCount() ? row
: ROW_INVALID
;
2218 bool TableControl_Impl::markRowAsDeselected( RowPos
const i_rowIndex
)
2220 ::std::vector
< RowPos
>::iterator selPos
= ::std::find( m_aSelectedRows
.begin(), m_aSelectedRows
.end(), i_rowIndex
);
2221 if ( selPos
== m_aSelectedRows
.end() )
2224 m_aSelectedRows
.erase( selPos
);
2229 bool TableControl_Impl::markRowAsSelected( RowPos
const i_rowIndex
)
2231 if ( isRowSelected( i_rowIndex
) )
2234 SelectionMode
const eSelMode
= getSelEngine()->GetSelectionMode();
2237 case SelectionMode::Single
:
2238 if ( !m_aSelectedRows
.empty() )
2240 OSL_ENSURE( m_aSelectedRows
.size() == 1, "TableControl::markRowAsSelected: SingleSelection with more than one selected element?" );
2241 m_aSelectedRows
[0] = i_rowIndex
;
2246 case SelectionMode::Multiple
:
2247 m_aSelectedRows
.push_back( i_rowIndex
);
2251 OSL_ENSURE( false, "TableControl_Impl::markRowAsSelected: unsupported selection mode!" );
2259 bool TableControl_Impl::markAllRowsAsDeselected()
2261 if ( m_aSelectedRows
.empty() )
2264 m_aSelectedRows
.clear();
2269 bool TableControl_Impl::markAllRowsAsSelected()
2271 SelectionMode
const eSelMode
= getSelEngine()->GetSelectionMode();
2272 ENSURE_OR_RETURN_FALSE( eSelMode
== SelectionMode::Multiple
, "TableControl_Impl::markAllRowsAsSelected: unsupported selection mode!" );
2274 if ( m_aSelectedRows
.size() == size_t( m_pModel
->getRowCount() ) )
2276 #if OSL_DEBUG_LEVEL > 0
2277 for ( TableSize row
= 0; row
< m_pModel
->getRowCount(); ++row
)
2279 OSL_ENSURE( isRowSelected( row
), "TableControl_Impl::markAllRowsAsSelected: inconsistency in the selected rows!" );
2282 // already all rows marked as selected
2286 m_aSelectedRows
.clear();
2287 for ( RowPos i
=0; i
< m_pModel
->getRowCount(); ++i
)
2288 m_aSelectedRows
.push_back(i
);
2294 void TableControl_Impl::commitAccessibleEvent( sal_Int16
const i_eventID
)
2296 impl_commitAccessibleEvent( i_eventID
, Any() );
2300 void TableControl_Impl::commitCellEvent( sal_Int16
const i_eventID
, const Any
& i_newValue
, const Any
& i_oldValue
)
2302 if ( impl_isAccessibleAlive() )
2303 m_pAccessibleTable
->commitCellEvent( i_eventID
, i_newValue
, i_oldValue
);
2307 void TableControl_Impl::commitTableEvent( sal_Int16
const i_eventID
, const Any
& i_newValue
, const Any
& i_oldValue
)
2309 if ( impl_isAccessibleAlive() )
2310 m_pAccessibleTable
->commitTableEvent( i_eventID
, i_newValue
, i_oldValue
);
2314 tools::Rectangle
TableControl_Impl::calcHeaderRect(bool bColHeader
)
2316 tools::Rectangle
const aRectTableWithHeaders( impl_getAllVisibleCellsArea() );
2317 Size
const aSizeTableWithHeaders( aRectTableWithHeaders
.GetSize() );
2319 return tools::Rectangle( aRectTableWithHeaders
.TopLeft(), Size( aSizeTableWithHeaders
.Width(), m_nColHeaderHeightPixel
) );
2321 return tools::Rectangle( aRectTableWithHeaders
.TopLeft(), Size( m_nRowHeaderWidthPixel
, aSizeTableWithHeaders
.Height() ) );
2325 tools::Rectangle
TableControl_Impl::calcHeaderCellRect( bool bColHeader
, sal_Int32 nPos
)
2327 tools::Rectangle
const aHeaderRect
= calcHeaderRect( bColHeader
);
2328 TableCellGeometry
const aGeometry(
2330 bColHeader
? nPos
: COL_ROW_HEADERS
,
2331 bColHeader
? ROW_COL_HEADERS
: nPos
2333 return aGeometry
.getRect();
2337 tools::Rectangle
TableControl_Impl::calcTableRect() const
2339 return impl_getAllVisibleDataCellArea();
2343 tools::Rectangle
TableControl_Impl::calcCellRect( sal_Int32 nRow
, sal_Int32 nCol
) const
2345 tools::Rectangle aCellRect
;
2346 impl_getCellRect( nRow
, nCol
, aCellRect
);
2351 IMPL_LINK_NOARG( TableControl_Impl
, OnUpdateScrollbars
, void*, void )
2353 // TODO: can't we simply use lcl_updateScrollbar here, so the scrollbars ranges are updated, instead of
2354 // doing a complete re-layout?
2359 IMPL_LINK( TableControl_Impl
, OnScroll
, ScrollBar
*, _pScrollbar
, void )
2361 DBG_ASSERT( ( _pScrollbar
== m_pVScroll
) || ( _pScrollbar
== m_pHScroll
),
2362 "TableControl_Impl::OnScroll: where did this come from?" );
2364 if ( _pScrollbar
== m_pVScroll
)
2365 impl_ni_ScrollRows( _pScrollbar
->GetDelta() );
2367 impl_ni_ScrollColumns( _pScrollbar
->GetDelta() );
2371 rtl::Reference
<vcl::table::IAccessibleTableControl
> TableControl_Impl::getAccessible( vcl::Window
& i_parentWindow
)
2373 if (m_pAccessibleTable
)
2374 return m_pAccessibleTable
;
2376 DBG_TESTSOLARMUTEX();
2377 if ( m_pAccessibleTable
== nullptr )
2379 Reference
< XAccessible
> const xAccParent
= i_parentWindow
.GetAccessible();
2380 if ( xAccParent
.is() )
2382 m_pAccessibleTable
= m_aFactoryAccess
.getFactory().createAccessibleTableControl(
2383 xAccParent
, m_rAntiImpl
2388 return m_pAccessibleTable
;
2392 void TableControl_Impl::disposeAccessible()
2394 if ( m_pAccessibleTable
)
2395 m_pAccessibleTable
->DisposeAccessImpl();
2396 m_pAccessibleTable
= nullptr;
2400 bool TableControl_Impl::impl_isAccessibleAlive() const
2402 return m_pAccessibleTable
&& m_pAccessibleTable
->isAlive();
2406 void TableControl_Impl::impl_commitAccessibleEvent( sal_Int16
const i_eventID
, Any
const & i_newValue
)
2408 if ( impl_isAccessibleAlive() )
2409 m_pAccessibleTable
->commitEvent( i_eventID
, i_newValue
);
2413 //= TableFunctionSet
2416 TableFunctionSet::TableFunctionSet(TableControl_Impl
* _pTableControl
)
2417 :m_pTableControl( _pTableControl
)
2418 ,m_nCurrentRow( ROW_INVALID
)
2422 TableFunctionSet::~TableFunctionSet()
2426 void TableFunctionSet::BeginDrag()
2430 void TableFunctionSet::CreateAnchor()
2432 m_pTableControl
->setAnchor( m_pTableControl
->getCurRow() );
2436 void TableFunctionSet::DestroyAnchor()
2438 m_pTableControl
->setAnchor( ROW_INVALID
);
2442 void TableFunctionSet::SetCursorAtPoint(const Point
& rPoint
, bool bDontSelectAtCursor
)
2444 // newRow is the row which includes the point, getCurRow() is the last selected row, before the mouse click
2445 RowPos newRow
= m_pTableControl
->getRowAtPoint( rPoint
);
2446 if ( newRow
== ROW_COL_HEADERS
)
2447 newRow
= m_pTableControl
->getTopRow();
2449 ColPos newCol
= m_pTableControl
->getColAtPoint( rPoint
);
2450 if ( newCol
== COL_ROW_HEADERS
)
2451 newCol
= m_pTableControl
->getLeftColumn();
2453 if ( ( newRow
== ROW_INVALID
) || ( newCol
== COL_INVALID
) )
2456 if ( bDontSelectAtCursor
)
2458 if ( m_pTableControl
->getSelectedRowCount() > 1 )
2459 m_pTableControl
->getSelEngine()->AddAlways(true);
2461 else if ( m_pTableControl
->getAnchor() == m_pTableControl
->getCurRow() )
2463 //selected region lies above the last selection
2464 if( m_pTableControl
->getCurRow() >= newRow
)
2466 //put selected rows in vector
2467 while ( m_pTableControl
->getAnchor() >= newRow
)
2469 m_pTableControl
->markRowAsSelected( m_pTableControl
->getAnchor() );
2470 m_pTableControl
->setAnchor( m_pTableControl
->getAnchor() - 1 );
2472 m_pTableControl
->setAnchor( m_pTableControl
->getAnchor() + 1 );
2474 //selected region lies beneath the last selected row
2477 while ( m_pTableControl
->getAnchor() <= newRow
)
2479 m_pTableControl
->markRowAsSelected( m_pTableControl
->getAnchor() );
2480 m_pTableControl
->setAnchor( m_pTableControl
->getAnchor() + 1 );
2482 m_pTableControl
->setAnchor( m_pTableControl
->getAnchor() - 1 );
2484 m_pTableControl
->invalidateSelectedRegion( m_pTableControl
->getCurRow(), newRow
);
2486 //no region selected
2489 if ( !m_pTableControl
->hasRowSelection() )
2490 m_pTableControl
->markRowAsSelected( newRow
);
2493 if ( m_pTableControl
->getSelEngine()->GetSelectionMode() == SelectionMode::Single
)
2496 m_pTableControl
->markRowAsSelected( newRow
);
2500 m_pTableControl
->markRowAsSelected( newRow
);
2503 if ( m_pTableControl
->getSelectedRowCount() > 1 && m_pTableControl
->getSelEngine()->GetSelectionMode() != SelectionMode::Single
)
2504 m_pTableControl
->getSelEngine()->AddAlways(true);
2506 m_pTableControl
->invalidateRow( newRow
);
2508 m_pTableControl
->goTo( newCol
, newRow
);
2511 bool TableFunctionSet::IsSelectionAtPoint( const Point
& rPoint
)
2513 m_pTableControl
->getSelEngine()->AddAlways(false);
2514 if ( !m_pTableControl
->hasRowSelection() )
2518 RowPos curRow
= m_pTableControl
->getRowAtPoint( rPoint
);
2519 m_pTableControl
->setAnchor( ROW_INVALID
);
2520 bool selected
= m_pTableControl
->isRowSelected( curRow
);
2521 m_nCurrentRow
= curRow
;
2526 void TableFunctionSet::DeselectAtPoint( const Point
& )
2528 m_pTableControl
->invalidateRow( m_nCurrentRow
);
2529 m_pTableControl
->markRowAsDeselected( m_nCurrentRow
);
2533 void TableFunctionSet::DeselectAll()
2535 if ( m_pTableControl
->hasRowSelection() )
2537 for ( size_t i
=0; i
<m_pTableControl
->getSelectedRowCount(); ++i
)
2539 RowPos
const rowIndex
= m_pTableControl
->getSelectedRowIndex(i
);
2540 m_pTableControl
->invalidateRow( rowIndex
);
2543 m_pTableControl
->markAllRowsAsDeselected();
2548 } // namespace svt::table
2551 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */