xref: /AOO41X/main/svtools/source/table/tablecontrol_impl.cxx (revision 707fc0d4d52eb4f69d89a98ffec6918ca5de6326)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_svtools.hxx"
26 
27 #include "svtools/table/tablecontrol.hxx"
28 #include "svtools/table/defaultinputhandler.hxx"
29 #include "svtools/table/tablemodel.hxx"
30 
31 #include "tabledatawindow.hxx"
32 #include "tablecontrol_impl.hxx"
33 #include "tablegeometry.hxx"
34 
35 /** === begin UNO includes === **/
36 #include <com/sun/star/accessibility/XAccessible.hpp>
37 #include <com/sun/star/accessibility/AccessibleTableModelChange.hpp>
38 #include <com/sun/star/accessibility/AccessibleEventId.hpp>
39 #include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp>
40 /** === end UNO includes === **/
41 
42 #include <comphelper/flagguard.hxx>
43 #include <vcl/scrbar.hxx>
44 #include <vcl/seleng.hxx>
45 #include <rtl/ref.hxx>
46 #include <vcl/image.hxx>
47 #include <tools/diagnose_ex.h>
48 
49 #include <functional>
50 #include <numeric>
51 
52 #define MIN_COLUMN_WIDTH_PIXEL  4
53 
54 //......................................................................................................................
55 namespace svt { namespace table
56 {
57 //......................................................................................................................
58 
59     /** === begin UNO using === **/
60     using ::com::sun::star::accessibility::AccessibleTableModelChange;
61     using ::com::sun::star::uno::makeAny;
62     using ::com::sun::star::uno::Any;
63     using ::com::sun::star::accessibility::XAccessible;
64     using ::com::sun::star::uno::Reference;
65     /** === end UNO using === **/
66     namespace AccessibleEventId = ::com::sun::star::accessibility::AccessibleEventId;
67     namespace AccessibleTableModelChangeType = ::com::sun::star::accessibility::AccessibleTableModelChangeType;
68 
69     //==================================================================================================================
70     //= SuppressCursor
71     //==================================================================================================================
72     class SuppressCursor
73     {
74     private:
75         ITableControl&  m_rTable;
76 
77     public:
78         SuppressCursor( ITableControl& _rTable )
79             :m_rTable( _rTable )
80         {
81             m_rTable.hideCursor();
82         }
83         ~SuppressCursor()
84         {
85             m_rTable.showCursor();
86         }
87     };
88 
89     //====================================================================
90     //= EmptyTableModel
91     //====================================================================
92     /** default implementation of an ->ITableModel, used as fallback when no
93         real model is present
94 
95         Instances of this class are static in any way, and provide the least
96         necessary default functionality for a table model.
97     */
98     class EmptyTableModel : public ITableModel
99     {
100     public:
101         EmptyTableModel()
102         {
103         }
104 
105         // ITableModel overridables
106         virtual TableSize           getColumnCount() const
107         {
108             return 0;
109         }
110         virtual TableSize           getRowCount() const
111         {
112             return 0;
113         }
114         virtual bool                hasColumnHeaders() const
115         {
116             return false;
117         }
118         virtual bool                hasRowHeaders() const
119         {
120             return false;
121         }
122         virtual bool                isCellEditable( ColPos col, RowPos row ) const
123         {
124             (void)col;
125             (void)row;
126             return false;
127         }
128         virtual PColumnModel        getColumnModel( ColPos column )
129         {
130             DBG_ERROR( "EmptyTableModel::getColumnModel: invalid call!" );
131             (void)column;
132             return PColumnModel();
133         }
134         virtual PTableRenderer      getRenderer() const
135         {
136             return PTableRenderer();
137         }
138         virtual PTableInputHandler  getInputHandler() const
139         {
140             return PTableInputHandler();
141         }
142         virtual TableMetrics        getRowHeight() const
143         {
144             return 5 * 100;
145         }
146         virtual void setRowHeight(TableMetrics _nRowHeight)
147         {
148             (void)_nRowHeight;
149         }
150         virtual TableMetrics        getColumnHeaderHeight() const
151         {
152             return 0;
153         }
154         virtual TableMetrics        getRowHeaderWidth() const
155         {
156             return 0;
157         }
158         virtual ScrollbarVisibility getVerticalScrollbarVisibility() const
159         {
160             return ScrollbarShowNever;
161         }
162         virtual ScrollbarVisibility getHorizontalScrollbarVisibility() const
163         {
164             return ScrollbarShowNever;
165         }
166         virtual void addTableModelListener( const PTableModelListener& i_listener )
167         {
168             (void)i_listener;
169         }
170         virtual void removeTableModelListener( const PTableModelListener& i_listener )
171         {
172             (void)i_listener;
173         }
174         virtual ::boost::optional< ::Color > getLineColor() const
175         {
176             return ::boost::optional< ::Color >();
177         }
178         virtual ::boost::optional< ::Color > getHeaderBackgroundColor() const
179         {
180             return ::boost::optional< ::Color >();
181         }
182         virtual ::boost::optional< ::Color > getHeaderTextColor() const
183         {
184             return ::boost::optional< ::Color >();
185         }
186         virtual ::boost::optional< ::Color >    getActiveSelectionBackColor() const
187         {
188             return ::boost::optional< ::Color >();
189         }
190         virtual ::boost::optional< ::Color >    getInactiveSelectionBackColor() const
191         {
192             return ::boost::optional< ::Color >();
193         }
194         virtual ::boost::optional< ::Color >    getActiveSelectionTextColor() const
195         {
196             return ::boost::optional< ::Color >();
197         }
198         virtual ::boost::optional< ::Color >    getInactiveSelectionTextColor() const
199         {
200             return ::boost::optional< ::Color >();
201         }
202         virtual ::boost::optional< ::Color > getTextColor() const
203         {
204             return ::boost::optional< ::Color >();
205         }
206         virtual ::boost::optional< ::Color > getTextLineColor() const
207         {
208             return ::boost::optional< ::Color >();
209         }
210         virtual ::boost::optional< ::std::vector< ::Color > > getRowBackgroundColors() const
211         {
212             return ::boost::optional< ::std::vector< ::Color > >();
213         }
214         virtual ::com::sun::star::style::VerticalAlignment getVerticalAlign() const
215         {
216             return com::sun::star::style::VerticalAlignment(0);
217         }
218         virtual ITableDataSort* getSortAdapter()
219         {
220             return NULL;
221         }
222         virtual void getCellContent( ColPos const i_col, RowPos const i_row, ::com::sun::star::uno::Any& o_cellContent )
223         {
224             (void)i_row;
225             (void)i_col;
226             o_cellContent.clear();
227         }
228         virtual void getCellToolTip( ColPos const, RowPos const, ::com::sun::star::uno::Any& )
229         {
230         }
231         virtual Any getRowHeading( RowPos const i_rowPos ) const
232         {
233             (void)i_rowPos;
234             return Any();
235         }
236     };
237 
238 
239     //====================================================================
240     //= TableControl_Impl
241     //====================================================================
242     DBG_NAME( TableControl_Impl )
243 
244 #if DBG_UTIL
245     //====================================================================
246     //= SuspendInvariants
247     //====================================================================
248     class SuspendInvariants
249     {
250     private:
251         const TableControl_Impl&    m_rTable;
252         sal_Int32                   m_nSuspendFlags;
253 
254     public:
255         SuspendInvariants( const TableControl_Impl& _rTable, sal_Int32 _nSuspendFlags )
256             :m_rTable( _rTable )
257             ,m_nSuspendFlags( _nSuspendFlags )
258         {
259             //DBG_ASSERT( ( m_rTable.m_nRequiredInvariants & m_nSuspendFlags ) == m_nSuspendFlags,
260             //    "SuspendInvariants: cannot suspend what is already suspended!" );
261             const_cast< TableControl_Impl& >( m_rTable ).m_nRequiredInvariants &= ~m_nSuspendFlags;
262         }
263         ~SuspendInvariants()
264         {
265             const_cast< TableControl_Impl& >( m_rTable ).m_nRequiredInvariants |= m_nSuspendFlags;
266         }
267     };
268     #define DBG_SUSPEND_INV( flags ) \
269         SuspendInvariants aSuspendInv( *this, flags );
270 #else
271     #define DBG_SUSPEND_INV( flags )
272 #endif
273 
274 #if DBG_UTIL
275     //====================================================================
276     const char* TableControl_Impl_checkInvariants( const void* _pInstance )
277     {
278         return static_cast< const TableControl_Impl* >( _pInstance )->impl_checkInvariants();
279     }
280 
281     namespace
282     {
283         template< typename SCALAR_TYPE >
284         bool lcl_checkLimitsExclusive( SCALAR_TYPE _nValue, SCALAR_TYPE _nMin, SCALAR_TYPE _nMax )
285         {
286             return ( _nValue > _nMin ) && ( _nValue < _nMax );
287         }
288 
289         template< typename SCALAR_TYPE >
290         bool lcl_checkLimitsExclusive_OrDefault_OrFallback( SCALAR_TYPE _nValue, SCALAR_TYPE _nMin, SCALAR_TYPE _nMax,
291             PTableModel _pModel, SCALAR_TYPE _nDefaultOrFallback )
292         {
293             if ( !_pModel )
294                 return _nValue == _nDefaultOrFallback;
295             if ( _nMax <= _nMin )
296                 return _nDefaultOrFallback == _nValue;
297             return lcl_checkLimitsExclusive( _nValue, _nMin, _nMax );
298         }
299     }
300 
301     //------------------------------------------------------------------------------------------------------------------
302     const sal_Char* TableControl_Impl::impl_checkInvariants() const
303     {
304         if ( !m_pModel )
305             return "no model, not even an EmptyTableModel";
306 
307         if ( !m_pDataWindow )
308             return "invalid data window!";
309 
310         if ( m_pModel->getColumnCount() != m_nColumnCount )
311             return "column counts are inconsistent!";
312 
313         if ( m_pModel->getRowCount() != m_nRowCount )
314             return "row counts are inconsistent!";
315 
316         if ( ( m_nCurColumn != COL_INVALID ) && !m_aColumnWidths.empty() && ( m_nCurColumn < 0 ) || ( m_nCurColumn >= (ColPos)m_aColumnWidths.size() ) )
317             return "current column is invalid!";
318 
319         if ( !lcl_checkLimitsExclusive_OrDefault_OrFallback( m_nTopRow, (RowPos)-1, m_nRowCount, getModel(), (RowPos)0 ) )
320             return "invalid top row value!";
321 
322         if ( !lcl_checkLimitsExclusive_OrDefault_OrFallback( m_nCurRow, (RowPos)-1, m_nRowCount, getModel(), ROW_INVALID ) )
323             return "invalid current row value!";
324 
325         if ( !lcl_checkLimitsExclusive_OrDefault_OrFallback( m_nLeftColumn, (ColPos)-1, m_nColumnCount, getModel(), (ColPos)0 ) )
326             return "invalid current column value!";
327 
328         if ( !lcl_checkLimitsExclusive_OrDefault_OrFallback( m_nCurColumn, (ColPos)-1, m_nColumnCount, getModel(), COL_INVALID ) )
329             return "invalid current column value!";
330 
331         if  ( m_pInputHandler != m_pModel->getInputHandler() )
332             return "input handler is not the model-provided one!";
333 
334         // m_aSelectedRows should have reasonable content
335         {
336             if ( m_aSelectedRows.size() > size_t( m_pModel->getRowCount() ) )
337                 return "there are more rows selected than actually exist";
338             for (   ::std::vector< RowPos >::const_iterator selRow = m_aSelectedRows.begin();
339                     selRow != m_aSelectedRows.end();
340                     ++selRow
341                 )
342             {
343                 if ( ( *selRow < 0 ) || ( *selRow >= m_pModel->getRowCount() ) )
344                     return "a non-existent row is selected";
345             }
346         }
347 
348         // m_nColHeaderHeightPixel consistent with the model's value?
349         {
350             TableMetrics nHeaderHeight = m_pModel->hasColumnHeaders() ? m_pModel->getColumnHeaderHeight() : 0;
351             nHeaderHeight = m_rAntiImpl.LogicToPixel( Size( 0, nHeaderHeight ), MAP_APPFONT ).Height();
352             if ( nHeaderHeight != m_nColHeaderHeightPixel )
353                 return "column header heights are inconsistent!";
354         }
355 
356         bool isDummyModel = dynamic_cast< const EmptyTableModel* >( m_pModel.get() ) != NULL;
357         if ( !isDummyModel )
358         {
359             TableMetrics nRowHeight = m_pModel->getRowHeight();
360             nRowHeight = m_rAntiImpl.LogicToPixel( Size( 0, nRowHeight ), MAP_APPFONT).Height();
361             if ( nRowHeight != m_nRowHeightPixel )
362                 return "row heights are inconsistent!";
363         }
364 
365         // m_nRowHeaderWidthPixel consistent with the model's value?
366         {
367             TableMetrics nHeaderWidth = m_pModel->hasRowHeaders() ? m_pModel->getRowHeaderWidth() : 0;
368             nHeaderWidth = m_rAntiImpl.LogicToPixel( Size( nHeaderWidth, 0 ), MAP_APPFONT ).Width();
369             if ( nHeaderWidth != m_nRowHeaderWidthPixel )
370                 return "row header widths are inconsistent!";
371         }
372 
373         // m_aColumnWidths consistency
374         if ( size_t( m_nColumnCount ) != m_aColumnWidths.size() )
375             return "wrong number of cached column widths";
376 
377         for (   ColumnPositions::const_iterator col = m_aColumnWidths.begin();
378                 col != m_aColumnWidths.end();
379             )
380         {
381             if ( col->getEnd() < col->getStart() )
382                 return "column widths: 'end' is expected to not be smaller than start";
383 
384             ColumnPositions::const_iterator nextCol = col + 1;
385             if ( nextCol != m_aColumnWidths.end() )
386                 if ( col->getEnd() != nextCol->getStart() )
387                     return "column widths: one column's end should be the next column's start";
388             col = nextCol;
389         }
390 
391         if ( m_nLeftColumn < m_nColumnCount )
392             if ( m_aColumnWidths[ m_nLeftColumn ].getStart() != m_nRowHeaderWidthPixel )
393                 return "the left-most column should start immediately after the row header";
394 
395         if ( m_nCursorHidden < 0 )
396             return "invalid hidden count for the cursor!";
397 
398         if ( ( m_nRequiredInvariants & INV_SCROLL_POSITION ) && m_pVScroll )
399         {
400             DBG_SUSPEND_INV( INV_SCROLL_POSITION );
401                 // prevent infinite recursion
402 
403             if ( m_nLeftColumn < 0 )
404                 return "invalid left-most column index";
405             if ( m_pVScroll->GetThumbPos() != m_nTopRow )
406                 return "vertical scroll bar |position| is incorrect!";
407             if ( m_pVScroll->GetRange().Max() != m_nRowCount )
408                 return "vertical scroll bar |range| is incorrect!";
409             if ( m_pVScroll->GetVisibleSize() != impl_getVisibleRows( false ) )
410                 return "vertical scroll bar |visible size| is incorrect!";
411         }
412 
413         if ( ( m_nRequiredInvariants & INV_SCROLL_POSITION ) && m_pHScroll )
414         {
415             DBG_SUSPEND_INV( INV_SCROLL_POSITION );
416                 // prevent infinite recursion
417 
418             if ( m_pHScroll->GetThumbPos() != m_nLeftColumn )
419                 return "horizontal scroll bar |position| is incorrect!";
420             if ( m_pHScroll->GetRange().Max() != m_nColumnCount )
421                 return "horizontal scroll bar |range| is incorrect!";
422             if ( m_pHScroll->GetVisibleSize() != impl_getVisibleColumns( false ) )
423                 return "horizontal scroll bar |visible size| is incorrect!";
424         }
425 
426         return NULL;
427     }
428 #endif
429 
430 #define DBG_CHECK_ME() \
431     DBG_CHKTHIS( TableControl_Impl, TableControl_Impl_checkInvariants )
432 
433     //------------------------------------------------------------------------------------------------------------------
434     TableControl_Impl::TableControl_Impl( TableControl& _rAntiImpl )
435         :m_rAntiImpl            ( _rAntiImpl                    )
436         ,m_pModel               ( new EmptyTableModel           )
437         ,m_pInputHandler        (                               )
438         ,m_nRowHeightPixel      ( 15                            )
439         ,m_nColHeaderHeightPixel( 0                             )
440         ,m_nRowHeaderWidthPixel ( 0                             )
441         ,m_nColumnCount         ( 0                             )
442         ,m_nRowCount            ( 0                             )
443         ,m_bColumnsFit          ( true                          )
444         ,m_nCurColumn           ( COL_INVALID                   )
445         ,m_nCurRow              ( ROW_INVALID                   )
446         ,m_nLeftColumn          ( 0                             )
447         ,m_nTopRow              ( 0                             )
448         ,m_nCursorHidden        ( 1                             )
449         ,m_pDataWindow          ( new TableDataWindow( *this )  )
450         ,m_pVScroll             ( NULL                          )
451         ,m_pHScroll             ( NULL                          )
452         ,m_pScrollCorner        ( NULL                          )
453         ,m_pSelEngine           (                               )
454         ,m_aSelectedRows        (                               )
455         ,m_pTableFunctionSet    ( new TableFunctionSet( this  ) )
456         ,m_nAnchor              ( -1                            )
457         ,m_bUpdatingColWidths   ( false                         )
458         ,m_pAccessibleTable     ( NULL                          )
459 #if DBG_UTIL
460         ,m_nRequiredInvariants ( INV_SCROLL_POSITION )
461 #endif
462     {
463         DBG_CTOR( TableControl_Impl, TableControl_Impl_checkInvariants );
464         m_pSelEngine = new SelectionEngine( m_pDataWindow.get(), m_pTableFunctionSet );
465         m_pSelEngine->SetSelectionMode(SINGLE_SELECTION);
466         m_pDataWindow->SetPosPixel( Point( 0, 0 ) );
467         m_pDataWindow->Show();
468     }
469 
470     //------------------------------------------------------------------------------------------------------------------
471     TableControl_Impl::~TableControl_Impl()
472     {
473         DBG_DTOR( TableControl_Impl, TableControl_Impl_checkInvariants );
474 
475         DELETEZ( m_pVScroll );
476         DELETEZ( m_pHScroll );
477         DELETEZ( m_pScrollCorner );
478         DELETEZ( m_pTableFunctionSet );
479         DELETEZ( m_pSelEngine );
480     }
481 
482     //------------------------------------------------------------------------------------------------------------------
483     void TableControl_Impl::setModel( PTableModel _pModel )
484     {
485         DBG_CHECK_ME();
486 
487         SuppressCursor aHideCursor( *this );
488 
489         if ( !!m_pModel )
490             m_pModel->removeTableModelListener( shared_from_this() );
491 
492         m_pModel = _pModel;
493         if ( !m_pModel)
494             m_pModel.reset( new EmptyTableModel );
495 
496         m_pModel->addTableModelListener( shared_from_this() );
497 
498         m_nCurRow = ROW_INVALID;
499         m_nCurColumn = COL_INVALID;
500 
501         // recalc some model-dependent cached info
502         impl_ni_updateCachedModelValues();
503         impl_ni_relayout();
504 
505         // completely invalidate
506         m_rAntiImpl.Invalidate();
507 
508         // reset cursor to (0,0)
509         if ( m_nRowCount ) m_nCurRow = 0;
510         if ( m_nColumnCount ) m_nCurColumn = 0;
511     }
512 
513     //------------------------------------------------------------------------------------------------------------------
514     namespace
515     {
516         bool lcl_adjustSelectedRows( ::std::vector< RowPos >& io_selectionIndexes, RowPos const i_firstAffectedRowIndex, TableSize const i_offset )
517         {
518             bool didChanges = false;
519             for (   ::std::vector< RowPos >::iterator selPos = io_selectionIndexes.begin();
520                     selPos != io_selectionIndexes.end();
521                     ++selPos
522                 )
523             {
524                 if ( *selPos < i_firstAffectedRowIndex )
525                     continue;
526                 *selPos += i_offset;
527                 didChanges = true;
528             }
529             return didChanges;
530         }
531     }
532 
533     //------------------------------------------------------------------------------------------------------------------
534     void TableControl_Impl::rowsInserted( RowPos i_first, RowPos i_last )
535     {
536         DBG_CHECK_ME();
537         OSL_PRECOND( i_last >= i_first, "TableControl_Impl::rowsInserted: invalid row indexes!" );
538 
539         TableSize const insertedRows = i_last - i_first + 1;
540 
541         // adjust selection, if necessary
542         bool const selectionChanged = lcl_adjustSelectedRows( m_aSelectedRows, i_first, insertedRows );
543 
544         // adjust our cached row count
545         m_nRowCount = m_pModel->getRowCount();
546 
547         // if the rows have been inserted before the current row, adjust this
548         if ( i_first <= m_nCurRow )
549             goTo( m_nCurColumn, m_nCurRow + insertedRows );
550 
551         // relayout, since the scrollbar need might have changed
552         impl_ni_relayout();
553 
554         // notify A1YY events
555         if ( impl_isAccessibleAlive() )
556         {
557             impl_commitAccessibleEvent( AccessibleEventId::TABLE_MODEL_CHANGED,
558                 makeAny( AccessibleTableModelChange( AccessibleTableModelChangeType::INSERT, i_first, i_last, 0, m_pModel->getColumnCount() ) ),
559                 Any()
560             );
561         }
562 
563         // schedule repaint
564         invalidateRowRange( i_first, ROW_INVALID );
565 
566         // call selection handlers, if necessary
567         if ( selectionChanged )
568             m_rAntiImpl.Select();
569     }
570 
571     //------------------------------------------------------------------------------------------------------------------
572     void TableControl_Impl::rowsRemoved( RowPos i_first, RowPos i_last )
573     {
574         sal_Int32 firstRemovedRow = i_first;
575         sal_Int32 lastRemovedRow = i_last;
576 
577         // adjust selection, if necessary
578         bool selectionChanged = false;
579         if ( i_first == -1 )
580         {
581             selectionChanged = markAllRowsAsDeselected();
582 
583             firstRemovedRow = 0;
584             lastRemovedRow = m_nRowCount - 1;
585         }
586         else
587         {
588             ENSURE_OR_RETURN_VOID( i_last >= i_first, "TableControl_Impl::rowsRemoved: illegal indexes!" );
589 
590             for ( sal_Int32 row = i_first; row <= i_last; ++row )
591             {
592                 if ( markRowAsDeselected( row ) )
593                     selectionChanged = true;
594             }
595 
596             if ( lcl_adjustSelectedRows( m_aSelectedRows, i_last + 1, i_first - i_last - 1 ) )
597                 selectionChanged = true;
598         }
599 
600         // adjust cached row count
601         m_nRowCount = m_pModel->getRowCount();
602 
603         // adjust the current row, if it is larger than the row count now
604         if ( m_nCurRow >= m_nRowCount )
605         {
606             if ( m_nRowCount > 0 )
607                 goTo( m_nCurColumn, m_nRowCount - 1 );
608             else
609                 m_nCurRow = ROW_INVALID;
610         }
611 
612         // relayout, since the scrollbar need might have changed
613         impl_ni_relayout();
614 
615         // notify A11Y events
616         if ( impl_isAccessibleAlive() )
617         {
618             commitTableEvent(
619                 AccessibleEventId::TABLE_MODEL_CHANGED,
620                 makeAny( AccessibleTableModelChange(
621                     AccessibleTableModelChangeType::DELETE,
622                     firstRemovedRow,
623                     lastRemovedRow,
624                     0,
625                     m_pModel->getColumnCount()
626                 ) ),
627                 Any()
628             );
629         }
630 
631         // schedule a repaint
632         invalidateRowRange( firstRemovedRow, ROW_INVALID );
633 
634         // call selection handlers, if necessary
635         if ( selectionChanged )
636             m_rAntiImpl.Select();
637     }
638 
639     //------------------------------------------------------------------------------------------------------------------
640     void TableControl_Impl::columnInserted( ColPos const i_colIndex )
641     {
642         m_nColumnCount = m_pModel->getColumnCount();
643         impl_ni_relayout();
644 
645         m_rAntiImpl.Invalidate();
646 
647         OSL_UNUSED( i_colIndex );
648    }
649 
650     //------------------------------------------------------------------------------------------------------------------
651     void TableControl_Impl::columnRemoved( ColPos const i_colIndex )
652     {
653         m_nColumnCount = m_pModel->getColumnCount();
654 
655         // adjust the current column, if it is larger than the column count now
656         if ( m_nCurColumn >= m_nColumnCount )
657         {
658             if ( m_nColumnCount > 0 )
659                 goTo( m_nCurColumn - 1, m_nCurRow );
660             else
661                 m_nCurColumn = COL_INVALID;
662         }
663 
664         impl_ni_relayout();
665 
666         m_rAntiImpl.Invalidate();
667 
668         OSL_UNUSED( i_colIndex );
669     }
670 
671     //------------------------------------------------------------------------------------------------------------------
672     void TableControl_Impl::allColumnsRemoved()
673     {
674         m_nColumnCount = m_pModel->getColumnCount();
675         impl_ni_relayout();
676 
677         m_rAntiImpl.Invalidate();
678     }
679 
680     //------------------------------------------------------------------------------------------------------------------
681     void TableControl_Impl::cellsUpdated( ColPos const i_firstCol, ColPos i_lastCol, RowPos const i_firstRow, RowPos const i_lastRow )
682     {
683         invalidateRowRange( i_firstRow, i_lastRow );
684 
685         OSL_UNUSED( i_firstCol );
686         OSL_UNUSED( i_lastCol );
687     }
688 
689     //------------------------------------------------------------------------------------------------------------------
690     void TableControl_Impl::tableMetricsChanged()
691     {
692         impl_ni_updateCachedTableMetrics();
693         impl_ni_relayout();
694         m_rAntiImpl.Invalidate();
695     }
696 
697     //------------------------------------------------------------------------------------------------------------------
698     void TableControl_Impl::impl_invalidateColumn( ColPos const i_column )
699     {
700         DBG_CHECK_ME();
701 
702         Rectangle const aAllCellsArea( impl_getAllVisibleCellsArea() );
703 
704         const TableColumnGeometry aColumn( *this, aAllCellsArea, i_column );
705         if ( aColumn.isValid() )
706             m_rAntiImpl.Invalidate( aColumn.getRect() );
707     }
708 
709     //------------------------------------------------------------------------------------------------------------------
710     void TableControl_Impl::columnChanged( ColPos const i_column, ColumnAttributeGroup const i_attributeGroup )
711     {
712         ColumnAttributeGroup nGroup( i_attributeGroup );
713         if ( nGroup & COL_ATTRS_APPEARANCE )
714         {
715             impl_invalidateColumn( i_column );
716             nGroup &= ~COL_ATTRS_APPEARANCE;
717         }
718 
719         if ( nGroup & COL_ATTRS_WIDTH )
720         {
721             if ( !m_bUpdatingColWidths )
722             {
723                 impl_ni_relayout( i_column );
724                 invalidate( TableAreaAll );
725             }
726 
727             nGroup &= ~COL_ATTRS_WIDTH;
728         }
729 
730         OSL_ENSURE( ( nGroup == COL_ATTRS_NONE ) || ( i_attributeGroup == COL_ATTRS_ALL ),
731             "TableControl_Impl::columnChanged: don't know how to handle this change!" );
732     }
733 
734     //------------------------------------------------------------------------------------------------------------------
735     Rectangle TableControl_Impl::impl_getAllVisibleCellsArea() const
736     {
737         DBG_CHECK_ME();
738 
739         Rectangle aArea( Point( 0, 0 ), Size( 0, 0 ) );
740 
741         // determine the right-most border of the last column which is
742         // at least partially visible
743         aArea.Right() = m_nRowHeaderWidthPixel;
744         if ( !m_aColumnWidths.empty() )
745         {
746             // the number of pixels which are scrolled out of the left hand
747             // side of the window
748             const long nScrolledOutLeft = m_nLeftColumn == 0 ? 0 : m_aColumnWidths[ m_nLeftColumn - 1 ].getEnd();
749 
750             ColumnPositions::const_reverse_iterator loop = m_aColumnWidths.rbegin();
751             do
752             {
753                 aArea.Right() = loop->getEnd() - nScrolledOutLeft + m_nRowHeaderWidthPixel;
754                 ++loop;
755             }
756             while ( (   loop != m_aColumnWidths.rend() )
757                  && (   loop->getEnd() - nScrolledOutLeft >= aArea.Right() )
758                  );
759         }
760         // so far, aArea.Right() denotes the first pixel *after* the cell area
761         --aArea.Right();
762 
763         // determine the last row which is at least partially visible
764         aArea.Bottom() =
765                 m_nColHeaderHeightPixel
766             +   impl_getVisibleRows( true ) * m_nRowHeightPixel
767             -   1;
768 
769         return aArea;
770     }
771 
772     //------------------------------------------------------------------------------------------------------------------
773     Rectangle TableControl_Impl::impl_getAllVisibleDataCellArea() const
774     {
775         DBG_CHECK_ME();
776 
777         Rectangle aArea( impl_getAllVisibleCellsArea() );
778         aArea.Left() = m_nRowHeaderWidthPixel;
779         aArea.Top() = m_nColHeaderHeightPixel;
780         return aArea;
781     }
782 
783     //------------------------------------------------------------------------------------------------------------------
784     void TableControl_Impl::impl_ni_updateCachedTableMetrics()
785     {
786         m_nRowHeightPixel = m_rAntiImpl.LogicToPixel( Size( 0, m_pModel->getRowHeight() ), MAP_APPFONT ).Height();
787 
788         m_nColHeaderHeightPixel = 0;
789         if ( m_pModel->hasColumnHeaders() )
790            m_nColHeaderHeightPixel = m_rAntiImpl.LogicToPixel( Size( 0, m_pModel->getColumnHeaderHeight() ), MAP_APPFONT ).Height();
791 
792         m_nRowHeaderWidthPixel = 0;
793         if ( m_pModel->hasRowHeaders() )
794             m_nRowHeaderWidthPixel = m_rAntiImpl.LogicToPixel( Size( m_pModel->getRowHeaderWidth(), 0 ), MAP_APPFONT).Width();
795     }
796 
797     //------------------------------------------------------------------------------------------------------------------
798     void TableControl_Impl::impl_ni_updateCachedModelValues()
799     {
800         m_pInputHandler = m_pModel->getInputHandler();
801         if ( !m_pInputHandler )
802             m_pInputHandler.reset( new DefaultInputHandler );
803 
804         m_nColumnCount = m_pModel->getColumnCount();
805         if ( m_nLeftColumn >= m_nColumnCount )
806             m_nLeftColumn = ( m_nColumnCount > 0 ) ? m_nColumnCount - 1 : 0;
807 
808         m_nRowCount = m_pModel->getRowCount();
809         if ( m_nTopRow >= m_nRowCount )
810             m_nTopRow = ( m_nRowCount > 0 ) ? m_nRowCount - 1 : 0;
811 
812         impl_ni_updateCachedTableMetrics();
813     }
814 
815     //------------------------------------------------------------------------------------------------------------------
816     namespace
817     {
818         //..............................................................................................................
819         /// determines whether a scrollbar is needed for the given values
820         bool lcl_determineScrollbarNeed( long const i_position, ScrollbarVisibility const i_visibility,
821             long const i_availableSpace, long const i_neededSpace )
822         {
823             if ( i_visibility == ScrollbarShowNever )
824                 return false;
825             if ( i_visibility == ScrollbarShowAlways )
826                 return true;
827             if ( i_position > 0 )
828                 return true;
829             if ( i_availableSpace >= i_neededSpace )
830                 return false;
831             return true;
832         }
833 
834         //..............................................................................................................
835         void lcl_setButtonRepeat( Window& _rWindow, sal_uLong _nDelay )
836         {
837             AllSettings aSettings = _rWindow.GetSettings();
838             MouseSettings aMouseSettings = aSettings.GetMouseSettings();
839 
840             aMouseSettings.SetButtonRepeat( _nDelay );
841             aSettings.SetMouseSettings( aMouseSettings );
842 
843             _rWindow.SetSettings( aSettings, sal_True );
844         }
845 
846         //..............................................................................................................
847         bool lcl_updateScrollbar( Window& _rParent, ScrollBar*& _rpBar,
848             bool const i_needBar, long _nVisibleUnits,
849             long _nPosition, long _nLineSize, long _nRange,
850             bool _bHorizontal, const Link& _rScrollHandler )
851         {
852             // do we currently have the scrollbar?
853             bool bHaveBar = _rpBar != NULL;
854 
855             // do we need to correct the scrollbar visibility?
856             if ( bHaveBar && !i_needBar )
857             {
858                 if ( _rpBar->IsTracking() )
859                     _rpBar->EndTracking();
860                 DELETEZ( _rpBar );
861             }
862             else if ( !bHaveBar && i_needBar )
863             {
864                 _rpBar = new ScrollBar(
865                     &_rParent,
866                     WB_DRAG | ( _bHorizontal ? WB_HSCROLL : WB_VSCROLL )
867                 );
868                 _rpBar->SetScrollHdl( _rScrollHandler );
869                 // get some speed into the scrolling ....
870                 lcl_setButtonRepeat( *_rpBar, 0 );
871             }
872 
873             if ( _rpBar )
874             {
875                 _rpBar->SetRange( Range( 0, _nRange ) );
876                 _rpBar->SetVisibleSize( _nVisibleUnits );
877                 _rpBar->SetPageSize( _nVisibleUnits );
878                 _rpBar->SetLineSize( _nLineSize );
879                 _rpBar->SetThumbPos( _nPosition );
880                 _rpBar->Show();
881             }
882 
883             return ( bHaveBar != i_needBar );
884         }
885 
886         //..............................................................................................................
887         /** returns the number of rows fitting into the given range,
888             for the given row height. Partially fitting rows are counted, too, if the
889             respective parameter says so.
890         */
891         TableSize lcl_getRowsFittingInto( long _nOverallHeight, long _nRowHeightPixel, bool _bAcceptPartialRow = false )
892         {
893             return  _bAcceptPartialRow
894                 ?   ( _nOverallHeight + ( _nRowHeightPixel - 1 ) ) / _nRowHeightPixel
895                 :   _nOverallHeight / _nRowHeightPixel;
896         }
897 
898         //..............................................................................................................
899         /** returns the number of columns fitting into the given area,
900             with the first visible column as given. Partially fitting columns are counted, too,
901             if the respective parameter says so.
902         */
903         TableSize lcl_getColumnsVisibleWithin( const Rectangle& _rArea, ColPos _nFirstVisibleColumn,
904             const TableControl_Impl& _rControl, bool _bAcceptPartialRow )
905         {
906             TableSize visibleColumns = 0;
907             TableColumnGeometry aColumn( _rControl, _rArea, _nFirstVisibleColumn );
908             while ( aColumn.isValid() )
909             {
910                 if ( !_bAcceptPartialRow )
911                     if ( aColumn.getRect().Right() > _rArea.Right() )
912                         // this column is only partially visible, and this is not allowed
913                         break;
914 
915                 aColumn.moveRight();
916                 ++visibleColumns;
917             }
918             return visibleColumns;
919         }
920 
921     }
922 
923     //------------------------------------------------------------------------------------------------------------------
924     long TableControl_Impl::impl_ni_calculateColumnWidths( ColPos const i_assumeInflexibleColumnsUpToIncluding,
925         bool const i_assumeVerticalScrollbar, ::std::vector< long >& o_newColWidthsPixel ) const
926     {
927         // the available horizontal space
928         long gridWidthPixel = m_rAntiImpl.GetOutputSizePixel().Width();
929         ENSURE_OR_RETURN( !!m_pModel, "TableControl_Impl::impl_ni_calculateColumnWidths: not allowed without a model!", gridWidthPixel );
930         if ( m_pModel->hasRowHeaders() && ( gridWidthPixel != 0 ) )
931         {
932             gridWidthPixel -= m_nRowHeaderWidthPixel;
933         }
934 
935         if ( i_assumeVerticalScrollbar && ( m_pModel->getVerticalScrollbarVisibility() != ScrollbarShowNever ) )
936         {
937             long nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
938             gridWidthPixel -= nScrollbarMetrics;
939         }
940 
941         // no need to do anything without columns
942         TableSize const colCount = m_pModel->getColumnCount();
943         if ( colCount == 0 )
944             return gridWidthPixel;
945 
946         // collect some meta data for our columns:
947         // - their current (pixel) metrics
948         long accumulatedCurrentWidth = 0;
949         ::std::vector< long > currentColWidths;
950         currentColWidths.reserve( colCount );
951         typedef ::std::vector< ::std::pair< long, long > >   ColumnLimits;
952         ColumnLimits effectiveColumnLimits;
953         effectiveColumnLimits.reserve( colCount );
954         long accumulatedMinWidth = 0;
955         long accumulatedMaxWidth = 0;
956         // - their relative flexibility
957         ::std::vector< ::sal_Int32 > columnFlexibilities;
958         columnFlexibilities.reserve( colCount );
959         long flexibilityDenominator = 0;
960         size_t flexibleColumnCount = 0;
961         for ( ColPos col = 0; col < colCount; ++col )
962         {
963             PColumnModel const pColumn = m_pModel->getColumnModel( col );
964             ENSURE_OR_THROW( !!pColumn, "invalid column returned by the model!" );
965 
966             // current width
967             long const currentWidth = appFontWidthToPixel( pColumn->getWidth() );
968             currentColWidths.push_back( currentWidth );
969 
970             // accumulated width
971             accumulatedCurrentWidth += currentWidth;
972 
973             // flexibility
974             ::sal_Int32 flexibility = pColumn->getFlexibility();
975             OSL_ENSURE( flexibility >= 0, "TableControl_Impl::impl_ni_calculateColumnWidths: a column's flexibility should be non-negative." );
976             if  (   ( flexibility < 0 )                                 // normalization
977                 ||  ( !pColumn->isResizable() )                         // column not resizeable => no auto-resize
978                 ||  ( col <= i_assumeInflexibleColumnsUpToIncluding )   // column shall be treated as inflexible => respec this
979                 )
980                 flexibility = 0;
981 
982             // min/max width
983             long effectiveMin = currentWidth, effectiveMax = currentWidth;
984             // if the column is not flexible, it will not be asked for min/max, but we assume the current width as limit then
985             if ( flexibility > 0 )
986             {
987                 long const minWidth = appFontWidthToPixel( pColumn->getMinWidth() );
988                 if ( minWidth > 0 )
989                     effectiveMin = minWidth;
990                 else
991                     effectiveMin = MIN_COLUMN_WIDTH_PIXEL;
992 
993                 long const maxWidth = appFontWidthToPixel( pColumn->getMaxWidth() );
994                 OSL_ENSURE( minWidth <= maxWidth, "TableControl_Impl::impl_ni_calculateColumnWidths: pretty undecided 'bout its width limits, this column!" );
995                 if ( ( maxWidth > 0 ) && ( maxWidth >= minWidth ) )
996                     effectiveMax = maxWidth;
997                 else
998                     effectiveMax = gridWidthPixel; // TODO: any better guess here?
999 
1000                 if ( effectiveMin == effectiveMax )
1001                     // if the min and the max are identical, this implies no flexibility at all
1002                     flexibility = 0;
1003             }
1004 
1005             columnFlexibilities.push_back( flexibility );
1006             flexibilityDenominator += flexibility;
1007             if ( flexibility > 0 )
1008                 ++flexibleColumnCount;
1009 
1010             effectiveColumnLimits.push_back( ::std::pair< long, long >( effectiveMin, effectiveMax ) );
1011             accumulatedMinWidth += effectiveMin;
1012             accumulatedMaxWidth += effectiveMax;
1013         }
1014 
1015         o_newColWidthsPixel = currentColWidths;
1016         if ( flexibilityDenominator == 0 )
1017         {
1018             // no column is flexible => don't adjust anything
1019         }
1020         else if ( gridWidthPixel > accumulatedCurrentWidth )
1021         {   // we have space to give away ...
1022             long distributePixel = gridWidthPixel - accumulatedCurrentWidth;
1023             if ( gridWidthPixel > accumulatedMaxWidth )
1024             {
1025                 // ... but the column's maximal widths are still less than we have
1026                 // => set them all to max
1027                 for ( size_t i = 0; i < size_t( colCount ); ++i )
1028                 {
1029                     o_newColWidthsPixel[i] = effectiveColumnLimits[i].second;
1030                 }
1031             }
1032             else
1033             {
1034                 bool startOver = false;
1035                 do
1036                 {
1037                     startOver = false;
1038                     // distribute the remaining space amongst all columns with a positive flexibility
1039                     for ( size_t i=0; i<o_newColWidthsPixel.size() && !startOver; ++i )
1040                     {
1041                         long const columnFlexibility = columnFlexibilities[i];
1042                         if ( columnFlexibility == 0 )
1043                             continue;
1044 
1045                         long newColWidth = currentColWidths[i] + columnFlexibility * distributePixel / flexibilityDenominator;
1046 
1047                         if ( newColWidth > effectiveColumnLimits[i].second )
1048                         {   // that was too much, we hit the col's maximum
1049                             // set the new width to exactly this maximum
1050                             newColWidth = effectiveColumnLimits[i].second;
1051                             // adjust the flexibility denominator ...
1052                             flexibilityDenominator -= columnFlexibility;
1053                             columnFlexibilities[i] = 0;
1054                             --flexibleColumnCount;
1055                             // ... and the remaining width ...
1056                             long const difference = newColWidth - currentColWidths[i];
1057                             distributePixel -= difference;
1058                             // ... this way, we ensure that the width not taken up by this column is consumed by the other
1059                             // flexible ones (if there are some)
1060 
1061                             // and start over with the first column, since there might be earlier columns which need
1062                             // to be recalculated now
1063                             startOver = true;
1064                         }
1065 
1066                         o_newColWidthsPixel[i] = newColWidth;
1067                     }
1068                 }
1069                 while ( startOver );
1070 
1071                 // are there pixels left (might be caused by rounding errors)?
1072                 distributePixel = gridWidthPixel - ::std::accumulate( o_newColWidthsPixel.begin(), o_newColWidthsPixel.end(), 0 );
1073                 while ( ( distributePixel > 0 ) && ( flexibleColumnCount > 0 ) )
1074                 {
1075                     // yes => ignore relative flexibilities, and subsequently distribute single pixels to all flexible
1076                     // columns which did not yet reach their maximum.
1077                     for ( size_t i=0; ( i < o_newColWidthsPixel.size() ) && ( distributePixel > 0 ); ++i )
1078                     {
1079                         if ( columnFlexibilities[i] == 0 )
1080                             continue;
1081 
1082                         OSL_ENSURE( o_newColWidthsPixel[i] <= effectiveColumnLimits[i].second,
1083                             "TableControl_Impl::impl_ni_calculateColumnWidths: inconsitency!" );
1084                         if ( o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first )
1085                         {
1086                             columnFlexibilities[i] = 0;
1087                             --flexibleColumnCount;
1088                             continue;
1089                         }
1090 
1091                         ++o_newColWidthsPixel[i];
1092                         --distributePixel;
1093                     }
1094                 }
1095             }
1096         }
1097         else if ( gridWidthPixel < accumulatedCurrentWidth )
1098         {   // we need to take away some space from the columns which allow it ...
1099             long takeAwayPixel = accumulatedCurrentWidth - gridWidthPixel;
1100             if ( gridWidthPixel < accumulatedMinWidth )
1101             {
1102                 // ... but the column's minimal widths are still more than we have
1103                 // => set them all to min
1104                 for ( size_t i = 0; i < size_t( colCount ); ++i )
1105                 {
1106                     o_newColWidthsPixel[i] = effectiveColumnLimits[i].first;
1107                 }
1108             }
1109             else
1110             {
1111                 bool startOver = false;
1112                 do
1113                 {
1114                     startOver = false;
1115                     // take away the space we need from the columns with a positive flexibility
1116                     for ( size_t i=0; i<o_newColWidthsPixel.size() && !startOver; ++i )
1117                     {
1118                         long const columnFlexibility = columnFlexibilities[i];
1119                         if ( columnFlexibility == 0 )
1120                             continue;
1121 
1122                         long newColWidth = currentColWidths[i] - columnFlexibility * takeAwayPixel / flexibilityDenominator;
1123 
1124                         if ( newColWidth < effectiveColumnLimits[i].first )
1125                         {   // that was too much, we hit the col's minimum
1126                             // set the new width to exactly this minimum
1127                             newColWidth = effectiveColumnLimits[i].first;
1128                             // adjust the flexibility denominator ...
1129                             flexibilityDenominator -= columnFlexibility;
1130                             columnFlexibilities[i] = 0;
1131                             --flexibleColumnCount;
1132                             // ... and the remaining width ...
1133                             long const difference = currentColWidths[i] - newColWidth;
1134                             takeAwayPixel -= difference;
1135 
1136                             // and start over with the first column, since there might be earlier columns which need
1137                             // to be recalculated now
1138                             startOver = true;
1139                         }
1140 
1141                         o_newColWidthsPixel[i] = newColWidth;
1142                     }
1143                 }
1144                 while ( startOver );
1145 
1146                 // are there pixels left (might be caused by rounding errors)?
1147                 takeAwayPixel = ::std::accumulate( o_newColWidthsPixel.begin(), o_newColWidthsPixel.end(), 0 ) - gridWidthPixel;
1148                 while ( ( takeAwayPixel > 0 ) && ( flexibleColumnCount > 0 ) )
1149                 {
1150                     // yes => ignore relative flexibilities, and subsequently take away pixels from all flexible
1151                     // columns which did not yet reach their minimum.
1152                     for ( size_t i=0; ( i < o_newColWidthsPixel.size() ) && ( takeAwayPixel > 0 ); ++i )
1153                     {
1154                         if ( columnFlexibilities[i] == 0 )
1155                             continue;
1156 
1157                         OSL_ENSURE( o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first,
1158                             "TableControl_Impl::impl_ni_calculateColumnWidths: inconsitency!" );
1159                         if ( o_newColWidthsPixel[i] <= effectiveColumnLimits[i].first )
1160                         {
1161                             columnFlexibilities[i] = 0;
1162                             --flexibleColumnCount;
1163                             continue;
1164                         }
1165 
1166                         --o_newColWidthsPixel[i];
1167                         --takeAwayPixel;
1168                     }
1169                 }
1170             }
1171         }
1172 
1173         return gridWidthPixel;
1174     }
1175 
1176     //------------------------------------------------------------------------------------------------------------------
1177     void TableControl_Impl::impl_ni_relayout( ColPos const i_assumeInflexibleColumnsUpToIncluding )
1178     {
1179         ENSURE_OR_RETURN_VOID( !m_bUpdatingColWidths, "TableControl_Impl::impl_ni_relayout: recursive call detected!" );
1180 
1181         m_aColumnWidths.resize( 0 );
1182         if ( !m_pModel )
1183             return;
1184 
1185         ::comphelper::FlagRestorationGuard const aWidthUpdateFlag( m_bUpdatingColWidths, true );
1186         SuppressCursor aHideCursor( *this );
1187 
1188         // layouting steps:
1189         //
1190         // 1. adjust column widths, leaving space for a vertical scrollbar
1191         // 2. determine need for a vertical scrollbar
1192         //    - V-YES: all fine, result from 1. is still valid
1193         //    - V-NO: result from 1. is still under consideration
1194         //
1195         // 3. determine need for a horizontal scrollbar
1196         //   - H-NO: all fine, result from 2. is still valid
1197         //   - H-YES: reconsider need for a vertical scrollbar, if result of 2. was V-NO
1198         //     - V-YES: all fine, result from 1. is still valid
1199         //     - V-NO: redistribute the remaining space (if any) amongst all columns which allow it
1200 
1201         ::std::vector< long > newWidthsPixel;
1202         long gridWidthPixel = impl_ni_calculateColumnWidths( i_assumeInflexibleColumnsUpToIncluding, true, newWidthsPixel );
1203 
1204         // the width/height of a scrollbar, needed several times below
1205         long const nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
1206 
1207         // determine the playground for the data cells (excluding headers)
1208         // TODO: what if the control is smaller than needed for the headers/scrollbars?
1209         Rectangle aDataCellPlayground( Point( 0, 0 ), m_rAntiImpl.GetOutputSizePixel() );
1210         aDataCellPlayground.Left() = m_nRowHeaderWidthPixel;
1211         aDataCellPlayground.Top() = m_nColHeaderHeightPixel;
1212 
1213         OSL_ENSURE( ( m_nRowCount == m_pModel->getRowCount() ) && ( m_nColumnCount == m_pModel->getColumnCount() ),
1214             "TableControl_Impl::impl_ni_relayout: how is this expected to work with invalid data?" );
1215         long const nAllColumnsWidth = ::std::accumulate( newWidthsPixel.begin(), newWidthsPixel.end(), 0 );
1216 
1217         ScrollbarVisibility const eVertScrollbar = m_pModel->getVerticalScrollbarVisibility();
1218         ScrollbarVisibility const eHorzScrollbar = m_pModel->getHorizontalScrollbarVisibility();
1219 
1220         // do we need a vertical scrollbar?
1221         bool bNeedVerticalScrollbar = lcl_determineScrollbarNeed(
1222             m_nTopRow, eVertScrollbar, aDataCellPlayground.GetHeight(), m_nRowHeightPixel * m_nRowCount );
1223         bool bFirstRoundVScrollNeed = false;
1224         if ( bNeedVerticalScrollbar )
1225         {
1226             aDataCellPlayground.Right() -= nScrollbarMetrics;
1227             bFirstRoundVScrollNeed = true;
1228         }
1229 
1230         // do we need a horizontal scrollbar?
1231         bool const bNeedHorizontalScrollbar = lcl_determineScrollbarNeed(
1232             m_nLeftColumn, eHorzScrollbar, aDataCellPlayground.GetWidth(), nAllColumnsWidth );
1233         if ( bNeedHorizontalScrollbar )
1234         {
1235             aDataCellPlayground.Bottom() -= nScrollbarMetrics;
1236 
1237             // now that we just found that we need a horizontal scrollbar,
1238             // the need for a vertical one may have changed, since the horizontal
1239             // SB might just occupy enough space so that not all rows do fit
1240             // anymore
1241             if  ( !bFirstRoundVScrollNeed )
1242             {
1243                 bNeedVerticalScrollbar = lcl_determineScrollbarNeed(
1244                     m_nTopRow, eVertScrollbar, aDataCellPlayground.GetHeight(), m_nRowHeightPixel * m_nRowCount );
1245                 if ( bNeedVerticalScrollbar )
1246                 {
1247                     aDataCellPlayground.Right() -= nScrollbarMetrics;
1248                 }
1249             }
1250         }
1251 
1252         // the initial call to impl_ni_calculateColumnWidths assumed that we need a vertical scrollbar. If, by now,
1253         // we know that this is not the case, re-calculate the column widths.
1254         if ( !bNeedVerticalScrollbar )
1255             gridWidthPixel = impl_ni_calculateColumnWidths( i_assumeInflexibleColumnsUpToIncluding, false, newWidthsPixel );
1256 
1257         // update the column objects with the new widths we finally calculated
1258         TableSize const colCount = m_pModel->getColumnCount();
1259         m_aColumnWidths.reserve( colCount );
1260         long accumulatedWidthPixel = m_nRowHeaderWidthPixel;
1261         bool anyColumnWidthChanged = false;
1262         for ( ColPos col = 0; col < colCount; ++col )
1263         {
1264             const long columnStart = accumulatedWidthPixel;
1265             const long columnEnd = columnStart + newWidthsPixel[col];
1266             m_aColumnWidths.push_back( MutableColumnMetrics( columnStart, columnEnd ) );
1267             accumulatedWidthPixel = columnEnd;
1268 
1269             // and don't forget to forward this to the column models
1270             PColumnModel const pColumn = m_pModel->getColumnModel( col );
1271             ENSURE_OR_THROW( !!pColumn, "invalid column returned by the model!" );
1272 
1273             long const oldColumnWidthAppFont = pColumn->getWidth();
1274             long const newColumnWidthAppFont = pixelWidthToAppFont( newWidthsPixel[col] );
1275             pColumn->setWidth( newColumnWidthAppFont );
1276 
1277             anyColumnWidthChanged |= ( oldColumnWidthAppFont != newColumnWidthAppFont );
1278         }
1279 
1280         // if the column widths changed, ensure everything is repainted
1281         if ( anyColumnWidthChanged )
1282             invalidate( TableAreaAll );
1283 
1284         // if the column resizing happened to leave some space at the right, but there are columns
1285         // scrolled out to the left, scroll them in
1286         while   (   ( m_nLeftColumn > 0 )
1287                 &&  ( accumulatedWidthPixel - m_aColumnWidths[ m_nLeftColumn - 1 ].getStart() <= gridWidthPixel )
1288                 )
1289         {
1290             --m_nLeftColumn;
1291         }
1292 
1293         // now adjust the column metrics, since they currently ignore the horizontal scroll position
1294         if ( m_nLeftColumn > 0 )
1295         {
1296             const long offsetPixel = m_aColumnWidths[ 0 ].getStart() - m_aColumnWidths[ m_nLeftColumn ].getStart();
1297             for (   ColumnPositions::iterator colPos = m_aColumnWidths.begin();
1298                     colPos != m_aColumnWidths.end();
1299                     ++colPos
1300                  )
1301             {
1302                 colPos->move( offsetPixel );
1303             }
1304         }
1305 
1306         // show or hide the scrollbars as needed, and position the data window
1307         impl_ni_positionChildWindows( aDataCellPlayground, bNeedVerticalScrollbar, bNeedHorizontalScrollbar );
1308     }
1309 
1310     //------------------------------------------------------------------------------------------------------------------
1311     void TableControl_Impl::impl_ni_positionChildWindows( Rectangle const & i_dataCellPlayground,
1312         bool const i_verticalScrollbar, bool const i_horizontalScrollbar )
1313     {
1314         long const nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
1315 
1316         // create or destroy the vertical scrollbar, as needed
1317         lcl_updateScrollbar(
1318             m_rAntiImpl,
1319             m_pVScroll,
1320             i_verticalScrollbar,
1321             lcl_getRowsFittingInto( i_dataCellPlayground.GetHeight(), m_nRowHeightPixel ),
1322                                                                     // visible units
1323             m_nTopRow,                                              // current position
1324             1,                                                      // line size
1325             m_nRowCount,                                            // range
1326             false,                                                  // vertical
1327             LINK( this, TableControl_Impl, OnScroll )               // scroll handler
1328         );
1329 
1330         // position it
1331         if ( m_pVScroll )
1332         {
1333             Rectangle aScrollbarArea(
1334                 Point( i_dataCellPlayground.Right() + 1, 0 ),
1335                 Size( nScrollbarMetrics, i_dataCellPlayground.Bottom() + 1 )
1336             );
1337             m_pVScroll->SetPosSizePixel(
1338                 aScrollbarArea.TopLeft(), aScrollbarArea.GetSize() );
1339         }
1340 
1341         // create or destroy the horizontal scrollbar, as needed
1342         lcl_updateScrollbar(
1343             m_rAntiImpl,
1344             m_pHScroll,
1345             i_horizontalScrollbar,
1346             lcl_getColumnsVisibleWithin( i_dataCellPlayground, m_nLeftColumn, *this, false ),
1347                                                                     // visible units
1348             m_nLeftColumn,                                          // current position
1349             1,                                                      // line size
1350             m_nColumnCount,                                         // range
1351             true,                                                   // horizontal
1352             LINK( this, TableControl_Impl, OnScroll )               // scroll handler
1353         );
1354 
1355         // position it
1356         if ( m_pHScroll )
1357         {
1358             TableSize const nVisibleUnits = lcl_getColumnsVisibleWithin( i_dataCellPlayground, m_nLeftColumn, *this, false );
1359             TableMetrics const nRange = m_nColumnCount;
1360             if( m_nLeftColumn + nVisibleUnits == nRange - 1 )
1361             {
1362                 if ( m_aColumnWidths[ nRange - 1 ].getStart() - m_aColumnWidths[ m_nLeftColumn ].getEnd() + m_aColumnWidths[ nRange-1 ].getWidth() > i_dataCellPlayground.GetWidth() )
1363                 {
1364                     m_pHScroll->SetVisibleSize( nVisibleUnits -1 );
1365                     m_pHScroll->SetPageSize( nVisibleUnits - 1 );
1366                 }
1367             }
1368             Rectangle aScrollbarArea(
1369                 Point( 0, i_dataCellPlayground.Bottom() + 1 ),
1370                 Size( i_dataCellPlayground.Right() + 1, nScrollbarMetrics )
1371             );
1372             m_pHScroll->SetPosSizePixel(
1373                 aScrollbarArea.TopLeft(), aScrollbarArea.GetSize() );
1374         }
1375 
1376         // the corner window connecting the two scrollbars in the lower right corner
1377         bool bHaveScrollCorner = NULL != m_pScrollCorner;
1378         bool bNeedScrollCorner = ( NULL != m_pHScroll ) && ( NULL != m_pVScroll );
1379         if ( bHaveScrollCorner && !bNeedScrollCorner )
1380         {
1381             DELETEZ( m_pScrollCorner );
1382         }
1383         else if ( !bHaveScrollCorner && bNeedScrollCorner )
1384         {
1385             m_pScrollCorner = new ScrollBarBox( &m_rAntiImpl );
1386             m_pScrollCorner->SetSizePixel( Size( nScrollbarMetrics, nScrollbarMetrics ) );
1387             m_pScrollCorner->SetPosPixel( Point( i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1 ) );
1388             m_pScrollCorner->Show();
1389         }
1390         else if(bHaveScrollCorner && bNeedScrollCorner)
1391         {
1392             m_pScrollCorner->SetPosPixel( Point( i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1 ) );
1393             m_pScrollCorner->Show();
1394         }
1395 
1396         // resize the data window
1397         m_pDataWindow->SetSizePixel( Size(
1398             i_dataCellPlayground.GetWidth() + m_nRowHeaderWidthPixel,
1399             i_dataCellPlayground.GetHeight() + m_nColHeaderHeightPixel
1400         ) );
1401     }
1402 
1403     //------------------------------------------------------------------------------------------------------------------
1404     void TableControl_Impl::onResize()
1405     {
1406         DBG_CHECK_ME();
1407 
1408         impl_ni_relayout();
1409         checkCursorPosition();
1410     }
1411 
1412     //------------------------------------------------------------------------------------------------------------------
1413     void TableControl_Impl::doPaintContent( const Rectangle& _rUpdateRect )
1414     {
1415         DBG_CHECK_ME();
1416 
1417         if ( !getModel() )
1418             return;
1419         PTableRenderer pRenderer = getModel()->getRenderer();
1420         DBG_ASSERT( !!pRenderer, "TableDataWindow::doPaintContent: invalid renderer!" );
1421         if ( !pRenderer )
1422             return;
1423 
1424         // our current style settings, to be passed to the renderer
1425         const StyleSettings& rStyle = m_rAntiImpl.GetSettings().GetStyleSettings();
1426         m_nRowCount = m_pModel->getRowCount();
1427         // the area occupied by all (at least partially) visible cells, including
1428         // headers
1429         Rectangle const aAllCellsWithHeaders( impl_getAllVisibleCellsArea() );
1430 
1431         // ............................
1432         // draw the header column area
1433         if ( m_pModel->hasColumnHeaders() )
1434         {
1435             TableRowGeometry const aHeaderRow( *this, Rectangle( Point( 0, 0 ),
1436                 aAllCellsWithHeaders.BottomRight() ), ROW_COL_HEADERS );
1437             Rectangle const aColRect(aHeaderRow.getRect());
1438             pRenderer->PaintHeaderArea(
1439                 *m_pDataWindow, aColRect, true, false, rStyle
1440             );
1441             // Note that strictly, aHeaderRow.getRect() also contains the intersection between column
1442             // and row header area. However, below we go to paint this intersection, again,
1443             // so this hopefully doesn't hurt if we already paint it here.
1444 
1445             for ( TableCellGeometry aCell( aHeaderRow, m_nLeftColumn );
1446                   aCell.isValid();
1447                   aCell.moveRight()
1448                 )
1449             {
1450                 if ( _rUpdateRect.GetIntersection( aCell.getRect() ).IsEmpty() )
1451                     continue;
1452 
1453                 bool isActiveColumn = ( aCell.getColumn() == getCurrentColumn() );
1454                 bool isSelectedColumn = false;
1455                 pRenderer->PaintColumnHeader( aCell.getColumn(), isActiveColumn, isSelectedColumn,
1456                     *m_pDataWindow, aCell.getRect(), rStyle );
1457             }
1458         }
1459         // the area occupied by the row header, if any
1460         Rectangle aRowHeaderArea;
1461         if ( m_pModel->hasRowHeaders() )
1462         {
1463             aRowHeaderArea = aAllCellsWithHeaders;
1464             aRowHeaderArea.Right() = m_nRowHeaderWidthPixel - 1;
1465 
1466             TableSize const nVisibleRows = impl_getVisibleRows( true );
1467             TableSize nActualRows = nVisibleRows;
1468             if ( m_nTopRow + nActualRows > m_nRowCount )
1469                 nActualRows = m_nRowCount - m_nTopRow;
1470             aRowHeaderArea.Bottom() = m_nColHeaderHeightPixel + m_nRowHeightPixel * nActualRows - 1;
1471 
1472             pRenderer->PaintHeaderArea( *m_pDataWindow, aRowHeaderArea, false, true, rStyle );
1473             // Note that strictly, aRowHeaderArea also contains the intersection between column
1474             // and row header area. However, below we go to paint this intersection, again,
1475             // so this hopefully doesn't hurt if we already paint it here.
1476 
1477             if ( m_pModel->hasColumnHeaders() )
1478             {
1479                 TableCellGeometry const aIntersection( *this, Rectangle( Point( 0, 0 ),
1480                     aAllCellsWithHeaders.BottomRight() ), COL_ROW_HEADERS, ROW_COL_HEADERS );
1481                 Rectangle const aInters( aIntersection.getRect() );
1482                 pRenderer->PaintHeaderArea(
1483                     *m_pDataWindow, aInters, true, true, rStyle
1484                 );
1485             }
1486         }
1487 
1488         // ............................
1489         // draw the table content row by row
1490 
1491         TableSize colCount = getModel()->getColumnCount();
1492 
1493         // paint all rows
1494         Rectangle const aAllDataCellsArea( impl_getAllVisibleDataCellArea() );
1495         for ( TableRowGeometry aRowIterator( *this, aAllCellsWithHeaders, getTopRow() );
1496               aRowIterator.isValid();
1497               aRowIterator.moveDown() )
1498         {
1499             if ( _rUpdateRect.GetIntersection( aRowIterator.getRect() ).IsEmpty() )
1500                 continue;
1501 
1502             bool const isControlFocused = m_rAntiImpl.HasControlFocus();
1503             bool const isSelectedRow = isRowSelected( aRowIterator.getRow() );
1504 
1505             Rectangle const aRect = aRowIterator.getRect().GetIntersection( aAllDataCellsArea );
1506 
1507             // give the redenderer a chance to prepare the row
1508             pRenderer->PrepareRow(
1509                 aRowIterator.getRow(), isControlFocused, isSelectedRow,
1510                 *m_pDataWindow, aRect, rStyle
1511             );
1512 
1513             // paint the row header
1514             if ( m_pModel->hasRowHeaders() )
1515             {
1516                 const Rectangle aCurrentRowHeader( aRowHeaderArea.GetIntersection( aRowIterator.getRect() ) );
1517                 pRenderer->PaintRowHeader( isControlFocused, isSelectedRow, *m_pDataWindow, aCurrentRowHeader,
1518                     rStyle );
1519             }
1520 
1521             if ( !colCount )
1522                 continue;
1523 
1524             // paint all cells in this row
1525             for ( TableCellGeometry aCell( aRowIterator, m_nLeftColumn );
1526                   aCell.isValid();
1527                   aCell.moveRight()
1528                 )
1529             {
1530                 bool isSelectedColumn = false;
1531                 pRenderer->PaintCell( aCell.getColumn(), isSelectedRow || isSelectedColumn, isControlFocused,
1532                                 *m_pDataWindow, aCell.getRect(), rStyle );
1533             }
1534         }
1535     }
1536     //------------------------------------------------------------------------------------------------------------------
1537     void TableControl_Impl::hideCursor()
1538     {
1539         DBG_CHECK_ME();
1540 
1541         if ( ++m_nCursorHidden == 1 )
1542             impl_ni_doSwitchCursor( false );
1543     }
1544 
1545     //------------------------------------------------------------------------------------------------------------------
1546     void TableControl_Impl::showCursor()
1547     {
1548         DBG_CHECK_ME();
1549 
1550         DBG_ASSERT( m_nCursorHidden > 0, "TableControl_Impl::showCursor: cursor not hidden!" );
1551         if ( --m_nCursorHidden == 0 )
1552             impl_ni_doSwitchCursor( true );
1553     }
1554 
1555     //------------------------------------------------------------------------------------------------------------------
1556     bool TableControl_Impl::dispatchAction( TableControlAction _eAction )
1557     {
1558         DBG_CHECK_ME();
1559 
1560         bool bSuccess = false;
1561         bool selectionChanged = false;
1562 
1563         switch ( _eAction )
1564         {
1565         case cursorDown:
1566         if ( m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION )
1567         {
1568             //if other rows already selected, deselect them
1569             if ( m_aSelectedRows.size()>0 )
1570             {
1571                 invalidateSelectedRows();
1572                 m_aSelectedRows.clear();
1573             }
1574             if ( m_nCurRow < m_nRowCount-1 )
1575             {
1576                 ++m_nCurRow;
1577                 m_aSelectedRows.push_back(m_nCurRow);
1578             }
1579             else
1580                 m_aSelectedRows.push_back(m_nCurRow);
1581             invalidateRow( m_nCurRow );
1582             ensureVisible(m_nCurColumn,m_nCurRow,false);
1583             selectionChanged = true;
1584             bSuccess = true;
1585         }
1586         else
1587         {
1588             if ( m_nCurRow < m_nRowCount - 1 )
1589                 bSuccess = goTo( m_nCurColumn, m_nCurRow + 1 );
1590         }
1591             break;
1592 
1593         case cursorUp:
1594         if(m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION)
1595         {
1596             if(m_aSelectedRows.size()>0)
1597             {
1598                 invalidateSelectedRows();
1599                 m_aSelectedRows.clear();
1600             }
1601             if(m_nCurRow>0)
1602             {
1603                 --m_nCurRow;
1604                 m_aSelectedRows.push_back(m_nCurRow);
1605                 invalidateRow( m_nCurRow );
1606             }
1607             else
1608             {
1609                 m_aSelectedRows.push_back(m_nCurRow);
1610                 invalidateRow( m_nCurRow );
1611             }
1612             ensureVisible(m_nCurColumn,m_nCurRow,false);
1613             selectionChanged = true;
1614             bSuccess = true;
1615         }
1616         else
1617         {
1618             if ( m_nCurRow > 0 )
1619                 bSuccess = goTo( m_nCurColumn, m_nCurRow - 1 );
1620         }
1621         break;
1622         case cursorLeft:
1623             if ( m_nCurColumn > 0 )
1624                 bSuccess = goTo( m_nCurColumn - 1, m_nCurRow );
1625             else
1626                 if ( ( m_nCurColumn == 0) && ( m_nCurRow > 0 ) )
1627                     bSuccess = goTo( m_nColumnCount - 1, m_nCurRow - 1 );
1628             break;
1629 
1630         case cursorRight:
1631             if ( m_nCurColumn < m_nColumnCount - 1 )
1632                 bSuccess = goTo( m_nCurColumn + 1, m_nCurRow );
1633             else
1634                 if ( ( m_nCurColumn == m_nColumnCount - 1 ) && ( m_nCurRow < m_nRowCount - 1 ) )
1635                     bSuccess = goTo( 0, m_nCurRow + 1 );
1636             break;
1637 
1638         case cursorToLineStart:
1639             bSuccess = goTo( 0, m_nCurRow );
1640             break;
1641 
1642         case cursorToLineEnd:
1643             bSuccess = goTo( m_nColumnCount - 1, m_nCurRow );
1644             break;
1645 
1646         case cursorToFirstLine:
1647             bSuccess = goTo( m_nCurColumn, 0 );
1648             break;
1649 
1650         case cursorToLastLine:
1651             bSuccess = goTo( m_nCurColumn, m_nRowCount - 1 );
1652             break;
1653 
1654         case cursorPageUp:
1655         {
1656             RowPos nNewRow = ::std::max( (RowPos)0, m_nCurRow - impl_getVisibleRows( false ) );
1657             bSuccess = goTo( m_nCurColumn, nNewRow );
1658         }
1659         break;
1660 
1661         case cursorPageDown:
1662         {
1663             RowPos nNewRow = ::std::min( m_nRowCount - 1, m_nCurRow + impl_getVisibleRows( false ) );
1664             bSuccess = goTo( m_nCurColumn, nNewRow );
1665         }
1666         break;
1667 
1668         case cursorTopLeft:
1669             bSuccess = goTo( 0, 0 );
1670             break;
1671 
1672         case cursorBottomRight:
1673             bSuccess = goTo( m_nColumnCount - 1, m_nRowCount - 1 );
1674             break;
1675 
1676         case cursorSelectRow:
1677         {
1678             if(m_pSelEngine->GetSelectionMode() == NO_SELECTION)
1679                 return bSuccess = false;
1680             //pos is the position of the current row in the vector of selected rows, if current row is selected
1681             int pos = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
1682             //if current row is selected, it should be deselected, when ALT+SPACE are pressed
1683             if(pos>-1)
1684             {
1685                 m_aSelectedRows.erase(m_aSelectedRows.begin()+pos);
1686                 if(m_aSelectedRows.empty() && m_nAnchor != -1)
1687                     m_nAnchor = -1;
1688             }
1689             //else select the row->put it in the vector
1690             else
1691                 m_aSelectedRows.push_back(m_nCurRow);
1692             invalidateRow( m_nCurRow );
1693             selectionChanged = true;
1694             bSuccess = true;
1695         }
1696             break;
1697         case cursorSelectRowUp:
1698         {
1699             if(m_pSelEngine->GetSelectionMode() == NO_SELECTION)
1700                 return bSuccess = false;
1701             else if(m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION)
1702             {
1703                 //if there are other selected rows, deselect them
1704                 return false;
1705             }
1706             else
1707             {
1708                 //there are other selected rows
1709                 if(m_aSelectedRows.size()>0)
1710                 {
1711                     //the anchor wasn't set -> a region is not selected, that's why clear all selection
1712                     //and select the current row
1713                     if(m_nAnchor==-1)
1714                     {
1715                         invalidateSelectedRows();
1716                         m_aSelectedRows.clear();
1717                         m_aSelectedRows.push_back(m_nCurRow);
1718                         invalidateRow( m_nCurRow );
1719                     }
1720                     else
1721                     {
1722                         //a region is already selected, prevRow is last selected row and the row above - nextRow - should be selected
1723                         int prevRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
1724                         int nextRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow-1);
1725                         if(prevRow>-1)
1726                         {
1727                             //if m_nCurRow isn't the upper one, can move up, otherwise not
1728                             if(m_nCurRow>0)
1729                                 m_nCurRow--;
1730                             else
1731                                 return bSuccess = true;
1732                             //if nextRow already selected, deselect it, otherwise select it
1733                             if(nextRow>-1 && m_aSelectedRows[nextRow] == m_nCurRow)
1734                             {
1735                                 m_aSelectedRows.erase(m_aSelectedRows.begin()+prevRow);
1736                                 invalidateRow( m_nCurRow + 1 );
1737                             }
1738                             else
1739                             {
1740                                 m_aSelectedRows.push_back(m_nCurRow);
1741                                 invalidateRow( m_nCurRow );
1742                             }
1743                         }
1744                         else
1745                         {
1746                             if(m_nCurRow>0)
1747                             {
1748                                 m_aSelectedRows.push_back(m_nCurRow);
1749                                 m_nCurRow--;
1750                                 m_aSelectedRows.push_back(m_nCurRow);
1751                                 invalidateSelectedRegion( m_nCurRow+1, m_nCurRow );
1752                             }
1753                         }
1754                     }
1755                 }
1756                 else
1757                 {
1758                     //if nothing is selected and the current row isn't the upper one
1759                     //select the current and one row above
1760                     //otherwise select only the upper row
1761                     if(m_nCurRow>0)
1762                     {
1763                         m_aSelectedRows.push_back(m_nCurRow);
1764                         m_nCurRow--;
1765                         m_aSelectedRows.push_back(m_nCurRow);
1766                         invalidateSelectedRegion( m_nCurRow+1, m_nCurRow );
1767                     }
1768                     else
1769                     {
1770                         m_aSelectedRows.push_back(m_nCurRow);
1771                         invalidateRow( m_nCurRow );
1772                     }
1773                 }
1774                 m_pSelEngine->SetAnchor(sal_True);
1775                 m_nAnchor = m_nCurRow;
1776                 ensureVisible(m_nCurColumn, m_nCurRow, false);
1777                 selectionChanged = true;
1778                 bSuccess = true;
1779             }
1780         }
1781         break;
1782         case cursorSelectRowDown:
1783         {
1784             if(m_pSelEngine->GetSelectionMode() == NO_SELECTION)
1785                 bSuccess = false;
1786             else if(m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION)
1787             {
1788                 bSuccess = false;
1789             }
1790             else
1791             {
1792                 if(m_aSelectedRows.size()>0)
1793                 {
1794                     //the anchor wasn't set -> a region is not selected, that's why clear all selection
1795                     //and select the current row
1796                     if(m_nAnchor==-1)
1797                     {
1798                         invalidateSelectedRows();
1799                         m_aSelectedRows.clear();
1800                         m_aSelectedRows.push_back(m_nCurRow);
1801                         invalidateRow( m_nCurRow );
1802                         }
1803                     else
1804                     {
1805                         //a region is already selected, prevRow is last selected row and the row beneath - nextRow - should be selected
1806                         int prevRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
1807                         int nextRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow+1);
1808                         if(prevRow>-1)
1809                         {
1810                             //if m_nCurRow isn't the last one, can move down, otherwise not
1811                             if(m_nCurRow<m_nRowCount-1)
1812                                 m_nCurRow++;
1813                             else
1814                                 return bSuccess = true;
1815                             //if next row already selected, deselect it, otherwise select it
1816                             if(nextRow>-1 && m_aSelectedRows[nextRow] == m_nCurRow)
1817                             {
1818                                 m_aSelectedRows.erase(m_aSelectedRows.begin()+prevRow);
1819                                 invalidateRow( m_nCurRow - 1 );
1820                             }
1821                             else
1822                             {
1823                                 m_aSelectedRows.push_back(m_nCurRow);
1824                                 invalidateRow( m_nCurRow );
1825                             }
1826                         }
1827                         else
1828                         {
1829                             if(m_nCurRow<m_nRowCount-1)
1830                             {
1831                                 m_aSelectedRows.push_back(m_nCurRow);
1832                                 m_nCurRow++;
1833                                 m_aSelectedRows.push_back(m_nCurRow);
1834                                 invalidateSelectedRegion( m_nCurRow-1, m_nCurRow );
1835                             }
1836                         }
1837                     }
1838                 }
1839                 else
1840                 {
1841                     //there wasn't any selection, select current and row beneath, otherwise only row beneath
1842                     if(m_nCurRow<m_nRowCount-1)
1843                     {
1844                         m_aSelectedRows.push_back(m_nCurRow);
1845                         m_nCurRow++;
1846                         m_aSelectedRows.push_back(m_nCurRow);
1847                         invalidateSelectedRegion( m_nCurRow-1, m_nCurRow );
1848                     }
1849                     else
1850                     {
1851                         m_aSelectedRows.push_back(m_nCurRow);
1852                         invalidateRow( m_nCurRow );
1853                     }
1854                 }
1855                 m_pSelEngine->SetAnchor(sal_True);
1856                 m_nAnchor = m_nCurRow;
1857                 ensureVisible(m_nCurColumn, m_nCurRow, false);
1858                 selectionChanged = true;
1859                 bSuccess = true;
1860             }
1861         }
1862         break;
1863 
1864         case cursorSelectRowAreaTop:
1865         {
1866             if(m_pSelEngine->GetSelectionMode() == NO_SELECTION)
1867                 bSuccess = false;
1868             else if(m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION)
1869                 bSuccess = false;
1870             else
1871             {
1872                 //select the region between the current and the upper row
1873                 RowPos iter = m_nCurRow;
1874                 invalidateSelectedRegion( m_nCurRow, 0 );
1875                 //put the rows in vector
1876                 while(iter>=0)
1877                 {
1878                     if ( !isRowSelected( iter ) )
1879                         m_aSelectedRows.push_back(iter);
1880                     --iter;
1881                 }
1882                 m_nCurRow = 0;
1883                 m_nAnchor = m_nCurRow;
1884                 m_pSelEngine->SetAnchor(sal_True);
1885                 ensureVisible(m_nCurColumn, 0, false);
1886                 selectionChanged = true;
1887                 bSuccess = true;
1888             }
1889         }
1890         break;
1891 
1892         case cursorSelectRowAreaBottom:
1893         {
1894             if(m_pSelEngine->GetSelectionMode() == NO_SELECTION)
1895                 return bSuccess = false;
1896             else if(m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION)
1897                 return bSuccess = false;
1898             //select the region between the current and the last row
1899             RowPos iter = m_nCurRow;
1900             invalidateSelectedRegion( m_nCurRow, m_nRowCount-1 );
1901             //put the rows in the vector
1902             while(iter<=m_nRowCount)
1903             {
1904                 if ( !isRowSelected( iter ) )
1905                     m_aSelectedRows.push_back(iter);
1906                 ++iter;
1907             }
1908             m_nCurRow = m_nRowCount-1;
1909             m_nAnchor = m_nCurRow;
1910             m_pSelEngine->SetAnchor(sal_True);
1911             ensureVisible(m_nCurColumn, m_nRowCount-1, false);
1912             selectionChanged = true;
1913             bSuccess = true;
1914         }
1915         break;
1916         default:
1917             DBG_ERROR( "TableControl_Impl::dispatchAction: unsupported action!" );
1918             break;
1919         }
1920 
1921         if ( bSuccess && selectionChanged )
1922         {
1923             m_rAntiImpl.Select();
1924         }
1925 
1926         return bSuccess;
1927     }
1928 
1929     //------------------------------------------------------------------------------------------------------------------
1930     void TableControl_Impl::impl_ni_doSwitchCursor( bool _bShow )
1931     {
1932         PTableRenderer pRenderer = !!m_pModel ? m_pModel->getRenderer() : PTableRenderer();
1933         if ( !!pRenderer )
1934         {
1935             Rectangle aCellRect;
1936             impl_getCellRect( m_nCurColumn, m_nCurRow, aCellRect );
1937             if ( _bShow )
1938                 pRenderer->ShowCellCursor( *m_pDataWindow, aCellRect );
1939             else
1940                 pRenderer->HideCellCursor( *m_pDataWindow, aCellRect );
1941         }
1942     }
1943 
1944     //------------------------------------------------------------------------------------------------------------------
1945     void TableControl_Impl::impl_getCellRect( ColPos _nColumn, RowPos _nRow, Rectangle& _rCellRect ) const
1946     {
1947         DBG_CHECK_ME();
1948 
1949         if  (   !m_pModel
1950             ||  ( COL_INVALID == _nColumn )
1951             ||  ( ROW_INVALID == _nRow )
1952             )
1953         {
1954             _rCellRect.SetEmpty();
1955             return;
1956         }
1957 
1958         TableCellGeometry aCell( *this, impl_getAllVisibleCellsArea(), _nColumn, _nRow );
1959         _rCellRect = aCell.getRect();
1960     }
1961 
1962     //------------------------------------------------------------------------------------------------------------------
1963     RowPos TableControl_Impl::getRowAtPoint( const Point& rPoint ) const
1964     {
1965         DBG_CHECK_ME();
1966         return impl_getRowForAbscissa( rPoint.Y() );
1967     }
1968 
1969     //------------------------------------------------------------------------------------------------------------------
1970     ColPos TableControl_Impl::getColAtPoint( const Point& rPoint ) const
1971     {
1972         DBG_CHECK_ME();
1973         return impl_getColumnForOrdinate( rPoint.X() );
1974     }
1975 
1976     //------------------------------------------------------------------------------------------------------------------
1977     TableCell TableControl_Impl::hitTest( Point const & i_point ) const
1978     {
1979         TableCell aCell( getColAtPoint( i_point ), getRowAtPoint( i_point ) );
1980         if ( aCell.nColumn > COL_ROW_HEADERS )
1981         {
1982             PColumnModel const pColumn = m_pModel->getColumnModel( aCell.nColumn );
1983             MutableColumnMetrics const & rColInfo( m_aColumnWidths[ aCell.nColumn ] );
1984             if  (   ( rColInfo.getEnd() - 3 <= i_point.X() )
1985                 &&  ( rColInfo.getEnd() >= i_point.X() )
1986                 &&  pColumn->isResizable()
1987                 )
1988             {
1989                 aCell.eArea = ColumnDivider;
1990             }
1991         }
1992         return aCell;
1993     }
1994 
1995     //------------------------------------------------------------------------------------------------------------------
1996     ColumnMetrics TableControl_Impl::getColumnMetrics( ColPos const i_column ) const
1997     {
1998         DBG_CHECK_ME();
1999 
2000         ENSURE_OR_RETURN( ( i_column >= 0 ) && ( i_column < m_pModel->getColumnCount() ),
2001             "TableControl_Impl::getColumnMetrics: illegal column index!", ColumnMetrics() );
2002         return (ColumnMetrics const &)m_aColumnWidths[ i_column ];
2003     }
2004 
2005     //------------------------------------------------------------------------------------------------------------------
2006     PTableModel TableControl_Impl::getModel() const
2007     {
2008         return m_pModel;
2009     }
2010 
2011     //------------------------------------------------------------------------------------------------------------------
2012     RowPos TableControl_Impl::getCurrentColumn() const
2013     {
2014         return m_nCurColumn;
2015     }
2016 
2017     //------------------------------------------------------------------------------------------------------------------
2018     RowPos TableControl_Impl::getCurrentRow() const
2019     {
2020         return m_nCurRow;
2021     }
2022 
2023     //------------------------------------------------------------------------------------------------------------------
2024     ::Size TableControl_Impl::getTableSizePixel() const
2025     {
2026         return m_pDataWindow->GetOutputSizePixel();
2027     }
2028 
2029     //------------------------------------------------------------------------------------------------------------------
2030     void TableControl_Impl::setPointer( Pointer const & i_pointer )
2031     {
2032         DBG_CHECK_ME();
2033         m_pDataWindow->SetPointer( i_pointer );
2034     }
2035 
2036     //------------------------------------------------------------------------------------------------------------------
2037     void TableControl_Impl::captureMouse()
2038     {
2039         m_pDataWindow->CaptureMouse();
2040     }
2041 
2042     //------------------------------------------------------------------------------------------------------------------
2043     void TableControl_Impl::releaseMouse()
2044     {
2045         m_pDataWindow->ReleaseMouse();
2046     }
2047 
2048     //------------------------------------------------------------------------------------------------------------------
2049     void TableControl_Impl::invalidate( TableArea const i_what )
2050     {
2051         switch ( i_what )
2052         {
2053         case TableAreaColumnHeaders:
2054             m_pDataWindow->Invalidate( calcHeaderRect( true ) );
2055             break;
2056 
2057         case TableAreaRowHeaders:
2058             m_pDataWindow->Invalidate( calcHeaderRect( false ) );
2059             break;
2060 
2061         case TableAreaDataArea:
2062             m_pDataWindow->Invalidate( impl_getAllVisibleDataCellArea() );
2063             break;
2064 
2065         case TableAreaAll:
2066             m_pDataWindow->Invalidate();
2067             break;
2068         }
2069     }
2070 
2071     //------------------------------------------------------------------------------------------------------------------
2072     long TableControl_Impl::pixelWidthToAppFont( long const i_pixels ) const
2073     {
2074         return m_pDataWindow->PixelToLogic( Size( i_pixels, 0 ), MAP_APPFONT ).Width();
2075     }
2076 
2077     //------------------------------------------------------------------------------------------------------------------
2078     long TableControl_Impl::appFontWidthToPixel( long const i_appFontUnits ) const
2079     {
2080         return m_pDataWindow->LogicToPixel( Size( i_appFontUnits, 0 ), MAP_APPFONT ).Width();
2081     }
2082 
2083     //------------------------------------------------------------------------------------------------------------------
2084     void TableControl_Impl::hideTracking()
2085     {
2086         m_pDataWindow->HideTracking();
2087     }
2088 
2089     //------------------------------------------------------------------------------------------------------------------
2090     void TableControl_Impl::showTracking( Rectangle const & i_location, sal_uInt16 const i_flags )
2091     {
2092         m_pDataWindow->ShowTracking( i_location, i_flags );
2093     }
2094 
2095     //------------------------------------------------------------------------------------------------------------------
2096     bool TableControl_Impl::activateCell( ColPos const i_col, RowPos const i_row )
2097     {
2098         DBG_CHECK_ME();
2099         return goTo( i_col, i_row );
2100     }
2101 
2102     //------------------------------------------------------------------------------------------------------------------
2103     void TableControl_Impl::invalidateSelectedRegion( RowPos _nPrevRow, RowPos _nCurRow )
2104     {
2105         DBG_CHECK_ME();
2106         // get the visible area of the table control and set the Left and right border of the region to be repainted
2107         Rectangle const aAllCells( impl_getAllVisibleCellsArea() );
2108 
2109         Rectangle aInvalidateRect;
2110         aInvalidateRect.Left() = aAllCells.Left();
2111         aInvalidateRect.Right() = aAllCells.Right();
2112         // if only one row is selected
2113         if ( _nPrevRow == _nCurRow )
2114         {
2115             Rectangle aCellRect;
2116             impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
2117             aInvalidateRect.Top() = aCellRect.Top();
2118             aInvalidateRect.Bottom() = aCellRect.Bottom();
2119         }
2120         //if the region is above the current row
2121         else if(_nPrevRow < _nCurRow )
2122         {
2123             Rectangle aCellRect;
2124             impl_getCellRect( m_nCurColumn, _nPrevRow, aCellRect );
2125             aInvalidateRect.Top() = aCellRect.Top();
2126             impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
2127             aInvalidateRect.Bottom() = aCellRect.Bottom();
2128         }
2129         //if the region is beneath the current row
2130         else
2131         {
2132             Rectangle aCellRect;
2133             impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
2134             aInvalidateRect.Top() = aCellRect.Top();
2135             impl_getCellRect( m_nCurColumn, _nPrevRow, aCellRect );
2136             aInvalidateRect.Bottom() = aCellRect.Bottom();
2137         }
2138         m_pDataWindow->Invalidate( aInvalidateRect );
2139     }
2140 
2141     //------------------------------------------------------------------------------------------------------------------
2142     void TableControl_Impl::invalidateSelectedRows()
2143     {
2144         for (   ::std::vector< RowPos >::iterator selRow = m_aSelectedRows.begin();
2145                 selRow != m_aSelectedRows.end();
2146                 ++selRow
2147             )
2148         {
2149             invalidateRow( *selRow );
2150         }
2151     }
2152 
2153     //------------------------------------------------------------------------------------------------------------------
2154     void TableControl_Impl::invalidateRowRange( RowPos const i_firstRow, RowPos const i_lastRow )
2155     {
2156         RowPos const firstRow = i_firstRow < m_nTopRow ? m_nTopRow : i_firstRow;
2157         RowPos const lastVisibleRow = m_nTopRow + impl_getVisibleRows( true ) - 1;
2158         RowPos const lastRow = ( ( i_lastRow == ROW_INVALID ) || ( i_lastRow > lastVisibleRow ) ) ? lastVisibleRow : i_lastRow;
2159 
2160         Rectangle aInvalidateRect;
2161 
2162         Rectangle const aVisibleCellsArea( impl_getAllVisibleCellsArea() );
2163         TableRowGeometry aRow( *this, aVisibleCellsArea, firstRow, true );
2164         while ( aRow.isValid() && ( aRow.getRow() <= lastRow ) )
2165         {
2166             aInvalidateRect.Union( aRow.getRect() );
2167             aRow.moveDown();
2168         }
2169 
2170         if ( i_lastRow == ROW_INVALID )
2171             aInvalidateRect.Bottom() = m_pDataWindow->GetOutputSizePixel().Height();
2172 
2173         m_pDataWindow->Invalidate( aInvalidateRect );
2174     }
2175 
2176     //------------------------------------------------------------------------------
2177     void TableControl_Impl::checkCursorPosition()
2178     {
2179         DBG_CHECK_ME();
2180 
2181         TableSize nVisibleRows = impl_getVisibleRows(true);
2182         TableSize nVisibleCols = impl_getVisibleColumns(true);
2183         if  (   ( m_nTopRow + nVisibleRows > m_nRowCount )
2184             &&  ( m_nRowCount >= nVisibleRows )
2185             )
2186         {
2187             --m_nTopRow;
2188         }
2189         else
2190         {
2191             m_nTopRow = 0;
2192         }
2193 
2194         if  (   ( m_nLeftColumn + nVisibleCols > m_nColumnCount )
2195             &&  ( m_nColumnCount >= nVisibleCols )
2196             )
2197         {
2198             --m_nLeftColumn;
2199         }
2200         else
2201         {
2202             m_nLeftColumn = 0;
2203         }
2204 
2205         m_pDataWindow->Invalidate();
2206     }
2207 
2208     //--------------------------------------------------------------------
2209     TableSize TableControl_Impl::impl_getVisibleRows( bool _bAcceptPartialRow ) const
2210     {
2211         DBG_CHECK_ME();
2212 
2213         DBG_ASSERT( m_pDataWindow, "TableControl_Impl::impl_getVisibleRows: no data window!" );
2214 
2215         return lcl_getRowsFittingInto(
2216             m_pDataWindow->GetOutputSizePixel().Height() - m_nColHeaderHeightPixel,
2217             m_nRowHeightPixel,
2218             _bAcceptPartialRow
2219         );
2220     }
2221 
2222     //--------------------------------------------------------------------
2223     TableSize TableControl_Impl::impl_getVisibleColumns( bool _bAcceptPartialCol ) const
2224     {
2225         DBG_CHECK_ME();
2226 
2227         DBG_ASSERT( m_pDataWindow, "TableControl_Impl::impl_getVisibleColumns: no data window!" );
2228 
2229         return lcl_getColumnsVisibleWithin(
2230             Rectangle( Point( 0, 0 ), m_pDataWindow->GetOutputSizePixel() ),
2231             m_nLeftColumn,
2232             *this,
2233             _bAcceptPartialCol
2234         );
2235     }
2236 
2237     //--------------------------------------------------------------------
2238     bool TableControl_Impl::goTo( ColPos _nColumn, RowPos _nRow )
2239     {
2240         DBG_CHECK_ME();
2241 
2242         // TODO: give veto listeners a chance
2243 
2244         if  (  ( _nColumn < 0 ) || ( _nColumn >= m_nColumnCount )
2245             || ( _nRow < 0 ) || ( _nRow >= m_nRowCount )
2246             )
2247         {
2248             OSL_ENSURE( false, "TableControl_Impl::goTo: invalid row or column index!" );
2249             return false;
2250         }
2251 
2252         SuppressCursor aHideCursor( *this );
2253         m_nCurColumn = _nColumn;
2254         m_nCurRow = _nRow;
2255 
2256         // ensure that the new cell is visible
2257         ensureVisible( m_nCurColumn, m_nCurRow, false );
2258         return true;
2259     }
2260 
2261     //--------------------------------------------------------------------
2262     void TableControl_Impl::ensureVisible( ColPos _nColumn, RowPos _nRow, bool _bAcceptPartialVisibility )
2263     {
2264         DBG_CHECK_ME();
2265         DBG_ASSERT( ( _nColumn >= 0 ) && ( _nColumn < m_nColumnCount )
2266                  && ( _nRow >= 0 ) && ( _nRow < m_nRowCount ),
2267                  "TableControl_Impl::ensureVisible: invalid coordinates!" );
2268 
2269         SuppressCursor aHideCursor( *this );
2270 
2271         if ( _nColumn < m_nLeftColumn )
2272             impl_scrollColumns( _nColumn - m_nLeftColumn );
2273         else
2274         {
2275             TableSize nVisibleColumns = impl_getVisibleColumns( _bAcceptPartialVisibility );
2276             if ( _nColumn > m_nLeftColumn + nVisibleColumns - 1 )
2277             {
2278                 impl_scrollColumns( _nColumn - ( m_nLeftColumn + nVisibleColumns - 1 ) );
2279                 // TODO: since not all columns have the same width, this might in theory result
2280                 // in the column still not being visible.
2281             }
2282         }
2283 
2284         if ( _nRow < m_nTopRow )
2285             impl_scrollRows( _nRow - m_nTopRow );
2286         else
2287         {
2288             TableSize nVisibleRows = impl_getVisibleRows( _bAcceptPartialVisibility );
2289             if ( _nRow > m_nTopRow + nVisibleRows - 1 )
2290                 impl_scrollRows( _nRow - ( m_nTopRow + nVisibleRows - 1 ) );
2291         }
2292     }
2293 
2294     //--------------------------------------------------------------------
2295     ::rtl::OUString TableControl_Impl::getCellContentAsString( RowPos const i_row, ColPos const i_col )
2296     {
2297         Any aCellValue;
2298         m_pModel->getCellContent( i_col, i_row, aCellValue );
2299 
2300         ::rtl::OUString sCellStringContent;
2301         m_pModel->getRenderer()->GetFormattedCellString( aCellValue, i_col, i_row, sCellStringContent );
2302 
2303         return sCellStringContent;
2304     }
2305 
2306     //--------------------------------------------------------------------
2307     TableSize TableControl_Impl::impl_ni_ScrollRows( TableSize _nRowDelta )
2308     {
2309         // compute new top row
2310         RowPos nNewTopRow =
2311             ::std::max(
2312                 ::std::min( (RowPos)( m_nTopRow + _nRowDelta ), (RowPos)( m_nRowCount - 1 ) ),
2313                 (RowPos)0
2314             );
2315 
2316         RowPos nOldTopRow = m_nTopRow;
2317         m_nTopRow = nNewTopRow;
2318 
2319         // if updates are enabled currently, scroll the viewport
2320         if ( m_nTopRow != nOldTopRow )
2321         {
2322             DBG_SUSPEND_INV( INV_SCROLL_POSITION );
2323             SuppressCursor aHideCursor( *this );
2324             // TODO: call a onStartScroll at our listener (or better an own onStartScroll,
2325             // which hides the cursor and then calls the listener)
2326             // Same for onEndScroll
2327 
2328             // scroll the view port, if possible
2329             long nPixelDelta = m_nRowHeightPixel * ( m_nTopRow - nOldTopRow );
2330 
2331             Rectangle aDataArea( Point( 0, m_nColHeaderHeightPixel ), m_pDataWindow->GetOutputSizePixel() );
2332 
2333             if  (   m_pDataWindow->GetBackground().IsScrollable()
2334                 &&  abs( nPixelDelta ) < aDataArea.GetHeight()
2335                 )
2336             {
2337                 m_pDataWindow->Scroll( 0, (long)-nPixelDelta, aDataArea, SCROLL_CLIP | SCROLL_UPDATE | SCROLL_CHILDREN);
2338             }
2339             else
2340                 m_pDataWindow->Invalidate( INVALIDATE_UPDATE );
2341 
2342             // update the position at the vertical scrollbar
2343             if ( m_pVScroll != NULL )
2344                 m_pVScroll->SetThumbPos( m_nTopRow );
2345         }
2346 
2347         // The scroll bar availaility might change when we scrolled.
2348         // For instance, imagine a view with 10 rows, if which 5 fit into the window, numbered 1 to 10.
2349         // Now let
2350         // - the user scroll to row number 6, so the last 5 rows are visible
2351         // - somebody remove the last 4 rows
2352         // - the user scroll to row number 5 being the top row, so the last two rows are visible
2353         // - somebody remove row number 6
2354         // - the user scroll to row number 1
2355         // => in this case, the need for the scrollbar vanishes immediately.
2356         if ( m_nTopRow == 0 )
2357             m_rAntiImpl.PostUserEvent( LINK( this, TableControl_Impl, OnUpdateScrollbars ) );
2358 
2359         return (TableSize)( m_nTopRow - nOldTopRow );
2360     }
2361 
2362     //--------------------------------------------------------------------
2363     TableSize TableControl_Impl::impl_scrollRows( TableSize const i_rowDelta )
2364     {
2365         DBG_CHECK_ME();
2366         return impl_ni_ScrollRows( i_rowDelta );
2367     }
2368 
2369     //--------------------------------------------------------------------
2370     TableSize TableControl_Impl::impl_ni_ScrollColumns( TableSize _nColumnDelta )
2371     {
2372         // compute new left column
2373         const ColPos nNewLeftColumn =
2374             ::std::max(
2375                 ::std::min( (ColPos)( m_nLeftColumn + _nColumnDelta ), (ColPos)( m_nColumnCount - 1 ) ),
2376                 (ColPos)0
2377             );
2378 
2379         const ColPos nOldLeftColumn = m_nLeftColumn;
2380         m_nLeftColumn = nNewLeftColumn;
2381 
2382         // if updates are enabled currently, scroll the viewport
2383         if ( m_nLeftColumn != nOldLeftColumn )
2384         {
2385             DBG_SUSPEND_INV( INV_SCROLL_POSITION );
2386             SuppressCursor aHideCursor( *this );
2387             // TODO: call a onStartScroll at our listener (or better an own onStartScroll,
2388             // which hides the cursor and then calls the listener)
2389             // Same for onEndScroll
2390 
2391             // scroll the view port, if possible
2392             const Rectangle aDataArea( Point( m_nRowHeaderWidthPixel, 0 ), m_pDataWindow->GetOutputSizePixel() );
2393 
2394             long nPixelDelta =
2395                     m_aColumnWidths[ nOldLeftColumn ].getStart()
2396                 -   m_aColumnWidths[ m_nLeftColumn ].getStart();
2397 
2398             // update our column positions
2399             // Do this *before* scrolling, as SCROLL_UPDATE will trigger a paint, which already needs the correct
2400             // information in m_aColumnWidths
2401             for (   ColumnPositions::iterator colPos = m_aColumnWidths.begin();
2402                     colPos != m_aColumnWidths.end();
2403                     ++colPos
2404                  )
2405             {
2406                 colPos->move( nPixelDelta );
2407             }
2408 
2409             // scroll the window content (if supported and possible), or invalidate the complete window
2410             if  (   m_pDataWindow->GetBackground().IsScrollable()
2411                 &&  abs( nPixelDelta ) < aDataArea.GetWidth()
2412                 )
2413             {
2414                 m_pDataWindow->Scroll( nPixelDelta, 0, aDataArea, SCROLL_CLIP | SCROLL_UPDATE );
2415             }
2416             else
2417                 m_pDataWindow->Invalidate( INVALIDATE_UPDATE );
2418 
2419             // update the position at the horizontal scrollbar
2420             if ( m_pHScroll != NULL )
2421                 m_pHScroll->SetThumbPos( m_nLeftColumn );
2422         }
2423 
2424         // The scroll bar availaility might change when we scrolled. This is because we do not hide
2425         // the scrollbar when it is, in theory, unnecessary, but currently at a position > 0. In this case, it will
2426         // be auto-hidden when it's scrolled back to pos 0.
2427         if ( m_nLeftColumn == 0 )
2428             m_rAntiImpl.PostUserEvent( LINK( this, TableControl_Impl, OnUpdateScrollbars ) );
2429 
2430         return (TableSize)( m_nLeftColumn - nOldLeftColumn );
2431     }
2432 
2433     //------------------------------------------------------------------------------------------------------------------
2434     TableSize TableControl_Impl::impl_scrollColumns( TableSize const i_columnDelta )
2435     {
2436         DBG_CHECK_ME();
2437         return impl_ni_ScrollColumns( i_columnDelta );
2438     }
2439 
2440     //------------------------------------------------------------------------------------------------------------------
2441     SelectionEngine* TableControl_Impl::getSelEngine()
2442     {
2443         return m_pSelEngine;
2444     }
2445 
2446     //------------------------------------------------------------------------------------------------------------------
2447     ScrollBar* TableControl_Impl::getHorzScrollbar()
2448     {
2449         return m_pHScroll;
2450     }
2451 
2452     //------------------------------------------------------------------------------------------------------------------
2453     ScrollBar* TableControl_Impl::getVertScrollbar()
2454     {
2455         return m_pVScroll;
2456     }
2457 
2458     //------------------------------------------------------------------------------------------------------------------
2459     bool TableControl_Impl::isRowSelected( RowPos i_row ) const
2460     {
2461         return ::std::find( m_aSelectedRows.begin(), m_aSelectedRows.end(), i_row ) != m_aSelectedRows.end();
2462     }
2463 
2464     //------------------------------------------------------------------------------------------------------------------
2465     RowPos TableControl_Impl::getSelectedRowIndex( size_t const i_selectionIndex ) const
2466     {
2467         if ( i_selectionIndex < m_aSelectedRows.size() )
2468             return m_aSelectedRows[ i_selectionIndex ];
2469         return ROW_INVALID;
2470     }
2471 
2472     //------------------------------------------------------------------------------------------------------------------
2473     int TableControl_Impl::getRowSelectedNumber(const ::std::vector<RowPos>& selectedRows, RowPos current)
2474     {
2475         std::vector<RowPos>::const_iterator it = ::std::find(selectedRows.begin(),selectedRows.end(),current);
2476         if ( it != selectedRows.end() )
2477         {
2478             return it - selectedRows.begin();
2479         }
2480         return -1;
2481     }
2482 
2483     //--------------------------------------------------------------------
2484     ColPos TableControl_Impl::impl_getColumnForOrdinate( long const i_ordinate ) const
2485     {
2486         DBG_CHECK_ME();
2487 
2488         if ( ( m_aColumnWidths.empty() ) || ( i_ordinate < 0 ) )
2489             return COL_INVALID;
2490 
2491         if ( i_ordinate < m_nRowHeaderWidthPixel )
2492             return COL_ROW_HEADERS;
2493 
2494         ColumnPositions::const_iterator lowerBound = ::std::lower_bound(
2495             m_aColumnWidths.begin(),
2496             m_aColumnWidths.end(),
2497             i_ordinate + 1,
2498             ColumnInfoPositionLess()
2499         );
2500         if ( lowerBound == m_aColumnWidths.end() )
2501         {
2502             // point is *behind* the start of the last column ...
2503             if ( i_ordinate < m_aColumnWidths.rbegin()->getEnd() )
2504                 // ... but still before its end
2505                 return m_nColumnCount - 1;
2506             return COL_INVALID;
2507         }
2508         return lowerBound - m_aColumnWidths.begin();
2509     }
2510 
2511     //--------------------------------------------------------------------
2512     RowPos TableControl_Impl::impl_getRowForAbscissa( long const i_abscissa ) const
2513     {
2514         DBG_CHECK_ME();
2515 
2516         if ( i_abscissa < 0 )
2517             return ROW_INVALID;
2518 
2519         if ( i_abscissa < m_nColHeaderHeightPixel )
2520             return ROW_COL_HEADERS;
2521 
2522         long const abscissa = i_abscissa - m_nColHeaderHeightPixel;
2523         long const row = m_nTopRow + abscissa / m_nRowHeightPixel;
2524         return row < m_pModel->getRowCount() ? row : ROW_INVALID;
2525     }
2526 
2527     //--------------------------------------------------------------------
2528     bool TableControl_Impl::markRowAsDeselected( RowPos const i_rowIndex )
2529     {
2530         DBG_CHECK_ME();
2531 
2532         ::std::vector< RowPos >::iterator selPos = ::std::find( m_aSelectedRows.begin(), m_aSelectedRows.end(), i_rowIndex );
2533         if ( selPos == m_aSelectedRows.end() )
2534             return false;
2535 
2536         m_aSelectedRows.erase( selPos );
2537         return true;
2538     }
2539 
2540     //--------------------------------------------------------------------
2541     bool TableControl_Impl::markRowAsSelected( RowPos const i_rowIndex )
2542     {
2543         DBG_CHECK_ME();
2544 
2545         if ( isRowSelected( i_rowIndex ) )
2546             return false;
2547 
2548         SelectionMode const eSelMode = getSelEngine()->GetSelectionMode();
2549         switch ( eSelMode )
2550         {
2551         case SINGLE_SELECTION:
2552             if ( !m_aSelectedRows.empty() )
2553             {
2554                 OSL_ENSURE( m_aSelectedRows.size() == 1, "TableControl::markRowAsSelected: SingleSelection with more than one selected element?" );
2555                 m_aSelectedRows[0] = i_rowIndex;
2556                 break;
2557             }
2558             // fall through
2559 
2560         case MULTIPLE_SELECTION:
2561             m_aSelectedRows.push_back( i_rowIndex );
2562             break;
2563 
2564         default:
2565             OSL_ENSURE( false, "TableControl_Impl::markRowAsSelected: unsupported selection mode!" );
2566             return false;
2567         }
2568 
2569         return true;
2570     }
2571 
2572     //--------------------------------------------------------------------
2573     bool TableControl_Impl::markAllRowsAsDeselected()
2574     {
2575         if ( m_aSelectedRows.empty() )
2576             return false;
2577 
2578         m_aSelectedRows.clear();
2579         return true;
2580     }
2581 
2582     //--------------------------------------------------------------------
2583     bool TableControl_Impl::markAllRowsAsSelected()
2584     {
2585         DBG_CHECK_ME();
2586 
2587         SelectionMode const eSelMode = getSelEngine()->GetSelectionMode();
2588         ENSURE_OR_RETURN_FALSE( eSelMode == MULTIPLE_SELECTION, "TableControl_Impl::markAllRowsAsSelected: unsupported selection mode!" );
2589 
2590         if ( m_aSelectedRows.size() == size_t( m_pModel->getRowCount() ) )
2591         {
2592         #if OSL_DEBUG_LEVEL > 0
2593             for ( TableSize row = 0; row < m_pModel->getRowCount(); ++row )
2594             {
2595                 OSL_ENSURE( isRowSelected( row ), "TableControl_Impl::markAllRowsAsSelected: inconsistency in the selected rows!" );
2596             }
2597         #endif
2598             // already all rows marked as selected
2599             return false;
2600         }
2601 
2602         m_aSelectedRows.clear();
2603         for ( RowPos i=0; i < m_pModel->getRowCount(); ++i )
2604             m_aSelectedRows.push_back(i);
2605 
2606         return true;
2607     }
2608 
2609     //--------------------------------------------------------------------
2610     void TableControl_Impl::commitAccessibleEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue )
2611     {
2612         impl_commitAccessibleEvent( i_eventID, i_newValue, i_oldValue );
2613     }
2614 
2615     //--------------------------------------------------------------------
2616     void TableControl_Impl::commitCellEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue )
2617     {
2618         DBG_CHECK_ME();
2619         if ( impl_isAccessibleAlive() )
2620             m_pAccessibleTable->commitCellEvent( i_eventID, i_newValue, i_oldValue );
2621     }
2622 
2623     //--------------------------------------------------------------------
2624     void TableControl_Impl::commitTableEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue )
2625     {
2626         DBG_CHECK_ME();
2627         if ( impl_isAccessibleAlive() )
2628             m_pAccessibleTable->commitTableEvent( i_eventID, i_newValue, i_oldValue );
2629     }
2630 
2631     //--------------------------------------------------------------------
2632     Rectangle TableControl_Impl::calcHeaderRect(bool bColHeader)
2633     {
2634         Rectangle const aRectTableWithHeaders( impl_getAllVisibleCellsArea() );
2635         Size const aSizeTableWithHeaders( aRectTableWithHeaders.GetSize() );
2636         if ( bColHeader )
2637             return Rectangle( aRectTableWithHeaders.TopLeft(), Size( aSizeTableWithHeaders.Width(), m_nColHeaderHeightPixel ) );
2638         else
2639             return Rectangle( aRectTableWithHeaders.TopLeft(), Size( m_nRowHeaderWidthPixel, aSizeTableWithHeaders.Height() ) );
2640     }
2641 
2642     //--------------------------------------------------------------------
2643     Rectangle TableControl_Impl::calcHeaderCellRect( bool bColHeader, sal_Int32 nPos )
2644     {
2645         Rectangle const aHeaderRect = calcHeaderRect( bColHeader );
2646         TableCellGeometry const aGeometry(
2647             *this, aHeaderRect,
2648             bColHeader ? nPos : COL_ROW_HEADERS,
2649             bColHeader ? ROW_COL_HEADERS : nPos
2650         );
2651         return aGeometry.getRect();
2652     }
2653 
2654     //--------------------------------------------------------------------
2655     Rectangle TableControl_Impl::calcTableRect()
2656     {
2657         return impl_getAllVisibleDataCellArea();
2658     }
2659 
2660     //--------------------------------------------------------------------
2661     Rectangle TableControl_Impl::calcCellRect( sal_Int32 nRow, sal_Int32 nCol )
2662     {
2663         Rectangle aCellRect;
2664         impl_getCellRect( nRow, nCol, aCellRect );
2665         return aCellRect;
2666     }
2667 
2668     //--------------------------------------------------------------------
2669     IMPL_LINK( TableControl_Impl, OnUpdateScrollbars, void*, /**/ )
2670     {
2671         DBG_CHECK_ME();
2672         // TODO: can't we simply use lcl_updateScrollbar here, so the scrollbars ranges are updated, instead of
2673         // doing a complete re-layout?
2674         impl_ni_relayout();
2675         return 1L;
2676     }
2677 
2678     //--------------------------------------------------------------------
2679     IMPL_LINK( TableControl_Impl, OnScroll, ScrollBar*, _pScrollbar )
2680     {
2681         DBG_ASSERT( ( _pScrollbar == m_pVScroll ) || ( _pScrollbar == m_pHScroll ),
2682             "TableControl_Impl::OnScroll: where did this come from?" );
2683 
2684         if ( _pScrollbar == m_pVScroll )
2685             impl_ni_ScrollRows( _pScrollbar->GetDelta() );
2686         else
2687             impl_ni_ScrollColumns( _pScrollbar->GetDelta() );
2688 
2689         return 0L;
2690     }
2691 
2692     //------------------------------------------------------------------------------------------------------------------
2693     Reference< XAccessible > TableControl_Impl::getAccessible( Window& i_parentWindow )
2694     {
2695         DBG_TESTSOLARMUTEX();
2696         if ( m_pAccessibleTable == NULL )
2697         {
2698             Reference< XAccessible > const xAccParent = i_parentWindow.GetAccessible();
2699             if ( xAccParent.is() )
2700             {
2701                 m_pAccessibleTable = m_aFactoryAccess.getFactory().createAccessibleTableControl(
2702                     xAccParent, m_rAntiImpl
2703                 );
2704             }
2705         }
2706 
2707         Reference< XAccessible > xAccessible;
2708         if ( m_pAccessibleTable )
2709             xAccessible = m_pAccessibleTable->getMyself();
2710         return xAccessible;
2711     }
2712 
2713     //------------------------------------------------------------------------------------------------------------------
2714     void TableControl_Impl::disposeAccessible()
2715     {
2716         if ( m_pAccessibleTable )
2717             m_pAccessibleTable->dispose();
2718         m_pAccessibleTable = NULL;
2719     }
2720 
2721     //------------------------------------------------------------------------------------------------------------------
2722     bool TableControl_Impl::impl_isAccessibleAlive() const
2723     {
2724         DBG_CHECK_ME();
2725         return ( NULL != m_pAccessibleTable ) && m_pAccessibleTable->isAlive();
2726     }
2727 
2728     //------------------------------------------------------------------------------------------------------------------
2729     void TableControl_Impl::impl_commitAccessibleEvent( sal_Int16 const i_eventID, Any const & i_newValue, Any const & i_oldValue )
2730     {
2731         DBG_CHECK_ME();
2732         if ( impl_isAccessibleAlive() )
2733             m_pAccessibleTable->commitEvent( i_eventID, i_newValue, i_oldValue );
2734     }
2735 
2736     //==================================================================================================================
2737     //= TableFunctionSet
2738     //==================================================================================================================
2739     //------------------------------------------------------------------------------------------------------------------
2740     TableFunctionSet::TableFunctionSet(TableControl_Impl* _pTableControl)
2741         :m_pTableControl( _pTableControl)
2742         ,m_nCurrentRow( ROW_INVALID )
2743     {
2744     }
2745     //------------------------------------------------------------------------------------------------------------------
2746     TableFunctionSet::~TableFunctionSet()
2747     {
2748     }
2749     //------------------------------------------------------------------------------------------------------------------
2750     void TableFunctionSet::BeginDrag()
2751     {
2752     }
2753     //------------------------------------------------------------------------------------------------------------------
2754     void TableFunctionSet::CreateAnchor()
2755     {
2756         m_pTableControl->setAnchor( m_pTableControl->getCurRow() );
2757     }
2758 
2759     //------------------------------------------------------------------------------------------------------------------
2760     void TableFunctionSet::DestroyAnchor()
2761     {
2762         m_pTableControl->setAnchor( ROW_INVALID );
2763     }
2764 
2765     //------------------------------------------------------------------------------------------------------------------
2766     sal_Bool TableFunctionSet::SetCursorAtPoint(const Point& rPoint, sal_Bool bDontSelectAtCursor)
2767     {
2768         sal_Bool bHandled = sal_False;
2769         // newRow is the row which includes the point, getCurRow() is the last selected row, before the mouse click
2770         RowPos newRow = m_pTableControl->getRowAtPoint( rPoint );
2771         if ( newRow == ROW_COL_HEADERS )
2772             newRow = m_pTableControl->getTopRow();
2773 
2774         ColPos newCol = m_pTableControl->getColAtPoint( rPoint );
2775         if ( newCol == COL_ROW_HEADERS )
2776             newCol = m_pTableControl->getLeftColumn();
2777 
2778         if ( ( newRow == ROW_INVALID ) || ( newCol == COL_INVALID ) )
2779             return sal_False;
2780 
2781         if ( bDontSelectAtCursor )
2782         {
2783             if ( m_pTableControl->getSelectedRowCount() > 1 )
2784                 m_pTableControl->getSelEngine()->AddAlways(sal_True);
2785             bHandled = sal_True;
2786         }
2787         else if ( m_pTableControl->getAnchor() == m_pTableControl->getCurRow() )
2788         {
2789             //selecting region,
2790             int diff = m_pTableControl->getCurRow() - newRow;
2791             //selected region lies above the last selection
2792             if( diff >= 0)
2793             {
2794                 //put selected rows in vector
2795                 while ( m_pTableControl->getAnchor() >= newRow )
2796                 {
2797                     m_pTableControl->markRowAsSelected( m_pTableControl->getAnchor() );
2798                     m_pTableControl->setAnchor( m_pTableControl->getAnchor() - 1 );
2799                     diff--;
2800                 }
2801                 m_pTableControl->setAnchor( m_pTableControl->getAnchor() + 1 );
2802             }
2803             //selected region lies beneath the last selected row
2804             else
2805             {
2806                 while ( m_pTableControl->getAnchor() <= newRow )
2807                 {
2808                     m_pTableControl->markRowAsSelected( m_pTableControl->getAnchor() );
2809                     m_pTableControl->setAnchor( m_pTableControl->getAnchor() + 1 );
2810                     diff++;
2811                 }
2812                 m_pTableControl->setAnchor( m_pTableControl->getAnchor() - 1 );
2813             }
2814             m_pTableControl->invalidateSelectedRegion( m_pTableControl->getCurRow(), newRow );
2815             bHandled = sal_True;
2816         }
2817         //no region selected
2818         else
2819         {
2820             if ( !m_pTableControl->hasRowSelection() )
2821                 m_pTableControl->markRowAsSelected( newRow );
2822             else
2823             {
2824                 if ( m_pTableControl->getSelEngine()->GetSelectionMode() == SINGLE_SELECTION )
2825                 {
2826                     DeselectAll();
2827                     m_pTableControl->markRowAsSelected( newRow );
2828                 }
2829                 else
2830                 {
2831                     m_pTableControl->markRowAsSelected( newRow );
2832                 }
2833             }
2834             if ( m_pTableControl->getSelectedRowCount() > 1 && m_pTableControl->getSelEngine()->GetSelectionMode() != SINGLE_SELECTION )
2835                 m_pTableControl->getSelEngine()->AddAlways(sal_True);
2836 
2837             m_pTableControl->invalidateRow( newRow );
2838             bHandled = sal_True;
2839         }
2840         m_pTableControl->goTo( newCol, newRow );
2841         return bHandled;
2842     }
2843     //------------------------------------------------------------------------------------------------------------------
2844     sal_Bool TableFunctionSet::IsSelectionAtPoint( const Point& rPoint )
2845     {
2846         m_pTableControl->getSelEngine()->AddAlways(sal_False);
2847         if ( !m_pTableControl->hasRowSelection() )
2848             return sal_False;
2849         else
2850         {
2851             RowPos curRow = m_pTableControl->getRowAtPoint( rPoint );
2852             m_pTableControl->setAnchor( ROW_INVALID );
2853             bool selected = m_pTableControl->isRowSelected( curRow );
2854             m_nCurrentRow = curRow;
2855             return selected;
2856         }
2857     }
2858     //------------------------------------------------------------------------------------------------------------------
2859     void TableFunctionSet::DeselectAtPoint( const Point& rPoint )
2860     {
2861         (void)rPoint;
2862         m_pTableControl->invalidateRow( m_nCurrentRow );
2863         m_pTableControl->markRowAsDeselected( m_nCurrentRow );
2864     }
2865 
2866     //------------------------------------------------------------------------------------------------------------------
2867     void TableFunctionSet::DeselectAll()
2868     {
2869         if ( m_pTableControl->hasRowSelection() )
2870         {
2871             for ( size_t i=0; i<m_pTableControl->getSelectedRowCount(); ++i )
2872             {
2873                 RowPos const rowIndex = m_pTableControl->getSelectedRowIndex(i);
2874                 m_pTableControl->invalidateRow( rowIndex );
2875             }
2876 
2877             m_pTableControl->markAllRowsAsDeselected();
2878         }
2879     }
2880 
2881 //......................................................................................................................
2882 } } // namespace svt::table
2883 //......................................................................................................................
2884