Bump version to 5.0-14
[LibreOffice.git] / svtools / source / table / tablecontrol_impl.cxx
blob4842dc748e3309e106a6320d01459bcf53a95032
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include "table/tablecontrol.hxx"
22 #include "table/defaultinputhandler.hxx"
23 #include <svtools/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/scrbar.hxx>
36 #include <vcl/seleng.hxx>
37 #include <vcl/settings.hxx>
38 #include <rtl/ref.hxx>
39 #include <vcl/image.hxx>
40 #include <tools/diagnose_ex.h>
42 #include <cstdlib>
43 #include <functional>
44 #include <numeric>
46 #define MIN_COLUMN_WIDTH_PIXEL 4
49 namespace svt { namespace table
53 using ::com::sun::star::accessibility::AccessibleTableModelChange;
54 using ::com::sun::star::uno::makeAny;
55 using ::com::sun::star::uno::Any;
56 using ::com::sun::star::accessibility::XAccessible;
57 using ::com::sun::star::uno::Reference;
59 namespace AccessibleEventId = ::com::sun::star::accessibility::AccessibleEventId;
60 namespace AccessibleTableModelChangeType = ::com::sun::star::accessibility::AccessibleTableModelChangeType;
63 //= SuppressCursor
65 class SuppressCursor
67 private:
68 ITableControl& m_rTable;
70 public:
71 SuppressCursor( ITableControl& _rTable )
72 :m_rTable( _rTable )
74 m_rTable.hideCursor();
76 ~SuppressCursor()
78 m_rTable.showCursor();
83 //= EmptyTableModel
85 /** default implementation of an ->ITableModel, used as fallback when no
86 real model is present
88 Instances of this class are static in any way, and provide the least
89 necessary default functionality for a table model.
91 class EmptyTableModel : public ITableModel
93 public:
94 EmptyTableModel()
98 // ITableModel overridables
99 virtual TableSize getColumnCount() const SAL_OVERRIDE
101 return 0;
103 virtual TableSize getRowCount() const SAL_OVERRIDE
105 return 0;
107 virtual bool hasColumnHeaders() const SAL_OVERRIDE
109 return false;
111 virtual bool hasRowHeaders() const SAL_OVERRIDE
113 return false;
115 virtual bool isCellEditable( ColPos col, RowPos row ) const SAL_OVERRIDE
117 (void)col;
118 (void)row;
119 return false;
121 virtual PColumnModel getColumnModel( ColPos column ) SAL_OVERRIDE
123 OSL_FAIL( "EmptyTableModel::getColumnModel: invalid call!" );
124 (void)column;
125 return PColumnModel();
127 virtual PTableRenderer getRenderer() const SAL_OVERRIDE
129 return PTableRenderer();
131 virtual PTableInputHandler getInputHandler() const SAL_OVERRIDE
133 return PTableInputHandler();
135 virtual TableMetrics getRowHeight() const SAL_OVERRIDE
137 return 5 * 100;
139 virtual TableMetrics getColumnHeaderHeight() const SAL_OVERRIDE
141 return 0;
143 virtual TableMetrics getRowHeaderWidth() const SAL_OVERRIDE
145 return 0;
147 virtual ScrollbarVisibility getVerticalScrollbarVisibility() const SAL_OVERRIDE
149 return ScrollbarShowNever;
151 virtual ScrollbarVisibility getHorizontalScrollbarVisibility() const SAL_OVERRIDE
153 return ScrollbarShowNever;
155 virtual void addTableModelListener( const PTableModelListener& i_listener ) SAL_OVERRIDE
157 (void)i_listener;
159 virtual void removeTableModelListener( const PTableModelListener& i_listener ) SAL_OVERRIDE
161 (void)i_listener;
163 virtual ::boost::optional< ::Color > getLineColor() const SAL_OVERRIDE
165 return ::boost::optional< ::Color >();
167 virtual ::boost::optional< ::Color > getHeaderBackgroundColor() const SAL_OVERRIDE
169 return ::boost::optional< ::Color >();
171 virtual ::boost::optional< ::Color > getHeaderTextColor() const SAL_OVERRIDE
173 return ::boost::optional< ::Color >();
175 virtual ::boost::optional< ::Color > getActiveSelectionBackColor() const SAL_OVERRIDE
177 return ::boost::optional< ::Color >();
179 virtual ::boost::optional< ::Color > getInactiveSelectionBackColor() const SAL_OVERRIDE
181 return ::boost::optional< ::Color >();
183 virtual ::boost::optional< ::Color > getActiveSelectionTextColor() const SAL_OVERRIDE
185 return ::boost::optional< ::Color >();
187 virtual ::boost::optional< ::Color > getInactiveSelectionTextColor() const SAL_OVERRIDE
189 return ::boost::optional< ::Color >();
191 virtual ::boost::optional< ::Color > getTextColor() const SAL_OVERRIDE
193 return ::boost::optional< ::Color >();
195 virtual ::boost::optional< ::Color > getTextLineColor() const SAL_OVERRIDE
197 return ::boost::optional< ::Color >();
199 virtual ::boost::optional< ::std::vector< ::Color > > getRowBackgroundColors() const SAL_OVERRIDE
201 return ::boost::optional< ::std::vector< ::Color > >();
203 virtual ::com::sun::star::style::VerticalAlignment getVerticalAlign() const SAL_OVERRIDE
205 return com::sun::star::style::VerticalAlignment(0);
207 virtual ITableDataSort* getSortAdapter() SAL_OVERRIDE
209 return NULL;
211 virtual bool isEnabled() const SAL_OVERRIDE
213 return true;
215 virtual void getCellContent( ColPos const i_col, RowPos const i_row, ::com::sun::star::uno::Any& o_cellContent ) SAL_OVERRIDE
217 (void)i_row;
218 (void)i_col;
219 o_cellContent.clear();
221 virtual void getCellToolTip( ColPos const, RowPos const, ::com::sun::star::uno::Any& ) SAL_OVERRIDE
224 virtual Any getRowHeading( RowPos const i_rowPos ) const SAL_OVERRIDE
226 (void)i_rowPos;
227 return Any();
231 TableControl_Impl::TableControl_Impl( TableControl& _rAntiImpl )
232 :m_rAntiImpl ( _rAntiImpl )
233 ,m_pModel ( new EmptyTableModel )
234 ,m_pInputHandler ( )
235 ,m_nRowHeightPixel ( 15 )
236 ,m_nColHeaderHeightPixel( 0 )
237 ,m_nRowHeaderWidthPixel ( 0 )
238 ,m_nColumnCount ( 0 )
239 ,m_nRowCount ( 0 )
240 ,m_nCurColumn ( COL_INVALID )
241 ,m_nCurRow ( ROW_INVALID )
242 ,m_nLeftColumn ( 0 )
243 ,m_nTopRow ( 0 )
244 ,m_nCursorHidden ( 1 )
245 ,m_pDataWindow ( VclPtr<TableDataWindow>::Create( *this ) )
246 ,m_pVScroll ( NULL )
247 ,m_pHScroll ( NULL )
248 ,m_pScrollCorner ( NULL )
249 ,m_pSelEngine ( )
250 ,m_aSelectedRows ( )
251 ,m_pTableFunctionSet ( new TableFunctionSet( this ) )
252 ,m_nAnchor ( -1 )
253 ,m_bUpdatingColWidths ( false )
254 ,m_pAccessibleTable ( NULL )
256 m_pSelEngine = new SelectionEngine( m_pDataWindow.get(), m_pTableFunctionSet );
257 m_pSelEngine->SetSelectionMode(SINGLE_SELECTION);
258 m_pDataWindow->SetPosPixel( Point( 0, 0 ) );
259 m_pDataWindow->Show();
262 TableControl_Impl::~TableControl_Impl()
264 m_pVScroll.disposeAndClear();
265 m_pHScroll.disposeAndClear();
266 m_pScrollCorner.disposeAndClear();
267 m_pDataWindow.disposeAndClear();
268 DELETEZ( m_pTableFunctionSet );
269 DELETEZ( m_pSelEngine );
272 void TableControl_Impl::setModel( PTableModel _pModel )
274 SuppressCursor aHideCursor( *this );
276 if ( !!m_pModel )
277 m_pModel->removeTableModelListener( shared_from_this() );
279 m_pModel = _pModel;
280 if ( !m_pModel)
281 m_pModel.reset( new EmptyTableModel );
283 m_pModel->addTableModelListener( shared_from_this() );
285 m_nCurRow = ROW_INVALID;
286 m_nCurColumn = COL_INVALID;
288 // recalc some model-dependent cached info
289 impl_ni_updateCachedModelValues();
290 impl_ni_relayout();
292 // completely invalidate
293 m_rAntiImpl.Invalidate();
295 // reset cursor to (0,0)
296 if ( m_nRowCount ) m_nCurRow = 0;
297 if ( m_nColumnCount ) m_nCurColumn = 0;
301 namespace
303 bool lcl_adjustSelectedRows( ::std::vector< RowPos >& io_selectionIndexes, RowPos const i_firstAffectedRowIndex, TableSize const i_offset )
305 bool didChanges = false;
306 for ( ::std::vector< RowPos >::iterator selPos = io_selectionIndexes.begin();
307 selPos != io_selectionIndexes.end();
308 ++selPos
311 if ( *selPos < i_firstAffectedRowIndex )
312 continue;
313 *selPos += i_offset;
314 didChanges = true;
316 return didChanges;
321 void TableControl_Impl::rowsInserted( RowPos i_first, RowPos i_last )
323 OSL_PRECOND( i_last >= i_first, "TableControl_Impl::rowsInserted: invalid row indexes!" );
325 TableSize const insertedRows = i_last - i_first + 1;
327 // adjust selection, if necessary
328 bool const selectionChanged = lcl_adjustSelectedRows( m_aSelectedRows, i_first, insertedRows );
330 // adjust our cached row count
331 m_nRowCount = m_pModel->getRowCount();
333 // if the rows have been inserted before the current row, adjust this
334 if ( i_first <= m_nCurRow )
335 goTo( m_nCurColumn, m_nCurRow + insertedRows );
337 // relayout, since the scrollbar need might have changed
338 impl_ni_relayout();
340 // notify A1YY events
341 if ( impl_isAccessibleAlive() )
343 impl_commitAccessibleEvent( AccessibleEventId::TABLE_MODEL_CHANGED,
344 makeAny( AccessibleTableModelChange( AccessibleTableModelChangeType::INSERT, i_first, i_last, 0, m_pModel->getColumnCount() ) ),
345 Any()
349 // schedule repaint
350 invalidateRowRange( i_first, ROW_INVALID );
352 // call selection handlers, if necessary
353 if ( selectionChanged )
354 m_rAntiImpl.Select();
358 void TableControl_Impl::rowsRemoved( RowPos i_first, RowPos i_last )
360 sal_Int32 firstRemovedRow = i_first;
361 sal_Int32 lastRemovedRow = i_last;
363 // adjust selection, if necessary
364 bool selectionChanged = false;
365 if ( i_first == -1 )
367 selectionChanged = markAllRowsAsDeselected();
369 firstRemovedRow = 0;
370 lastRemovedRow = m_nRowCount - 1;
372 else
374 ENSURE_OR_RETURN_VOID( i_last >= i_first, "TableControl_Impl::rowsRemoved: illegal indexes!" );
376 for ( sal_Int32 row = i_first; row <= i_last; ++row )
378 if ( markRowAsDeselected( row ) )
379 selectionChanged = true;
382 if ( lcl_adjustSelectedRows( m_aSelectedRows, i_last + 1, i_first - i_last - 1 ) )
383 selectionChanged = true;
386 // adjust cached row count
387 m_nRowCount = m_pModel->getRowCount();
389 // adjust the current row, if it is larger than the row count now
390 if ( m_nCurRow >= m_nRowCount )
392 if ( m_nRowCount > 0 )
393 goTo( m_nCurColumn, m_nRowCount - 1 );
394 else
396 m_nCurRow = ROW_INVALID;
397 m_nTopRow = 0;
400 else if ( m_nRowCount == 0 )
402 m_nTopRow = 0;
406 // relayout, since the scrollbar need might have changed
407 impl_ni_relayout();
409 // notify A11Y events
410 if ( impl_isAccessibleAlive() )
412 commitTableEvent(
413 AccessibleEventId::TABLE_MODEL_CHANGED,
414 makeAny( AccessibleTableModelChange(
415 AccessibleTableModelChangeType::DELETE,
416 firstRemovedRow,
417 lastRemovedRow,
419 m_pModel->getColumnCount()
420 ) ),
421 Any()
425 // schedule a repaint
426 invalidateRowRange( firstRemovedRow, ROW_INVALID );
428 // call selection handlers, if necessary
429 if ( selectionChanged )
430 m_rAntiImpl.Select();
434 void TableControl_Impl::columnInserted( ColPos const i_colIndex )
436 m_nColumnCount = m_pModel->getColumnCount();
437 impl_ni_relayout();
439 m_rAntiImpl.Invalidate();
441 OSL_UNUSED( i_colIndex );
445 void TableControl_Impl::columnRemoved( ColPos const i_colIndex )
447 m_nColumnCount = m_pModel->getColumnCount();
449 // adjust the current column, if it is larger than the column count now
450 if ( m_nCurColumn >= m_nColumnCount )
452 if ( m_nColumnCount > 0 )
453 goTo( m_nCurColumn - 1, m_nCurRow );
454 else
455 m_nCurColumn = COL_INVALID;
458 impl_ni_relayout();
460 m_rAntiImpl.Invalidate();
462 OSL_UNUSED( i_colIndex );
466 void TableControl_Impl::allColumnsRemoved()
468 m_nColumnCount = m_pModel->getColumnCount();
469 impl_ni_relayout();
471 m_rAntiImpl.Invalidate();
475 void TableControl_Impl::cellsUpdated( ColPos const i_firstCol, ColPos i_lastCol, RowPos const i_firstRow, RowPos const i_lastRow )
477 invalidateRowRange( i_firstRow, i_lastRow );
479 OSL_UNUSED( i_firstCol );
480 OSL_UNUSED( i_lastCol );
484 void TableControl_Impl::tableMetricsChanged()
486 impl_ni_updateCachedTableMetrics();
487 impl_ni_relayout();
488 m_rAntiImpl.Invalidate();
492 void TableControl_Impl::impl_invalidateColumn( ColPos const i_column )
494 Rectangle const aAllCellsArea( impl_getAllVisibleCellsArea() );
496 const TableColumnGeometry aColumn( *this, aAllCellsArea, i_column );
497 if ( aColumn.isValid() )
498 m_rAntiImpl.Invalidate( aColumn.getRect() );
502 void TableControl_Impl::columnChanged( ColPos const i_column, ColumnAttributeGroup const i_attributeGroup )
504 ColumnAttributeGroup nGroup( i_attributeGroup );
505 if ( nGroup & ColumnAttributeGroup::APPEARANCE )
507 impl_invalidateColumn( i_column );
508 nGroup &= ~ColumnAttributeGroup::APPEARANCE;
511 if ( nGroup & ColumnAttributeGroup::WIDTH )
513 if ( !m_bUpdatingColWidths )
515 impl_ni_relayout( i_column );
516 invalidate( TableAreaAll );
519 nGroup &= ~ColumnAttributeGroup::WIDTH;
522 OSL_ENSURE( ( nGroup == ColumnAttributeGroup::NONE ) || ( i_attributeGroup == ColumnAttributeGroup::ALL ),
523 "TableControl_Impl::columnChanged: don't know how to handle this change!" );
527 Rectangle TableControl_Impl::impl_getAllVisibleCellsArea() const
529 Rectangle aArea( Point( 0, 0 ), Size( 0, 0 ) );
531 // determine the right-most border of the last column which is
532 // at least partially visible
533 aArea.Right() = m_nRowHeaderWidthPixel;
534 if ( !m_aColumnWidths.empty() )
536 // the number of pixels which are scrolled out of the left hand
537 // side of the window
538 const long nScrolledOutLeft = m_nLeftColumn == 0 ? 0 : m_aColumnWidths[ m_nLeftColumn - 1 ].getEnd();
540 ColumnPositions::const_reverse_iterator loop = m_aColumnWidths.rbegin();
543 aArea.Right() = loop->getEnd() - nScrolledOutLeft + m_nRowHeaderWidthPixel;
544 ++loop;
546 while ( ( loop != m_aColumnWidths.rend() )
547 && ( loop->getEnd() - nScrolledOutLeft >= aArea.Right() )
550 // so far, aArea.Right() denotes the first pixel *after* the cell area
551 --aArea.Right();
553 // determine the last row which is at least partially visible
554 aArea.Bottom() =
555 m_nColHeaderHeightPixel
556 + impl_getVisibleRows( true ) * m_nRowHeightPixel
557 - 1;
559 return aArea;
563 Rectangle TableControl_Impl::impl_getAllVisibleDataCellArea() const
565 Rectangle aArea( impl_getAllVisibleCellsArea() );
566 aArea.Left() = m_nRowHeaderWidthPixel;
567 aArea.Top() = m_nColHeaderHeightPixel;
568 return aArea;
572 void TableControl_Impl::impl_ni_updateCachedTableMetrics()
574 m_nRowHeightPixel = m_rAntiImpl.LogicToPixel( Size( 0, m_pModel->getRowHeight() ), MAP_APPFONT ).Height();
576 m_nColHeaderHeightPixel = 0;
577 if ( m_pModel->hasColumnHeaders() )
578 m_nColHeaderHeightPixel = m_rAntiImpl.LogicToPixel( Size( 0, m_pModel->getColumnHeaderHeight() ), MAP_APPFONT ).Height();
580 m_nRowHeaderWidthPixel = 0;
581 if ( m_pModel->hasRowHeaders() )
582 m_nRowHeaderWidthPixel = m_rAntiImpl.LogicToPixel( Size( m_pModel->getRowHeaderWidth(), 0 ), MAP_APPFONT).Width();
586 void TableControl_Impl::impl_ni_updateCachedModelValues()
588 m_pInputHandler = m_pModel->getInputHandler();
589 if ( !m_pInputHandler )
590 m_pInputHandler.reset( new DefaultInputHandler );
592 m_nColumnCount = m_pModel->getColumnCount();
593 if ( m_nLeftColumn >= m_nColumnCount )
594 m_nLeftColumn = ( m_nColumnCount > 0 ) ? m_nColumnCount - 1 : 0;
596 m_nRowCount = m_pModel->getRowCount();
597 if ( m_nTopRow >= m_nRowCount )
598 m_nTopRow = ( m_nRowCount > 0 ) ? m_nRowCount - 1 : 0;
600 impl_ni_updateCachedTableMetrics();
604 namespace
607 /// determines whether a scrollbar is needed for the given values
608 bool lcl_determineScrollbarNeed( long const i_position, ScrollbarVisibility const i_visibility,
609 long const i_availableSpace, long const i_neededSpace )
611 if ( i_visibility == ScrollbarShowNever )
612 return false;
613 if ( i_visibility == ScrollbarShowAlways )
614 return true;
615 if ( i_position > 0 )
616 return true;
617 if ( i_availableSpace >= i_neededSpace )
618 return false;
619 return true;
623 void lcl_setButtonRepeat( vcl::Window& _rWindow, sal_uLong _nDelay )
625 AllSettings aSettings = _rWindow.GetSettings();
626 MouseSettings aMouseSettings = aSettings.GetMouseSettings();
628 aMouseSettings.SetButtonRepeat( _nDelay );
629 aSettings.SetMouseSettings( aMouseSettings );
631 _rWindow.SetSettings( aSettings, true );
635 bool lcl_updateScrollbar( vcl::Window& _rParent, VclPtr<ScrollBar>& _rpBar,
636 bool const i_needBar, long _nVisibleUnits,
637 long _nPosition, long _nLineSize, long _nRange,
638 bool _bHorizontal, const Link<>& _rScrollHandler )
640 // do we currently have the scrollbar?
641 bool bHaveBar = _rpBar != nullptr;
643 // do we need to correct the scrollbar visibility?
644 if ( bHaveBar && !i_needBar )
646 if ( _rpBar->IsTracking() )
647 _rpBar->EndTracking();
648 _rpBar.disposeAndClear();
650 else if ( !bHaveBar && i_needBar )
652 _rpBar = VclPtr<ScrollBar>::Create(
654 &_rParent,
655 WB_DRAG | ( _bHorizontal ? WB_HSCROLL : WB_VSCROLL )
657 _rpBar->SetScrollHdl( _rScrollHandler );
658 // get some speed into the scrolling ....
659 lcl_setButtonRepeat( *_rpBar, 0 );
662 if ( _rpBar )
664 _rpBar->SetRange( Range( 0, _nRange ) );
665 _rpBar->SetVisibleSize( _nVisibleUnits );
666 _rpBar->SetPageSize( _nVisibleUnits );
667 _rpBar->SetLineSize( _nLineSize );
668 _rpBar->SetThumbPos( _nPosition );
669 _rpBar->Show();
672 return ( bHaveBar != i_needBar );
676 /** returns the number of rows fitting into the given range,
677 for the given row height. Partially fitting rows are counted, too, if the
678 respective parameter says so.
680 TableSize lcl_getRowsFittingInto( long _nOverallHeight, long _nRowHeightPixel, bool _bAcceptPartialRow = false )
682 return _bAcceptPartialRow
683 ? ( _nOverallHeight + ( _nRowHeightPixel - 1 ) ) / _nRowHeightPixel
684 : _nOverallHeight / _nRowHeightPixel;
688 /** returns the number of columns fitting into the given area,
689 with the first visible column as given. Partially fitting columns are counted, too,
690 if the respective parameter says so.
692 TableSize lcl_getColumnsVisibleWithin( const Rectangle& _rArea, ColPos _nFirstVisibleColumn,
693 const TableControl_Impl& _rControl, bool _bAcceptPartialRow )
695 TableSize visibleColumns = 0;
696 TableColumnGeometry aColumn( _rControl, _rArea, _nFirstVisibleColumn );
697 while ( aColumn.isValid() )
699 if ( !_bAcceptPartialRow )
700 if ( aColumn.getRect().Right() > _rArea.Right() )
701 // this column is only partially visible, and this is not allowed
702 break;
704 aColumn.moveRight();
705 ++visibleColumns;
707 return visibleColumns;
713 long TableControl_Impl::impl_ni_calculateColumnWidths( ColPos const i_assumeInflexibleColumnsUpToIncluding,
714 bool const i_assumeVerticalScrollbar, ::std::vector< long >& o_newColWidthsPixel ) const
716 // the available horizontal space
717 long gridWidthPixel = m_rAntiImpl.GetOutputSizePixel().Width();
718 ENSURE_OR_RETURN( !!m_pModel, "TableControl_Impl::impl_ni_calculateColumnWidths: not allowed without a model!", gridWidthPixel );
719 if ( m_pModel->hasRowHeaders() && ( gridWidthPixel != 0 ) )
721 gridWidthPixel -= m_nRowHeaderWidthPixel;
724 if ( i_assumeVerticalScrollbar && ( m_pModel->getVerticalScrollbarVisibility() != ScrollbarShowNever ) )
726 long nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
727 gridWidthPixel -= nScrollbarMetrics;
730 // no need to do anything without columns
731 TableSize const colCount = m_pModel->getColumnCount();
732 if ( colCount == 0 )
733 return gridWidthPixel;
735 // collect some meta data for our columns:
736 // - their current (pixel) metrics
737 long accumulatedCurrentWidth = 0;
738 ::std::vector< long > currentColWidths;
739 currentColWidths.reserve( colCount );
740 typedef ::std::vector< ::std::pair< long, long > > ColumnLimits;
741 ColumnLimits effectiveColumnLimits;
742 effectiveColumnLimits.reserve( colCount );
743 long accumulatedMinWidth = 0;
744 long accumulatedMaxWidth = 0;
745 // - their relative flexibility
746 ::std::vector< ::sal_Int32 > columnFlexibilities;
747 columnFlexibilities.reserve( colCount );
748 long flexibilityDenominator = 0;
749 size_t flexibleColumnCount = 0;
750 for ( ColPos col = 0; col < colCount; ++col )
752 PColumnModel const pColumn = m_pModel->getColumnModel( col );
753 ENSURE_OR_THROW( !!pColumn, "invalid column returned by the model!" );
755 // current width
756 long const currentWidth = appFontWidthToPixel( pColumn->getWidth() );
757 currentColWidths.push_back( currentWidth );
759 // accumulated width
760 accumulatedCurrentWidth += currentWidth;
762 // flexibility
763 ::sal_Int32 flexibility = pColumn->getFlexibility();
764 OSL_ENSURE( flexibility >= 0, "TableControl_Impl::impl_ni_calculateColumnWidths: a column's flexibility should be non-negative." );
765 if ( ( flexibility < 0 ) // normalization
766 || ( !pColumn->isResizable() ) // column not resizable => no auto-resize
767 || ( col <= i_assumeInflexibleColumnsUpToIncluding ) // column shall be treated as inflexible => respec this
769 flexibility = 0;
771 // min/max width
772 long effectiveMin = currentWidth, effectiveMax = currentWidth;
773 // if the column is not flexible, it will not be asked for min/max, but we assume the current width as limit then
774 if ( flexibility > 0 )
776 long const minWidth = appFontWidthToPixel( pColumn->getMinWidth() );
777 if ( minWidth > 0 )
778 effectiveMin = minWidth;
779 else
780 effectiveMin = MIN_COLUMN_WIDTH_PIXEL;
782 long const maxWidth = appFontWidthToPixel( pColumn->getMaxWidth() );
783 OSL_ENSURE( minWidth <= maxWidth, "TableControl_Impl::impl_ni_calculateColumnWidths: pretty undecided 'bout its width limits, this column!" );
784 if ( ( maxWidth > 0 ) && ( maxWidth >= minWidth ) )
785 effectiveMax = maxWidth;
786 else
787 effectiveMax = gridWidthPixel; // TODO: any better guess here?
789 if ( effectiveMin == effectiveMax )
790 // if the min and the max are identical, this implies no flexibility at all
791 flexibility = 0;
794 columnFlexibilities.push_back( flexibility );
795 flexibilityDenominator += flexibility;
796 if ( flexibility > 0 )
797 ++flexibleColumnCount;
799 effectiveColumnLimits.push_back( ::std::pair< long, long >( effectiveMin, effectiveMax ) );
800 accumulatedMinWidth += effectiveMin;
801 accumulatedMaxWidth += effectiveMax;
804 o_newColWidthsPixel = currentColWidths;
805 if ( flexibilityDenominator == 0 )
807 // no column is flexible => don't adjust anything
809 else if ( gridWidthPixel > accumulatedCurrentWidth )
810 { // we have space to give away ...
811 long distributePixel = gridWidthPixel - accumulatedCurrentWidth;
812 if ( gridWidthPixel > accumulatedMaxWidth )
814 // ... but the column's maximal widths are still less than we have
815 // => set them all to max
816 for ( size_t i = 0; i < size_t( colCount ); ++i )
818 o_newColWidthsPixel[i] = effectiveColumnLimits[i].second;
821 else
823 bool startOver = false;
826 startOver = false;
827 // distribute the remaining space amongst all columns with a positive flexibility
828 for ( size_t i=0; i<o_newColWidthsPixel.size() && !startOver; ++i )
830 long const columnFlexibility = columnFlexibilities[i];
831 if ( columnFlexibility == 0 )
832 continue;
834 long newColWidth = currentColWidths[i] + columnFlexibility * distributePixel / flexibilityDenominator;
836 if ( newColWidth > effectiveColumnLimits[i].second )
837 { // that was too much, we hit the col's maximum
838 // set the new width to exactly this maximum
839 newColWidth = effectiveColumnLimits[i].second;
840 // adjust the flexibility denominator ...
841 flexibilityDenominator -= columnFlexibility;
842 columnFlexibilities[i] = 0;
843 --flexibleColumnCount;
844 // ... and the remaining width ...
845 long const difference = newColWidth - currentColWidths[i];
846 distributePixel -= difference;
847 // ... this way, we ensure that the width not taken up by this column is consumed by the other
848 // flexible ones (if there are some)
850 // and start over with the first column, since there might be earlier columns which need
851 // to be recalculated now
852 startOver = true;
855 o_newColWidthsPixel[i] = newColWidth;
858 while ( startOver );
860 // are there pixels left (might be caused by rounding errors)?
861 distributePixel = gridWidthPixel - ::std::accumulate( o_newColWidthsPixel.begin(), o_newColWidthsPixel.end(), 0 );
862 while ( ( distributePixel > 0 ) && ( flexibleColumnCount > 0 ) )
864 // yes => ignore relative flexibilities, and subsequently distribute single pixels to all flexible
865 // columns which did not yet reach their maximum.
866 for ( size_t i=0; ( i < o_newColWidthsPixel.size() ) && ( distributePixel > 0 ); ++i )
868 if ( columnFlexibilities[i] == 0 )
869 continue;
871 OSL_ENSURE( o_newColWidthsPixel[i] <= effectiveColumnLimits[i].second,
872 "TableControl_Impl::impl_ni_calculateColumnWidths: inconsitency!" );
873 if ( o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first )
875 columnFlexibilities[i] = 0;
876 --flexibleColumnCount;
877 continue;
880 ++o_newColWidthsPixel[i];
881 --distributePixel;
886 else if ( gridWidthPixel < accumulatedCurrentWidth )
887 { // we need to take away some space from the columns which allow it ...
888 long takeAwayPixel = accumulatedCurrentWidth - gridWidthPixel;
889 if ( gridWidthPixel < accumulatedMinWidth )
891 // ... but the column's minimal widths are still more than we have
892 // => set them all to min
893 for ( size_t i = 0; i < size_t( colCount ); ++i )
895 o_newColWidthsPixel[i] = effectiveColumnLimits[i].first;
898 else
900 bool startOver = false;
903 startOver = false;
904 // take away the space we need from the columns with a positive flexibility
905 for ( size_t i=0; i<o_newColWidthsPixel.size() && !startOver; ++i )
907 long const columnFlexibility = columnFlexibilities[i];
908 if ( columnFlexibility == 0 )
909 continue;
911 long newColWidth = currentColWidths[i] - columnFlexibility * takeAwayPixel / flexibilityDenominator;
913 if ( newColWidth < effectiveColumnLimits[i].first )
914 { // that was too much, we hit the col's minimum
915 // set the new width to exactly this minimum
916 newColWidth = effectiveColumnLimits[i].first;
917 // adjust the flexibility denominator ...
918 flexibilityDenominator -= columnFlexibility;
919 columnFlexibilities[i] = 0;
920 --flexibleColumnCount;
921 // ... and the remaining width ...
922 long const difference = currentColWidths[i] - newColWidth;
923 takeAwayPixel -= difference;
925 // and start over with the first column, since there might be earlier columns which need
926 // to be recalculated now
927 startOver = true;
930 o_newColWidthsPixel[i] = newColWidth;
933 while ( startOver );
935 // are there pixels left (might be caused by rounding errors)?
936 takeAwayPixel = ::std::accumulate( o_newColWidthsPixel.begin(), o_newColWidthsPixel.end(), 0 ) - gridWidthPixel;
937 while ( ( takeAwayPixel > 0 ) && ( flexibleColumnCount > 0 ) )
939 // yes => ignore relative flexibilities, and subsequently take away pixels from all flexible
940 // columns which did not yet reach their minimum.
941 for ( size_t i=0; ( i < o_newColWidthsPixel.size() ) && ( takeAwayPixel > 0 ); ++i )
943 if ( columnFlexibilities[i] == 0 )
944 continue;
946 OSL_ENSURE( o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first,
947 "TableControl_Impl::impl_ni_calculateColumnWidths: inconsitency!" );
948 if ( o_newColWidthsPixel[i] <= effectiveColumnLimits[i].first )
950 columnFlexibilities[i] = 0;
951 --flexibleColumnCount;
952 continue;
955 --o_newColWidthsPixel[i];
956 --takeAwayPixel;
962 return gridWidthPixel;
966 void TableControl_Impl::impl_ni_relayout( ColPos const i_assumeInflexibleColumnsUpToIncluding )
968 ENSURE_OR_RETURN_VOID( !m_bUpdatingColWidths, "TableControl_Impl::impl_ni_relayout: recursive call detected!" );
970 m_aColumnWidths.resize( 0 );
971 if ( !m_pModel )
972 return;
974 ::comphelper::FlagRestorationGuard const aWidthUpdateFlag( m_bUpdatingColWidths, true );
975 SuppressCursor aHideCursor( *this );
977 // layouting steps:
979 // 1. adjust column widths, leaving space for a vertical scrollbar
980 // 2. determine need for a vertical scrollbar
981 // - V-YES: all fine, result from 1. is still valid
982 // - V-NO: result from 1. is still under consideration
984 // 3. determine need for a horizontal scrollbar
985 // - H-NO: all fine, result from 2. is still valid
986 // - H-YES: reconsider need for a vertical scrollbar, if result of 2. was V-NO
987 // - V-YES: all fine, result from 1. is still valid
988 // - V-NO: redistribute the remaining space (if any) amongst all columns which allow it
990 ::std::vector< long > newWidthsPixel;
991 long gridWidthPixel = impl_ni_calculateColumnWidths( i_assumeInflexibleColumnsUpToIncluding, true, newWidthsPixel );
993 // the width/height of a scrollbar, needed several times below
994 long const nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
996 // determine the playground for the data cells (excluding headers)
997 // TODO: what if the control is smaller than needed for the headers/scrollbars?
998 Rectangle aDataCellPlayground( Point( 0, 0 ), m_rAntiImpl.GetOutputSizePixel() );
999 aDataCellPlayground.Left() = m_nRowHeaderWidthPixel;
1000 aDataCellPlayground.Top() = m_nColHeaderHeightPixel;
1002 OSL_ENSURE( ( m_nRowCount == m_pModel->getRowCount() ) && ( m_nColumnCount == m_pModel->getColumnCount() ),
1003 "TableControl_Impl::impl_ni_relayout: how is this expected to work with invalid data?" );
1004 long const nAllColumnsWidth = ::std::accumulate( newWidthsPixel.begin(), newWidthsPixel.end(), 0 );
1006 ScrollbarVisibility const eVertScrollbar = m_pModel->getVerticalScrollbarVisibility();
1007 ScrollbarVisibility const eHorzScrollbar = m_pModel->getHorizontalScrollbarVisibility();
1009 // do we need a vertical scrollbar?
1010 bool bNeedVerticalScrollbar = lcl_determineScrollbarNeed(
1011 m_nTopRow, eVertScrollbar, aDataCellPlayground.GetHeight(), m_nRowHeightPixel * m_nRowCount );
1012 bool bFirstRoundVScrollNeed = false;
1013 if ( bNeedVerticalScrollbar )
1015 aDataCellPlayground.Right() -= nScrollbarMetrics;
1016 bFirstRoundVScrollNeed = true;
1019 // do we need a horizontal scrollbar?
1020 bool const bNeedHorizontalScrollbar = lcl_determineScrollbarNeed(
1021 m_nLeftColumn, eHorzScrollbar, aDataCellPlayground.GetWidth(), nAllColumnsWidth );
1022 if ( bNeedHorizontalScrollbar )
1024 aDataCellPlayground.Bottom() -= nScrollbarMetrics;
1026 // now that we just found that we need a horizontal scrollbar,
1027 // the need for a vertical one may have changed, since the horizontal
1028 // SB might just occupy enough space so that not all rows do fit
1029 // anymore
1030 if ( !bFirstRoundVScrollNeed )
1032 bNeedVerticalScrollbar = lcl_determineScrollbarNeed(
1033 m_nTopRow, eVertScrollbar, aDataCellPlayground.GetHeight(), m_nRowHeightPixel * m_nRowCount );
1034 if ( bNeedVerticalScrollbar )
1036 aDataCellPlayground.Right() -= nScrollbarMetrics;
1041 // the initial call to impl_ni_calculateColumnWidths assumed that we need a vertical scrollbar. If, by now,
1042 // we know that this is not the case, re-calculate the column widths.
1043 if ( !bNeedVerticalScrollbar )
1044 gridWidthPixel = impl_ni_calculateColumnWidths( i_assumeInflexibleColumnsUpToIncluding, false, newWidthsPixel );
1046 // update the column objects with the new widths we finally calculated
1047 TableSize const colCount = m_pModel->getColumnCount();
1048 m_aColumnWidths.reserve( colCount );
1049 long accumulatedWidthPixel = m_nRowHeaderWidthPixel;
1050 bool anyColumnWidthChanged = false;
1051 for ( ColPos col = 0; col < colCount; ++col )
1053 const long columnStart = accumulatedWidthPixel;
1054 const long columnEnd = columnStart + newWidthsPixel[col];
1055 m_aColumnWidths.push_back( MutableColumnMetrics( columnStart, columnEnd ) );
1056 accumulatedWidthPixel = columnEnd;
1058 // and don't forget to forward this to the column models
1059 PColumnModel const pColumn = m_pModel->getColumnModel( col );
1060 ENSURE_OR_THROW( !!pColumn, "invalid column returned by the model!" );
1062 long const oldColumnWidthAppFont = pColumn->getWidth();
1063 long const newColumnWidthAppFont = pixelWidthToAppFont( newWidthsPixel[col] );
1064 pColumn->setWidth( newColumnWidthAppFont );
1066 anyColumnWidthChanged |= ( oldColumnWidthAppFont != newColumnWidthAppFont );
1069 // if the column widths changed, ensure everything is repainted
1070 if ( anyColumnWidthChanged )
1071 invalidate( TableAreaAll );
1073 // if the column resizing happened to leave some space at the right, but there are columns
1074 // scrolled out to the left, scroll them in
1075 while ( ( m_nLeftColumn > 0 )
1076 && ( accumulatedWidthPixel - m_aColumnWidths[ m_nLeftColumn - 1 ].getStart() <= gridWidthPixel )
1079 --m_nLeftColumn;
1082 // now adjust the column metrics, since they currently ignore the horizontal scroll position
1083 if ( m_nLeftColumn > 0 )
1085 const long offsetPixel = m_aColumnWidths[ 0 ].getStart() - m_aColumnWidths[ m_nLeftColumn ].getStart();
1086 for ( ColumnPositions::iterator colPos = m_aColumnWidths.begin();
1087 colPos != m_aColumnWidths.end();
1088 ++colPos
1091 colPos->move( offsetPixel );
1095 // show or hide the scrollbars as needed, and position the data window
1096 impl_ni_positionChildWindows( aDataCellPlayground, bNeedVerticalScrollbar, bNeedHorizontalScrollbar );
1100 void TableControl_Impl::impl_ni_positionChildWindows( Rectangle const & i_dataCellPlayground,
1101 bool const i_verticalScrollbar, bool const i_horizontalScrollbar )
1103 long const nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
1105 // create or destroy the vertical scrollbar, as needed
1106 lcl_updateScrollbar(
1107 m_rAntiImpl,
1108 m_pVScroll,
1109 i_verticalScrollbar,
1110 lcl_getRowsFittingInto( i_dataCellPlayground.GetHeight(), m_nRowHeightPixel ),
1111 // visible units
1112 m_nTopRow, // current position
1113 1, // line size
1114 m_nRowCount, // range
1115 false, // vertical
1116 LINK( this, TableControl_Impl, OnScroll ) // scroll handler
1119 // position it
1120 if ( m_pVScroll )
1122 Rectangle aScrollbarArea(
1123 Point( i_dataCellPlayground.Right() + 1, 0 ),
1124 Size( nScrollbarMetrics, i_dataCellPlayground.Bottom() + 1 )
1126 m_pVScroll->SetPosSizePixel(
1127 aScrollbarArea.TopLeft(), aScrollbarArea.GetSize() );
1130 // create or destroy the horizontal scrollbar, as needed
1131 lcl_updateScrollbar(
1132 m_rAntiImpl,
1133 m_pHScroll,
1134 i_horizontalScrollbar,
1135 lcl_getColumnsVisibleWithin( i_dataCellPlayground, m_nLeftColumn, *this, false ),
1136 // visible units
1137 m_nLeftColumn, // current position
1138 1, // line size
1139 m_nColumnCount, // range
1140 true, // horizontal
1141 LINK( this, TableControl_Impl, OnScroll ) // scroll handler
1144 // position it
1145 if ( m_pHScroll )
1147 TableSize const nVisibleUnits = lcl_getColumnsVisibleWithin( i_dataCellPlayground, m_nLeftColumn, *this, false );
1148 TableMetrics const nRange = m_nColumnCount;
1149 if( m_nLeftColumn + nVisibleUnits == nRange - 1 )
1151 if ( m_aColumnWidths[ nRange - 1 ].getStart() - m_aColumnWidths[ m_nLeftColumn ].getEnd() + m_aColumnWidths[ nRange-1 ].getWidth() > i_dataCellPlayground.GetWidth() )
1153 m_pHScroll->SetVisibleSize( nVisibleUnits -1 );
1154 m_pHScroll->SetPageSize( nVisibleUnits - 1 );
1157 Rectangle aScrollbarArea(
1158 Point( 0, i_dataCellPlayground.Bottom() + 1 ),
1159 Size( i_dataCellPlayground.Right() + 1, nScrollbarMetrics )
1161 m_pHScroll->SetPosSizePixel(
1162 aScrollbarArea.TopLeft(), aScrollbarArea.GetSize() );
1165 // the corner window connecting the two scrollbars in the lower right corner
1166 bool bHaveScrollCorner = nullptr != m_pScrollCorner;
1167 bool bNeedScrollCorner = ( nullptr != m_pHScroll ) && ( nullptr != m_pVScroll );
1168 if ( bHaveScrollCorner && !bNeedScrollCorner )
1170 m_pScrollCorner.disposeAndClear();
1172 else if ( !bHaveScrollCorner && bNeedScrollCorner )
1174 m_pScrollCorner = VclPtr<ScrollBarBox>::Create( &m_rAntiImpl );
1175 m_pScrollCorner->SetSizePixel( Size( nScrollbarMetrics, nScrollbarMetrics ) );
1176 m_pScrollCorner->SetPosPixel( Point( i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1 ) );
1177 m_pScrollCorner->Show();
1179 else if(bHaveScrollCorner && bNeedScrollCorner)
1181 m_pScrollCorner->SetPosPixel( Point( i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1 ) );
1182 m_pScrollCorner->Show();
1185 // resize the data window
1186 m_pDataWindow->SetSizePixel( Size(
1187 i_dataCellPlayground.GetWidth() + m_nRowHeaderWidthPixel,
1188 i_dataCellPlayground.GetHeight() + m_nColHeaderHeightPixel
1189 ) );
1193 void TableControl_Impl::onResize()
1195 impl_ni_relayout();
1196 checkCursorPosition();
1200 void TableControl_Impl::doPaintContent(vcl::RenderContext& rRenderContext, const Rectangle& _rUpdateRect)
1202 if (!getModel())
1203 return;
1204 PTableRenderer pRenderer = getModel()->getRenderer();
1205 DBG_ASSERT(!!pRenderer, "TableDataWindow::doPaintContent: invalid renderer!");
1206 if (!pRenderer)
1207 return;
1209 // our current style settings, to be passed to the renderer
1210 const StyleSettings& rStyle = rRenderContext.GetSettings().GetStyleSettings();
1211 m_nRowCount = m_pModel->getRowCount();
1212 // the area occupied by all (at least partially) visible cells, including
1213 // headers
1214 Rectangle const aAllCellsWithHeaders( impl_getAllVisibleCellsArea() );
1216 // draw the header column area
1217 if (m_pModel->hasColumnHeaders())
1219 TableRowGeometry const aHeaderRow(*this, Rectangle(Point(0, 0), aAllCellsWithHeaders.BottomRight()), ROW_COL_HEADERS);
1220 Rectangle const aColRect(aHeaderRow.getRect());
1221 pRenderer->PaintHeaderArea(rRenderContext, aColRect, true, false, rStyle);
1222 // Note that strictly, aHeaderRow.getRect() also contains the intersection between column
1223 // and row header area. However, below we go to paint this intersection, again,
1224 // so this hopefully doesn't hurt if we already paint it here.
1226 for (TableCellGeometry aCell(aHeaderRow, m_nLeftColumn); aCell.isValid(); aCell.moveRight())
1228 if (_rUpdateRect.GetIntersection(aCell.getRect()).IsEmpty())
1229 continue;
1231 bool isActiveColumn = (aCell.getColumn() == getCurrentColumn());
1232 bool isSelectedColumn = false;
1233 pRenderer->PaintColumnHeader(aCell.getColumn(), isActiveColumn, isSelectedColumn, rRenderContext, aCell.getRect(), rStyle);
1236 // the area occupied by the row header, if any
1237 Rectangle aRowHeaderArea;
1238 if (m_pModel->hasRowHeaders())
1240 aRowHeaderArea = aAllCellsWithHeaders;
1241 aRowHeaderArea.Right() = m_nRowHeaderWidthPixel - 1;
1243 TableSize const nVisibleRows = impl_getVisibleRows(true);
1244 TableSize nActualRows = nVisibleRows;
1245 if (m_nTopRow + nActualRows > m_nRowCount)
1246 nActualRows = m_nRowCount - m_nTopRow;
1247 aRowHeaderArea.Bottom() = m_nColHeaderHeightPixel + m_nRowHeightPixel * nActualRows - 1;
1249 pRenderer->PaintHeaderArea(rRenderContext, aRowHeaderArea, false, true, rStyle);
1250 // Note that strictly, aRowHeaderArea also contains the intersection between column
1251 // and row header area. However, below we go to paint this intersection, again,
1252 // so this hopefully doesn't hurt if we already paint it here.
1254 if (m_pModel->hasColumnHeaders())
1256 TableCellGeometry const aIntersection(*this, Rectangle(Point(0, 0), aAllCellsWithHeaders.BottomRight()),
1257 COL_ROW_HEADERS, ROW_COL_HEADERS);
1258 Rectangle const aInters(aIntersection.getRect());
1259 pRenderer->PaintHeaderArea(rRenderContext, aInters, true, true, rStyle);
1263 // draw the table content row by row
1264 TableSize colCount = getModel()->getColumnCount();
1266 // paint all rows
1267 Rectangle const aAllDataCellsArea(impl_getAllVisibleDataCellArea());
1268 for (TableRowGeometry aRowIterator(*this, aAllCellsWithHeaders, getTopRow()); aRowIterator.isValid(); aRowIterator.moveDown())
1270 if (_rUpdateRect.GetIntersection(aRowIterator.getRect() ).IsEmpty())
1271 continue;
1273 bool const isControlFocused = m_rAntiImpl.HasControlFocus();
1274 bool const isSelectedRow = isRowSelected(aRowIterator.getRow());
1276 Rectangle const aRect = aRowIterator.getRect().GetIntersection(aAllDataCellsArea);
1278 // give the redenderer a chance to prepare the row
1279 pRenderer->PrepareRow(aRowIterator.getRow(), isControlFocused, isSelectedRow, rRenderContext, aRect, rStyle);
1281 // paint the row header
1282 if (m_pModel->hasRowHeaders())
1284 const Rectangle aCurrentRowHeader(aRowHeaderArea.GetIntersection(aRowIterator.getRect()));
1285 pRenderer->PaintRowHeader(isControlFocused, isSelectedRow, rRenderContext, aCurrentRowHeader, rStyle);
1288 if (!colCount)
1289 continue;
1291 // paint all cells in this row
1292 for (TableCellGeometry aCell(aRowIterator, m_nLeftColumn); aCell.isValid(); aCell.moveRight())
1294 bool isSelectedColumn = false;
1295 pRenderer->PaintCell(aCell.getColumn(), isSelectedRow || isSelectedColumn, isControlFocused,
1296 rRenderContext, aCell.getRect(), rStyle);
1301 void TableControl_Impl::hideCursor()
1303 if ( ++m_nCursorHidden == 1 )
1304 impl_ni_doSwitchCursor( false );
1308 void TableControl_Impl::showCursor()
1310 DBG_ASSERT( m_nCursorHidden > 0, "TableControl_Impl::showCursor: cursor not hidden!" );
1311 if ( --m_nCursorHidden == 0 )
1312 impl_ni_doSwitchCursor( true );
1316 bool TableControl_Impl::dispatchAction( TableControlAction _eAction )
1318 bool bSuccess = false;
1319 bool selectionChanged = false;
1321 switch ( _eAction )
1323 case cursorDown:
1324 if ( m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION )
1326 //if other rows already selected, deselect them
1327 if(!m_aSelectedRows.empty())
1329 invalidateSelectedRows();
1330 m_aSelectedRows.clear();
1332 if ( m_nCurRow < m_nRowCount-1 )
1334 ++m_nCurRow;
1335 m_aSelectedRows.push_back(m_nCurRow);
1337 else
1338 m_aSelectedRows.push_back(m_nCurRow);
1339 invalidateRow( m_nCurRow );
1340 ensureVisible(m_nCurColumn,m_nCurRow,false);
1341 selectionChanged = true;
1342 bSuccess = true;
1344 else
1346 if ( m_nCurRow < m_nRowCount - 1 )
1347 bSuccess = goTo( m_nCurColumn, m_nCurRow + 1 );
1349 break;
1351 case cursorUp:
1352 if(m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION)
1354 if(!m_aSelectedRows.empty())
1356 invalidateSelectedRows();
1357 m_aSelectedRows.clear();
1359 if(m_nCurRow>0)
1361 --m_nCurRow;
1362 m_aSelectedRows.push_back(m_nCurRow);
1363 invalidateRow( m_nCurRow );
1365 else
1367 m_aSelectedRows.push_back(m_nCurRow);
1368 invalidateRow( m_nCurRow );
1370 ensureVisible(m_nCurColumn,m_nCurRow,false);
1371 selectionChanged = true;
1372 bSuccess = true;
1374 else
1376 if ( m_nCurRow > 0 )
1377 bSuccess = goTo( m_nCurColumn, m_nCurRow - 1 );
1379 break;
1380 case cursorLeft:
1381 if ( m_nCurColumn > 0 )
1382 bSuccess = goTo( m_nCurColumn - 1, m_nCurRow );
1383 else
1384 if ( ( m_nCurColumn == 0) && ( m_nCurRow > 0 ) )
1385 bSuccess = goTo( m_nColumnCount - 1, m_nCurRow - 1 );
1386 break;
1388 case cursorRight:
1389 if ( m_nCurColumn < m_nColumnCount - 1 )
1390 bSuccess = goTo( m_nCurColumn + 1, m_nCurRow );
1391 else
1392 if ( ( m_nCurColumn == m_nColumnCount - 1 ) && ( m_nCurRow < m_nRowCount - 1 ) )
1393 bSuccess = goTo( 0, m_nCurRow + 1 );
1394 break;
1396 case cursorToLineStart:
1397 bSuccess = goTo( 0, m_nCurRow );
1398 break;
1400 case cursorToLineEnd:
1401 bSuccess = goTo( m_nColumnCount - 1, m_nCurRow );
1402 break;
1404 case cursorToFirstLine:
1405 bSuccess = goTo( m_nCurColumn, 0 );
1406 break;
1408 case cursorToLastLine:
1409 bSuccess = goTo( m_nCurColumn, m_nRowCount - 1 );
1410 break;
1412 case cursorPageUp:
1414 RowPos nNewRow = ::std::max( (RowPos)0, m_nCurRow - impl_getVisibleRows( false ) );
1415 bSuccess = goTo( m_nCurColumn, nNewRow );
1417 break;
1419 case cursorPageDown:
1421 RowPos nNewRow = ::std::min( m_nRowCount - 1, m_nCurRow + impl_getVisibleRows( false ) );
1422 bSuccess = goTo( m_nCurColumn, nNewRow );
1424 break;
1426 case cursorTopLeft:
1427 bSuccess = goTo( 0, 0 );
1428 break;
1430 case cursorBottomRight:
1431 bSuccess = goTo( m_nColumnCount - 1, m_nRowCount - 1 );
1432 break;
1434 case cursorSelectRow:
1436 if(m_pSelEngine->GetSelectionMode() == NO_SELECTION)
1437 return bSuccess = false;
1438 //pos is the position of the current row in the vector of selected rows, if current row is selected
1439 int pos = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
1440 //if current row is selected, it should be deselected, when ALT+SPACE are pressed
1441 if(pos>-1)
1443 m_aSelectedRows.erase(m_aSelectedRows.begin()+pos);
1444 if(m_aSelectedRows.empty() && m_nAnchor != -1)
1445 m_nAnchor = -1;
1447 //else select the row->put it in the vector
1448 else
1449 m_aSelectedRows.push_back(m_nCurRow);
1450 invalidateRow( m_nCurRow );
1451 selectionChanged = true;
1452 bSuccess = true;
1454 break;
1455 case cursorSelectRowUp:
1457 if(m_pSelEngine->GetSelectionMode() == NO_SELECTION)
1458 return bSuccess = false;
1459 else if(m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION)
1461 //if there are other selected rows, deselect them
1462 return false;
1464 else
1466 //there are other selected rows
1467 if(!m_aSelectedRows.empty())
1469 //the anchor wasn't set -> a region is not selected, that's why clear all selection
1470 //and select the current row
1471 if(m_nAnchor==-1)
1473 invalidateSelectedRows();
1474 m_aSelectedRows.clear();
1475 m_aSelectedRows.push_back(m_nCurRow);
1476 invalidateRow( m_nCurRow );
1478 else
1480 //a region is already selected, prevRow is last selected row and the row above - nextRow - should be selected
1481 int prevRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
1482 int nextRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow-1);
1483 if(prevRow>-1)
1485 //if m_nCurRow isn't the upper one, can move up, otherwise not
1486 if(m_nCurRow>0)
1487 m_nCurRow--;
1488 else
1489 return bSuccess = true;
1490 //if nextRow already selected, deselect it, otherwise select it
1491 if(nextRow>-1 && m_aSelectedRows[nextRow] == m_nCurRow)
1493 m_aSelectedRows.erase(m_aSelectedRows.begin()+prevRow);
1494 invalidateRow( m_nCurRow + 1 );
1496 else
1498 m_aSelectedRows.push_back(m_nCurRow);
1499 invalidateRow( m_nCurRow );
1502 else
1504 if(m_nCurRow>0)
1506 m_aSelectedRows.push_back(m_nCurRow);
1507 m_nCurRow--;
1508 m_aSelectedRows.push_back(m_nCurRow);
1509 invalidateSelectedRegion( m_nCurRow+1, m_nCurRow );
1514 else
1516 //if nothing is selected and the current row isn't the upper one
1517 //select the current and one row above
1518 //otherwise select only the upper row
1519 if(m_nCurRow>0)
1521 m_aSelectedRows.push_back(m_nCurRow);
1522 m_nCurRow--;
1523 m_aSelectedRows.push_back(m_nCurRow);
1524 invalidateSelectedRegion( m_nCurRow+1, m_nCurRow );
1526 else
1528 m_aSelectedRows.push_back(m_nCurRow);
1529 invalidateRow( m_nCurRow );
1532 m_pSelEngine->SetAnchor(true);
1533 m_nAnchor = m_nCurRow;
1534 ensureVisible(m_nCurColumn, m_nCurRow, false);
1535 selectionChanged = true;
1536 bSuccess = true;
1539 break;
1540 case cursorSelectRowDown:
1542 if(m_pSelEngine->GetSelectionMode() == NO_SELECTION)
1543 bSuccess = false;
1544 else if(m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION)
1546 bSuccess = false;
1548 else
1550 if(!m_aSelectedRows.empty())
1552 //the anchor wasn't set -> a region is not selected, that's why clear all selection
1553 //and select the current row
1554 if(m_nAnchor==-1)
1556 invalidateSelectedRows();
1557 m_aSelectedRows.clear();
1558 m_aSelectedRows.push_back(m_nCurRow);
1559 invalidateRow( m_nCurRow );
1561 else
1563 //a region is already selected, prevRow is last selected row and the row beneath - nextRow - should be selected
1564 int prevRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
1565 int nextRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow+1);
1566 if(prevRow>-1)
1568 //if m_nCurRow isn't the last one, can move down, otherwise not
1569 if(m_nCurRow<m_nRowCount-1)
1570 m_nCurRow++;
1571 else
1572 return bSuccess = true;
1573 //if next row already selected, deselect it, otherwise select it
1574 if(nextRow>-1 && m_aSelectedRows[nextRow] == m_nCurRow)
1576 m_aSelectedRows.erase(m_aSelectedRows.begin()+prevRow);
1577 invalidateRow( m_nCurRow - 1 );
1579 else
1581 m_aSelectedRows.push_back(m_nCurRow);
1582 invalidateRow( m_nCurRow );
1585 else
1587 if(m_nCurRow<m_nRowCount-1)
1589 m_aSelectedRows.push_back(m_nCurRow);
1590 m_nCurRow++;
1591 m_aSelectedRows.push_back(m_nCurRow);
1592 invalidateSelectedRegion( m_nCurRow-1, m_nCurRow );
1597 else
1599 //there wasn't any selection, select current and row beneath, otherwise only row beneath
1600 if(m_nCurRow<m_nRowCount-1)
1602 m_aSelectedRows.push_back(m_nCurRow);
1603 m_nCurRow++;
1604 m_aSelectedRows.push_back(m_nCurRow);
1605 invalidateSelectedRegion( m_nCurRow-1, m_nCurRow );
1607 else
1609 m_aSelectedRows.push_back(m_nCurRow);
1610 invalidateRow( m_nCurRow );
1613 m_pSelEngine->SetAnchor(true);
1614 m_nAnchor = m_nCurRow;
1615 ensureVisible(m_nCurColumn, m_nCurRow, false);
1616 selectionChanged = true;
1617 bSuccess = true;
1620 break;
1622 case cursorSelectRowAreaTop:
1624 if(m_pSelEngine->GetSelectionMode() == NO_SELECTION)
1625 bSuccess = false;
1626 else if(m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION)
1627 bSuccess = false;
1628 else
1630 //select the region between the current and the upper row
1631 RowPos iter = m_nCurRow;
1632 invalidateSelectedRegion( m_nCurRow, 0 );
1633 //put the rows in vector
1634 while(iter>=0)
1636 if ( !isRowSelected( iter ) )
1637 m_aSelectedRows.push_back(iter);
1638 --iter;
1640 m_nCurRow = 0;
1641 m_nAnchor = m_nCurRow;
1642 m_pSelEngine->SetAnchor(true);
1643 ensureVisible(m_nCurColumn, 0, false);
1644 selectionChanged = true;
1645 bSuccess = true;
1648 break;
1650 case cursorSelectRowAreaBottom:
1652 if(m_pSelEngine->GetSelectionMode() == NO_SELECTION)
1653 return bSuccess = false;
1654 else if(m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION)
1655 return bSuccess = false;
1656 //select the region between the current and the last row
1657 RowPos iter = m_nCurRow;
1658 invalidateSelectedRegion( m_nCurRow, m_nRowCount-1 );
1659 //put the rows in the vector
1660 while(iter<=m_nRowCount)
1662 if ( !isRowSelected( iter ) )
1663 m_aSelectedRows.push_back(iter);
1664 ++iter;
1666 m_nCurRow = m_nRowCount-1;
1667 m_nAnchor = m_nCurRow;
1668 m_pSelEngine->SetAnchor(true);
1669 ensureVisible(m_nCurColumn, m_nRowCount-1, false);
1670 selectionChanged = true;
1671 bSuccess = true;
1673 break;
1674 default:
1675 OSL_FAIL( "TableControl_Impl::dispatchAction: unsupported action!" );
1676 break;
1679 if ( bSuccess && selectionChanged )
1681 m_rAntiImpl.Select();
1684 return bSuccess;
1688 void TableControl_Impl::impl_ni_doSwitchCursor( bool _bShow )
1690 PTableRenderer pRenderer = !!m_pModel ? m_pModel->getRenderer() : PTableRenderer();
1691 if ( !!pRenderer )
1693 Rectangle aCellRect;
1694 impl_getCellRect( m_nCurColumn, m_nCurRow, aCellRect );
1695 if ( _bShow )
1696 pRenderer->ShowCellCursor( *m_pDataWindow, aCellRect );
1697 else
1698 pRenderer->HideCellCursor( *m_pDataWindow, aCellRect );
1703 void TableControl_Impl::impl_getCellRect( ColPos _nColumn, RowPos _nRow, Rectangle& _rCellRect ) const
1705 if ( !m_pModel
1706 || ( COL_INVALID == _nColumn )
1707 || ( ROW_INVALID == _nRow )
1710 _rCellRect.SetEmpty();
1711 return;
1714 TableCellGeometry aCell( *this, impl_getAllVisibleCellsArea(), _nColumn, _nRow );
1715 _rCellRect = aCell.getRect();
1719 RowPos TableControl_Impl::getRowAtPoint( const Point& rPoint ) const
1721 return impl_getRowForAbscissa( rPoint.Y() );
1725 ColPos TableControl_Impl::getColAtPoint( const Point& rPoint ) const
1727 return impl_getColumnForOrdinate( rPoint.X() );
1731 TableCell TableControl_Impl::hitTest( Point const & i_point ) const
1733 TableCell aCell( getColAtPoint( i_point ), getRowAtPoint( i_point ) );
1734 if ( aCell.nColumn > COL_ROW_HEADERS )
1736 PColumnModel const pColumn = m_pModel->getColumnModel( aCell.nColumn );
1737 MutableColumnMetrics const & rColInfo( m_aColumnWidths[ aCell.nColumn ] );
1738 if ( ( rColInfo.getEnd() - 3 <= i_point.X() )
1739 && ( rColInfo.getEnd() >= i_point.X() )
1740 && pColumn->isResizable()
1743 aCell.eArea = ColumnDivider;
1746 return aCell;
1750 ColumnMetrics TableControl_Impl::getColumnMetrics( ColPos const i_column ) const
1752 ENSURE_OR_RETURN( ( i_column >= 0 ) && ( i_column < m_pModel->getColumnCount() ),
1753 "TableControl_Impl::getColumnMetrics: illegal column index!", ColumnMetrics() );
1754 return (ColumnMetrics const &)m_aColumnWidths[ i_column ];
1758 PTableModel TableControl_Impl::getModel() const
1760 return m_pModel;
1764 RowPos TableControl_Impl::getCurrentColumn() const
1766 return m_nCurColumn;
1770 RowPos TableControl_Impl::getCurrentRow() const
1772 return m_nCurRow;
1776 ::Size TableControl_Impl::getTableSizePixel() const
1778 return m_pDataWindow->GetOutputSizePixel();
1782 void TableControl_Impl::setPointer( Pointer const & i_pointer )
1784 m_pDataWindow->SetPointer( i_pointer );
1788 void TableControl_Impl::captureMouse()
1790 m_pDataWindow->CaptureMouse();
1794 void TableControl_Impl::releaseMouse()
1796 m_pDataWindow->ReleaseMouse();
1800 void TableControl_Impl::invalidate( TableArea const i_what )
1802 switch ( i_what )
1804 case TableAreaColumnHeaders:
1805 m_pDataWindow->Invalidate( calcHeaderRect( true ) );
1806 break;
1808 case TableAreaRowHeaders:
1809 m_pDataWindow->Invalidate( calcHeaderRect( false ) );
1810 break;
1812 case TableAreaDataArea:
1813 m_pDataWindow->Invalidate( impl_getAllVisibleDataCellArea() );
1814 break;
1816 case TableAreaAll:
1817 m_pDataWindow->Invalidate();
1818 m_pDataWindow->GetParent()->Invalidate( INVALIDATE_TRANSPARENT );
1819 break;
1824 long TableControl_Impl::pixelWidthToAppFont( long const i_pixels ) const
1826 return m_pDataWindow->PixelToLogic( Size( i_pixels, 0 ), MAP_APPFONT ).Width();
1830 long TableControl_Impl::appFontWidthToPixel( long const i_appFontUnits ) const
1832 return m_pDataWindow->LogicToPixel( Size( i_appFontUnits, 0 ), MAP_APPFONT ).Width();
1836 void TableControl_Impl::hideTracking()
1838 m_pDataWindow->HideTracking();
1842 void TableControl_Impl::showTracking( Rectangle const & i_location, sal_uInt16 const i_flags )
1844 m_pDataWindow->ShowTracking( i_location, i_flags );
1848 bool TableControl_Impl::activateCell( ColPos const i_col, RowPos const i_row )
1850 return goTo( i_col, i_row );
1854 void TableControl_Impl::invalidateSelectedRegion( RowPos _nPrevRow, RowPos _nCurRow )
1856 // get the visible area of the table control and set the Left and right border of the region to be repainted
1857 Rectangle const aAllCells( impl_getAllVisibleCellsArea() );
1859 Rectangle aInvalidateRect;
1860 aInvalidateRect.Left() = aAllCells.Left();
1861 aInvalidateRect.Right() = aAllCells.Right();
1862 // if only one row is selected
1863 if ( _nPrevRow == _nCurRow )
1865 Rectangle aCellRect;
1866 impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
1867 aInvalidateRect.Top() = aCellRect.Top();
1868 aInvalidateRect.Bottom() = aCellRect.Bottom();
1870 //if the region is above the current row
1871 else if(_nPrevRow < _nCurRow )
1873 Rectangle aCellRect;
1874 impl_getCellRect( m_nCurColumn, _nPrevRow, aCellRect );
1875 aInvalidateRect.Top() = aCellRect.Top();
1876 impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
1877 aInvalidateRect.Bottom() = aCellRect.Bottom();
1879 //if the region is beneath the current row
1880 else
1882 Rectangle aCellRect;
1883 impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
1884 aInvalidateRect.Top() = aCellRect.Top();
1885 impl_getCellRect( m_nCurColumn, _nPrevRow, aCellRect );
1886 aInvalidateRect.Bottom() = aCellRect.Bottom();
1889 invalidateRect(aInvalidateRect);
1892 void TableControl_Impl::invalidateRect(const Rectangle &rInvalidateRect)
1894 m_pDataWindow->Invalidate( rInvalidateRect,
1895 m_pDataWindow->GetControlBackground().GetTransparency() ? INVALIDATE_TRANSPARENT : 0 );
1899 void TableControl_Impl::invalidateSelectedRows()
1901 for ( ::std::vector< RowPos >::iterator selRow = m_aSelectedRows.begin();
1902 selRow != m_aSelectedRows.end();
1903 ++selRow
1906 invalidateRow( *selRow );
1911 void TableControl_Impl::invalidateRowRange( RowPos const i_firstRow, RowPos const i_lastRow )
1913 RowPos const firstRow = i_firstRow < m_nTopRow ? m_nTopRow : i_firstRow;
1914 RowPos const lastVisibleRow = m_nTopRow + impl_getVisibleRows( true ) - 1;
1915 RowPos const lastRow = ( ( i_lastRow == ROW_INVALID ) || ( i_lastRow > lastVisibleRow ) ) ? lastVisibleRow : i_lastRow;
1917 Rectangle aInvalidateRect;
1919 Rectangle const aVisibleCellsArea( impl_getAllVisibleCellsArea() );
1920 TableRowGeometry aRow( *this, aVisibleCellsArea, firstRow, true );
1921 while ( aRow.isValid() && ( aRow.getRow() <= lastRow ) )
1923 aInvalidateRect.Union( aRow.getRect() );
1924 aRow.moveDown();
1927 if ( i_lastRow == ROW_INVALID )
1928 aInvalidateRect.Bottom() = m_pDataWindow->GetOutputSizePixel().Height();
1930 invalidateRect(aInvalidateRect);
1934 void TableControl_Impl::checkCursorPosition()
1937 TableSize nVisibleRows = impl_getVisibleRows(true);
1938 TableSize nVisibleCols = impl_getVisibleColumns(true);
1939 if ( ( m_nTopRow + nVisibleRows > m_nRowCount )
1940 && ( m_nRowCount >= nVisibleRows )
1943 --m_nTopRow;
1945 else
1947 m_nTopRow = 0;
1950 if ( ( m_nLeftColumn + nVisibleCols > m_nColumnCount )
1951 && ( m_nColumnCount >= nVisibleCols )
1954 --m_nLeftColumn;
1956 else
1958 m_nLeftColumn = 0;
1961 m_pDataWindow->Invalidate();
1965 TableSize TableControl_Impl::impl_getVisibleRows( bool _bAcceptPartialRow ) const
1967 DBG_ASSERT( m_pDataWindow, "TableControl_Impl::impl_getVisibleRows: no data window!" );
1969 return lcl_getRowsFittingInto(
1970 m_pDataWindow->GetOutputSizePixel().Height() - m_nColHeaderHeightPixel,
1971 m_nRowHeightPixel,
1972 _bAcceptPartialRow
1977 TableSize TableControl_Impl::impl_getVisibleColumns( bool _bAcceptPartialCol ) const
1979 DBG_ASSERT( m_pDataWindow, "TableControl_Impl::impl_getVisibleColumns: no data window!" );
1981 return lcl_getColumnsVisibleWithin(
1982 Rectangle( Point( 0, 0 ), m_pDataWindow->GetOutputSizePixel() ),
1983 m_nLeftColumn,
1984 *this,
1985 _bAcceptPartialCol
1990 bool TableControl_Impl::goTo( ColPos _nColumn, RowPos _nRow )
1992 // TODO: give veto listeners a chance
1994 if ( ( _nColumn < 0 ) || ( _nColumn >= m_nColumnCount )
1995 || ( _nRow < 0 ) || ( _nRow >= m_nRowCount )
1998 OSL_ENSURE( false, "TableControl_Impl::goTo: invalid row or column index!" );
1999 return false;
2002 SuppressCursor aHideCursor( *this );
2003 m_nCurColumn = _nColumn;
2004 m_nCurRow = _nRow;
2006 // ensure that the new cell is visible
2007 ensureVisible( m_nCurColumn, m_nCurRow, false );
2008 return true;
2012 void TableControl_Impl::ensureVisible( ColPos _nColumn, RowPos _nRow, bool _bAcceptPartialVisibility )
2014 DBG_ASSERT( ( _nColumn >= 0 ) && ( _nColumn < m_nColumnCount )
2015 && ( _nRow >= 0 ) && ( _nRow < m_nRowCount ),
2016 "TableControl_Impl::ensureVisible: invalid coordinates!" );
2018 SuppressCursor aHideCursor( *this );
2020 if ( _nColumn < m_nLeftColumn )
2021 impl_scrollColumns( _nColumn - m_nLeftColumn );
2022 else
2024 TableSize nVisibleColumns = impl_getVisibleColumns( _bAcceptPartialVisibility );
2025 if ( _nColumn > m_nLeftColumn + nVisibleColumns - 1 )
2027 impl_scrollColumns( _nColumn - ( m_nLeftColumn + nVisibleColumns - 1 ) );
2028 // TODO: since not all columns have the same width, this might in theory result
2029 // in the column still not being visible.
2033 if ( _nRow < m_nTopRow )
2034 impl_scrollRows( _nRow - m_nTopRow );
2035 else
2037 TableSize nVisibleRows = impl_getVisibleRows( _bAcceptPartialVisibility );
2038 if ( _nRow > m_nTopRow + nVisibleRows - 1 )
2039 impl_scrollRows( _nRow - ( m_nTopRow + nVisibleRows - 1 ) );
2044 OUString TableControl_Impl::getCellContentAsString( RowPos const i_row, ColPos const i_col )
2046 Any aCellValue;
2047 m_pModel->getCellContent( i_col, i_row, aCellValue );
2049 OUString sCellStringContent;
2050 m_pModel->getRenderer()->GetFormattedCellString( aCellValue, i_col, i_row, sCellStringContent );
2052 return sCellStringContent;
2056 TableSize TableControl_Impl::impl_ni_ScrollRows( TableSize _nRowDelta )
2058 // compute new top row
2059 RowPos nNewTopRow =
2060 ::std::max(
2061 ::std::min( (RowPos)( m_nTopRow + _nRowDelta ), (RowPos)( m_nRowCount - 1 ) ),
2062 (RowPos)0
2065 RowPos nOldTopRow = m_nTopRow;
2066 m_nTopRow = nNewTopRow;
2068 // if updates are enabled currently, scroll the viewport
2069 if ( m_nTopRow != nOldTopRow )
2071 SuppressCursor aHideCursor( *this );
2072 // TODO: call a onStartScroll at our listener (or better an own onStartScroll,
2073 // which hides the cursor and then calls the listener)
2074 // Same for onEndScroll
2076 // scroll the view port, if possible
2077 long nPixelDelta = m_nRowHeightPixel * ( m_nTopRow - nOldTopRow );
2079 Rectangle aDataArea( Point( 0, m_nColHeaderHeightPixel ), m_pDataWindow->GetOutputSizePixel() );
2081 if ( m_pDataWindow->GetBackground().IsScrollable()
2082 && std::abs( nPixelDelta ) < aDataArea.GetHeight()
2085 m_pDataWindow->Scroll( 0, (long)-nPixelDelta, aDataArea, SCROLL_CLIP | SCROLL_UPDATE | SCROLL_CHILDREN);
2087 else
2089 m_pDataWindow->Invalidate( INVALIDATE_UPDATE );
2090 m_pDataWindow->GetParent()->Invalidate( INVALIDATE_TRANSPARENT );
2093 // update the position at the vertical scrollbar
2094 if ( m_pVScroll != nullptr )
2095 m_pVScroll->SetThumbPos( m_nTopRow );
2098 // The scroll bar availaility might change when we scrolled.
2099 // For instance, imagine a view with 10 rows, if which 5 fit into the window, numbered 1 to 10.
2100 // Now let
2101 // - the user scroll to row number 6, so the last 5 rows are visible
2102 // - somebody remove the last 4 rows
2103 // - the user scroll to row number 5 being the top row, so the last two rows are visible
2104 // - somebody remove row number 6
2105 // - the user scroll to row number 1
2106 // => in this case, the need for the scrollbar vanishes immediately.
2107 if ( m_nTopRow == 0 )
2108 m_rAntiImpl.PostUserEvent( LINK( this, TableControl_Impl, OnUpdateScrollbars ) );
2110 return (TableSize)( m_nTopRow - nOldTopRow );
2114 TableSize TableControl_Impl::impl_scrollRows( TableSize const i_rowDelta )
2116 return impl_ni_ScrollRows( i_rowDelta );
2120 TableSize TableControl_Impl::impl_ni_ScrollColumns( TableSize _nColumnDelta )
2122 // compute new left column
2123 const ColPos nNewLeftColumn =
2124 ::std::max(
2125 ::std::min( (ColPos)( m_nLeftColumn + _nColumnDelta ), (ColPos)( m_nColumnCount - 1 ) ),
2126 (ColPos)0
2129 const ColPos nOldLeftColumn = m_nLeftColumn;
2130 m_nLeftColumn = nNewLeftColumn;
2132 // if updates are enabled currently, scroll the viewport
2133 if ( m_nLeftColumn != nOldLeftColumn )
2135 SuppressCursor aHideCursor( *this );
2136 // TODO: call a onStartScroll at our listener (or better an own onStartScroll,
2137 // which hides the cursor and then calls the listener)
2138 // Same for onEndScroll
2140 // scroll the view port, if possible
2141 const Rectangle aDataArea( Point( m_nRowHeaderWidthPixel, 0 ), m_pDataWindow->GetOutputSizePixel() );
2143 long nPixelDelta =
2144 m_aColumnWidths[ nOldLeftColumn ].getStart()
2145 - m_aColumnWidths[ m_nLeftColumn ].getStart();
2147 // update our column positions
2148 // Do this *before* scrolling, as SCROLL_UPDATE will trigger a paint, which already needs the correct
2149 // information in m_aColumnWidths
2150 for ( ColumnPositions::iterator colPos = m_aColumnWidths.begin();
2151 colPos != m_aColumnWidths.end();
2152 ++colPos
2155 colPos->move( nPixelDelta );
2158 // scroll the window content (if supported and possible), or invalidate the complete window
2159 if ( m_pDataWindow->GetBackground().IsScrollable()
2160 && std::abs( nPixelDelta ) < aDataArea.GetWidth()
2163 m_pDataWindow->Scroll( nPixelDelta, 0, aDataArea, SCROLL_CLIP | SCROLL_UPDATE );
2165 else
2167 m_pDataWindow->Invalidate( INVALIDATE_UPDATE );
2168 m_pDataWindow->GetParent()->Invalidate( INVALIDATE_TRANSPARENT );
2171 // update the position at the horizontal scrollbar
2172 if ( m_pHScroll != nullptr )
2173 m_pHScroll->SetThumbPos( m_nLeftColumn );
2176 // The scroll bar availaility might change when we scrolled. This is because we do not hide
2177 // the scrollbar when it is, in theory, unnecessary, but currently at a position > 0. In this case, it will
2178 // be auto-hidden when it's scrolled back to pos 0.
2179 if ( m_nLeftColumn == 0 )
2180 m_rAntiImpl.PostUserEvent( LINK( this, TableControl_Impl, OnUpdateScrollbars ) );
2182 return (TableSize)( m_nLeftColumn - nOldLeftColumn );
2186 TableSize TableControl_Impl::impl_scrollColumns( TableSize const i_columnDelta )
2188 return impl_ni_ScrollColumns( i_columnDelta );
2192 SelectionEngine* TableControl_Impl::getSelEngine()
2194 return m_pSelEngine;
2197 bool TableControl_Impl::isRowSelected( RowPos i_row ) const
2199 return ::std::find( m_aSelectedRows.begin(), m_aSelectedRows.end(), i_row ) != m_aSelectedRows.end();
2203 RowPos TableControl_Impl::getSelectedRowIndex( size_t const i_selectionIndex ) const
2205 if ( i_selectionIndex < m_aSelectedRows.size() )
2206 return m_aSelectedRows[ i_selectionIndex ];
2207 return ROW_INVALID;
2211 int TableControl_Impl::getRowSelectedNumber(const ::std::vector<RowPos>& selectedRows, RowPos current)
2213 std::vector<RowPos>::const_iterator it = ::std::find(selectedRows.begin(),selectedRows.end(),current);
2214 if ( it != selectedRows.end() )
2216 return it - selectedRows.begin();
2218 return -1;
2222 ColPos TableControl_Impl::impl_getColumnForOrdinate( long const i_ordinate ) const
2224 if ( ( m_aColumnWidths.empty() ) || ( i_ordinate < 0 ) )
2225 return COL_INVALID;
2227 if ( i_ordinate < m_nRowHeaderWidthPixel )
2228 return COL_ROW_HEADERS;
2230 ColumnPositions::const_iterator lowerBound = ::std::lower_bound(
2231 m_aColumnWidths.begin(),
2232 m_aColumnWidths.end(),
2233 MutableColumnMetrics(i_ordinate+1, i_ordinate+1),
2234 ColumnInfoPositionLess()
2236 if ( lowerBound == m_aColumnWidths.end() )
2238 // point is *behind* the start of the last column ...
2239 if ( i_ordinate < m_aColumnWidths.rbegin()->getEnd() )
2240 // ... but still before its end
2241 return m_nColumnCount - 1;
2242 return COL_INVALID;
2244 return lowerBound - m_aColumnWidths.begin();
2248 RowPos TableControl_Impl::impl_getRowForAbscissa( long const i_abscissa ) const
2250 if ( i_abscissa < 0 )
2251 return ROW_INVALID;
2253 if ( i_abscissa < m_nColHeaderHeightPixel )
2254 return ROW_COL_HEADERS;
2256 long const abscissa = i_abscissa - m_nColHeaderHeightPixel;
2257 long const row = m_nTopRow + abscissa / m_nRowHeightPixel;
2258 return row < m_pModel->getRowCount() ? row : ROW_INVALID;
2262 bool TableControl_Impl::markRowAsDeselected( RowPos const i_rowIndex )
2264 ::std::vector< RowPos >::iterator selPos = ::std::find( m_aSelectedRows.begin(), m_aSelectedRows.end(), i_rowIndex );
2265 if ( selPos == m_aSelectedRows.end() )
2266 return false;
2268 m_aSelectedRows.erase( selPos );
2269 return true;
2273 bool TableControl_Impl::markRowAsSelected( RowPos const i_rowIndex )
2275 if ( isRowSelected( i_rowIndex ) )
2276 return false;
2278 SelectionMode const eSelMode = getSelEngine()->GetSelectionMode();
2279 switch ( eSelMode )
2281 case SINGLE_SELECTION:
2282 if ( !m_aSelectedRows.empty() )
2284 OSL_ENSURE( m_aSelectedRows.size() == 1, "TableControl::markRowAsSelected: SingleSelection with more than one selected element?" );
2285 m_aSelectedRows[0] = i_rowIndex;
2286 break;
2288 // fall through
2290 case MULTIPLE_SELECTION:
2291 m_aSelectedRows.push_back( i_rowIndex );
2292 break;
2294 default:
2295 OSL_ENSURE( false, "TableControl_Impl::markRowAsSelected: unsupported selection mode!" );
2296 return false;
2299 return true;
2303 bool TableControl_Impl::markAllRowsAsDeselected()
2305 if ( m_aSelectedRows.empty() )
2306 return false;
2308 m_aSelectedRows.clear();
2309 return true;
2313 bool TableControl_Impl::markAllRowsAsSelected()
2315 SelectionMode const eSelMode = getSelEngine()->GetSelectionMode();
2316 ENSURE_OR_RETURN_FALSE( eSelMode == MULTIPLE_SELECTION, "TableControl_Impl::markAllRowsAsSelected: unsupported selection mode!" );
2318 if ( m_aSelectedRows.size() == size_t( m_pModel->getRowCount() ) )
2320 #if OSL_DEBUG_LEVEL > 0
2321 for ( TableSize row = 0; row < m_pModel->getRowCount(); ++row )
2323 OSL_ENSURE( isRowSelected( row ), "TableControl_Impl::markAllRowsAsSelected: inconsistency in the selected rows!" );
2325 #endif
2326 // already all rows marked as selected
2327 return false;
2330 m_aSelectedRows.clear();
2331 for ( RowPos i=0; i < m_pModel->getRowCount(); ++i )
2332 m_aSelectedRows.push_back(i);
2334 return true;
2338 void TableControl_Impl::commitAccessibleEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue )
2340 impl_commitAccessibleEvent( i_eventID, i_newValue, i_oldValue );
2344 void TableControl_Impl::commitCellEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue )
2346 if ( impl_isAccessibleAlive() )
2347 m_pAccessibleTable->commitCellEvent( i_eventID, i_newValue, i_oldValue );
2351 void TableControl_Impl::commitTableEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue )
2353 if ( impl_isAccessibleAlive() )
2354 m_pAccessibleTable->commitTableEvent( i_eventID, i_newValue, i_oldValue );
2358 Rectangle TableControl_Impl::calcHeaderRect(bool bColHeader)
2360 Rectangle const aRectTableWithHeaders( impl_getAllVisibleCellsArea() );
2361 Size const aSizeTableWithHeaders( aRectTableWithHeaders.GetSize() );
2362 if ( bColHeader )
2363 return Rectangle( aRectTableWithHeaders.TopLeft(), Size( aSizeTableWithHeaders.Width(), m_nColHeaderHeightPixel ) );
2364 else
2365 return Rectangle( aRectTableWithHeaders.TopLeft(), Size( m_nRowHeaderWidthPixel, aSizeTableWithHeaders.Height() ) );
2369 Rectangle TableControl_Impl::calcHeaderCellRect( bool bColHeader, sal_Int32 nPos )
2371 Rectangle const aHeaderRect = calcHeaderRect( bColHeader );
2372 TableCellGeometry const aGeometry(
2373 *this, aHeaderRect,
2374 bColHeader ? nPos : COL_ROW_HEADERS,
2375 bColHeader ? ROW_COL_HEADERS : nPos
2377 return aGeometry.getRect();
2381 Rectangle TableControl_Impl::calcTableRect()
2383 return impl_getAllVisibleDataCellArea();
2387 Rectangle TableControl_Impl::calcCellRect( sal_Int32 nRow, sal_Int32 nCol )
2389 Rectangle aCellRect;
2390 impl_getCellRect( nRow, nCol, aCellRect );
2391 return aCellRect;
2395 IMPL_LINK_NOARG( TableControl_Impl, OnUpdateScrollbars )
2397 // TODO: can't we simply use lcl_updateScrollbar here, so the scrollbars ranges are updated, instead of
2398 // doing a complete re-layout?
2399 impl_ni_relayout();
2400 return 1L;
2404 IMPL_LINK( TableControl_Impl, OnScroll, ScrollBar*, _pScrollbar )
2406 DBG_ASSERT( ( _pScrollbar == m_pVScroll ) || ( _pScrollbar == m_pHScroll ),
2407 "TableControl_Impl::OnScroll: where did this come from?" );
2409 if ( _pScrollbar == m_pVScroll )
2410 impl_ni_ScrollRows( _pScrollbar->GetDelta() );
2411 else
2412 impl_ni_ScrollColumns( _pScrollbar->GetDelta() );
2414 return 0L;
2418 Reference< XAccessible > TableControl_Impl::getAccessible( vcl::Window& i_parentWindow )
2420 DBG_TESTSOLARMUTEX();
2421 if ( m_pAccessibleTable == NULL )
2423 Reference< XAccessible > const xAccParent = i_parentWindow.GetAccessible();
2424 if ( xAccParent.is() )
2426 m_pAccessibleTable = m_aFactoryAccess.getFactory().createAccessibleTableControl(
2427 xAccParent, m_rAntiImpl
2432 Reference< XAccessible > xAccessible;
2433 if ( m_pAccessibleTable )
2434 xAccessible = m_pAccessibleTable->getMyself();
2435 return xAccessible;
2439 void TableControl_Impl::disposeAccessible()
2441 if ( m_pAccessibleTable )
2442 m_pAccessibleTable->DisposeAccessImpl();
2443 m_pAccessibleTable = NULL;
2447 bool TableControl_Impl::impl_isAccessibleAlive() const
2449 return ( NULL != m_pAccessibleTable ) && m_pAccessibleTable->isAlive();
2453 void TableControl_Impl::impl_commitAccessibleEvent( sal_Int16 const i_eventID, Any const & i_newValue, Any const & i_oldValue )
2455 if ( impl_isAccessibleAlive() )
2456 m_pAccessibleTable->commitEvent( i_eventID, i_newValue, i_oldValue );
2460 //= TableFunctionSet
2463 TableFunctionSet::TableFunctionSet(TableControl_Impl* _pTableControl)
2464 :m_pTableControl( _pTableControl)
2465 ,m_nCurrentRow( ROW_INVALID )
2469 TableFunctionSet::~TableFunctionSet()
2473 void TableFunctionSet::BeginDrag()
2477 void TableFunctionSet::CreateAnchor()
2479 m_pTableControl->setAnchor( m_pTableControl->getCurRow() );
2483 void TableFunctionSet::DestroyAnchor()
2485 m_pTableControl->setAnchor( ROW_INVALID );
2489 bool TableFunctionSet::SetCursorAtPoint(const Point& rPoint, bool bDontSelectAtCursor)
2491 bool bHandled = false;
2492 // newRow is the row which includes the point, getCurRow() is the last selected row, before the mouse click
2493 RowPos newRow = m_pTableControl->getRowAtPoint( rPoint );
2494 if ( newRow == ROW_COL_HEADERS )
2495 newRow = m_pTableControl->getTopRow();
2497 ColPos newCol = m_pTableControl->getColAtPoint( rPoint );
2498 if ( newCol == COL_ROW_HEADERS )
2499 newCol = m_pTableControl->getLeftColumn();
2501 if ( ( newRow == ROW_INVALID ) || ( newCol == COL_INVALID ) )
2502 return false;
2504 if ( bDontSelectAtCursor )
2506 if ( m_pTableControl->getSelectedRowCount() > 1 )
2507 m_pTableControl->getSelEngine()->AddAlways(true);
2508 bHandled = true;
2510 else if ( m_pTableControl->getAnchor() == m_pTableControl->getCurRow() )
2512 //selecting region,
2513 int diff = m_pTableControl->getCurRow() - newRow;
2514 //selected region lies above the last selection
2515 if( diff >= 0)
2517 //put selected rows in vector
2518 while ( m_pTableControl->getAnchor() >= newRow )
2520 m_pTableControl->markRowAsSelected( m_pTableControl->getAnchor() );
2521 m_pTableControl->setAnchor( m_pTableControl->getAnchor() - 1 );
2522 diff--;
2524 m_pTableControl->setAnchor( m_pTableControl->getAnchor() + 1 );
2526 //selected region lies beneath the last selected row
2527 else
2529 while ( m_pTableControl->getAnchor() <= newRow )
2531 m_pTableControl->markRowAsSelected( m_pTableControl->getAnchor() );
2532 m_pTableControl->setAnchor( m_pTableControl->getAnchor() + 1 );
2533 diff++;
2535 m_pTableControl->setAnchor( m_pTableControl->getAnchor() - 1 );
2537 m_pTableControl->invalidateSelectedRegion( m_pTableControl->getCurRow(), newRow );
2538 bHandled = true;
2540 //no region selected
2541 else
2543 if ( !m_pTableControl->hasRowSelection() )
2544 m_pTableControl->markRowAsSelected( newRow );
2545 else
2547 if ( m_pTableControl->getSelEngine()->GetSelectionMode() == SINGLE_SELECTION )
2549 DeselectAll();
2550 m_pTableControl->markRowAsSelected( newRow );
2552 else
2554 m_pTableControl->markRowAsSelected( newRow );
2557 if ( m_pTableControl->getSelectedRowCount() > 1 && m_pTableControl->getSelEngine()->GetSelectionMode() != SINGLE_SELECTION )
2558 m_pTableControl->getSelEngine()->AddAlways(true);
2560 m_pTableControl->invalidateRow( newRow );
2561 bHandled = true;
2563 m_pTableControl->goTo( newCol, newRow );
2564 return bHandled;
2567 bool TableFunctionSet::IsSelectionAtPoint( const Point& rPoint )
2569 m_pTableControl->getSelEngine()->AddAlways(false);
2570 if ( !m_pTableControl->hasRowSelection() )
2571 return false;
2572 else
2574 RowPos curRow = m_pTableControl->getRowAtPoint( rPoint );
2575 m_pTableControl->setAnchor( ROW_INVALID );
2576 bool selected = m_pTableControl->isRowSelected( curRow );
2577 m_nCurrentRow = curRow;
2578 return selected;
2582 void TableFunctionSet::DeselectAtPoint( const Point& rPoint )
2584 (void)rPoint;
2585 m_pTableControl->invalidateRow( m_nCurrentRow );
2586 m_pTableControl->markRowAsDeselected( m_nCurrentRow );
2590 void TableFunctionSet::DeselectAll()
2592 if ( m_pTableControl->hasRowSelection() )
2594 for ( size_t i=0; i<m_pTableControl->getSelectedRowCount(); ++i )
2596 RowPos const rowIndex = m_pTableControl->getSelectedRowIndex(i);
2597 m_pTableControl->invalidateRow( rowIndex );
2600 m_pTableControl->markAllRowsAsDeselected();
2605 } } // namespace svt::table
2608 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */