xref: /trunk/main/svtools/source/table/tablecontrol_impl.cxx (revision d3e0dd8eb215533c15e891ee35bd141abe9397ee)
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             {
610                 m_nCurRow = ROW_INVALID;
611                 m_nTopRow = 0;
612             }
613         }
614         else if ( m_nRowCount == 0 )
615         {
616             m_nTopRow = 0;
617         }
618 
619 
620         // relayout, since the scrollbar need might have changed
621         impl_ni_relayout();
622 
623         // notify A11Y events
624         if ( impl_isAccessibleAlive() )
625         {
626             commitTableEvent(
627                 AccessibleEventId::TABLE_MODEL_CHANGED,
628                 makeAny( AccessibleTableModelChange(
629                     AccessibleTableModelChangeType::DELETE,
630                     firstRemovedRow,
631                     lastRemovedRow,
632                     0,
633                     m_pModel->getColumnCount()
634                 ) ),
635                 Any()
636             );
637         }
638 
639         // schedule a repaint
640         invalidateRowRange( firstRemovedRow, ROW_INVALID );
641 
642         // call selection handlers, if necessary
643         if ( selectionChanged )
644             m_rAntiImpl.Select();
645     }
646 
647     //------------------------------------------------------------------------------------------------------------------
648     void TableControl_Impl::columnInserted( ColPos const i_colIndex )
649     {
650         m_nColumnCount = m_pModel->getColumnCount();
651         impl_ni_relayout();
652 
653         m_rAntiImpl.Invalidate();
654 
655         OSL_UNUSED( i_colIndex );
656    }
657 
658     //------------------------------------------------------------------------------------------------------------------
659     void TableControl_Impl::columnRemoved( ColPos const i_colIndex )
660     {
661         m_nColumnCount = m_pModel->getColumnCount();
662 
663         // adjust the current column, if it is larger than the column count now
664         if ( m_nCurColumn >= m_nColumnCount )
665         {
666             if ( m_nColumnCount > 0 )
667                 goTo( m_nCurColumn - 1, m_nCurRow );
668             else
669                 m_nCurColumn = COL_INVALID;
670         }
671 
672         impl_ni_relayout();
673 
674         m_rAntiImpl.Invalidate();
675 
676         OSL_UNUSED( i_colIndex );
677     }
678 
679     //------------------------------------------------------------------------------------------------------------------
680     void TableControl_Impl::allColumnsRemoved()
681     {
682         m_nColumnCount = m_pModel->getColumnCount();
683         impl_ni_relayout();
684 
685         m_rAntiImpl.Invalidate();
686     }
687 
688     //------------------------------------------------------------------------------------------------------------------
689     void TableControl_Impl::cellsUpdated( ColPos const i_firstCol, ColPos i_lastCol, RowPos const i_firstRow, RowPos const i_lastRow )
690     {
691         invalidateRowRange( i_firstRow, i_lastRow );
692 
693         OSL_UNUSED( i_firstCol );
694         OSL_UNUSED( i_lastCol );
695     }
696 
697     //------------------------------------------------------------------------------------------------------------------
698     void TableControl_Impl::tableMetricsChanged()
699     {
700         impl_ni_updateCachedTableMetrics();
701         impl_ni_relayout();
702         m_rAntiImpl.Invalidate();
703     }
704 
705     //------------------------------------------------------------------------------------------------------------------
706     void TableControl_Impl::impl_invalidateColumn( ColPos const i_column )
707     {
708         DBG_CHECK_ME();
709 
710         Rectangle const aAllCellsArea( impl_getAllVisibleCellsArea() );
711 
712         const TableColumnGeometry aColumn( *this, aAllCellsArea, i_column );
713         if ( aColumn.isValid() )
714             m_rAntiImpl.Invalidate( aColumn.getRect() );
715     }
716 
717     //------------------------------------------------------------------------------------------------------------------
718     void TableControl_Impl::columnChanged( ColPos const i_column, ColumnAttributeGroup const i_attributeGroup )
719     {
720         ColumnAttributeGroup nGroup( i_attributeGroup );
721         if ( nGroup & COL_ATTRS_APPEARANCE )
722         {
723             impl_invalidateColumn( i_column );
724             nGroup &= ~COL_ATTRS_APPEARANCE;
725         }
726 
727         if ( nGroup & COL_ATTRS_WIDTH )
728         {
729             if ( !m_bUpdatingColWidths )
730             {
731                 impl_ni_relayout( i_column );
732                 invalidate( TableAreaAll );
733             }
734 
735             nGroup &= ~COL_ATTRS_WIDTH;
736         }
737 
738         OSL_ENSURE( ( nGroup == COL_ATTRS_NONE ) || ( i_attributeGroup == COL_ATTRS_ALL ),
739             "TableControl_Impl::columnChanged: don't know how to handle this change!" );
740     }
741 
742     //------------------------------------------------------------------------------------------------------------------
743     Rectangle TableControl_Impl::impl_getAllVisibleCellsArea() const
744     {
745         DBG_CHECK_ME();
746 
747         Rectangle aArea( Point( 0, 0 ), Size( 0, 0 ) );
748 
749         // determine the right-most border of the last column which is
750         // at least partially visible
751         aArea.Right() = m_nRowHeaderWidthPixel;
752         if ( !m_aColumnWidths.empty() )
753         {
754             // the number of pixels which are scrolled out of the left hand
755             // side of the window
756             const long nScrolledOutLeft = m_nLeftColumn == 0 ? 0 : m_aColumnWidths[ m_nLeftColumn - 1 ].getEnd();
757 
758             ColumnPositions::const_reverse_iterator loop = m_aColumnWidths.rbegin();
759             do
760             {
761                 aArea.Right() = loop->getEnd() - nScrolledOutLeft + m_nRowHeaderWidthPixel;
762                 ++loop;
763             }
764             while ( (   loop != m_aColumnWidths.rend() )
765                  && (   loop->getEnd() - nScrolledOutLeft >= aArea.Right() )
766                  );
767         }
768         // so far, aArea.Right() denotes the first pixel *after* the cell area
769         --aArea.Right();
770 
771         // determine the last row which is at least partially visible
772         aArea.Bottom() =
773                 m_nColHeaderHeightPixel
774             +   impl_getVisibleRows( true ) * m_nRowHeightPixel
775             -   1;
776 
777         return aArea;
778     }
779 
780     //------------------------------------------------------------------------------------------------------------------
781     Rectangle TableControl_Impl::impl_getAllVisibleDataCellArea() const
782     {
783         DBG_CHECK_ME();
784 
785         Rectangle aArea( impl_getAllVisibleCellsArea() );
786         aArea.Left() = m_nRowHeaderWidthPixel;
787         aArea.Top() = m_nColHeaderHeightPixel;
788         return aArea;
789     }
790 
791     //------------------------------------------------------------------------------------------------------------------
792     void TableControl_Impl::impl_ni_updateCachedTableMetrics()
793     {
794         m_nRowHeightPixel = m_rAntiImpl.LogicToPixel( Size( 0, m_pModel->getRowHeight() ), MAP_APPFONT ).Height();
795 
796         m_nColHeaderHeightPixel = 0;
797         if ( m_pModel->hasColumnHeaders() )
798            m_nColHeaderHeightPixel = m_rAntiImpl.LogicToPixel( Size( 0, m_pModel->getColumnHeaderHeight() ), MAP_APPFONT ).Height();
799 
800         m_nRowHeaderWidthPixel = 0;
801         if ( m_pModel->hasRowHeaders() )
802             m_nRowHeaderWidthPixel = m_rAntiImpl.LogicToPixel( Size( m_pModel->getRowHeaderWidth(), 0 ), MAP_APPFONT).Width();
803     }
804 
805     //------------------------------------------------------------------------------------------------------------------
806     void TableControl_Impl::impl_ni_updateCachedModelValues()
807     {
808         m_pInputHandler = m_pModel->getInputHandler();
809         if ( !m_pInputHandler )
810             m_pInputHandler.reset( new DefaultInputHandler );
811 
812         m_nColumnCount = m_pModel->getColumnCount();
813         if ( m_nLeftColumn >= m_nColumnCount )
814             m_nLeftColumn = ( m_nColumnCount > 0 ) ? m_nColumnCount - 1 : 0;
815 
816         m_nRowCount = m_pModel->getRowCount();
817         if ( m_nTopRow >= m_nRowCount )
818             m_nTopRow = ( m_nRowCount > 0 ) ? m_nRowCount - 1 : 0;
819 
820         impl_ni_updateCachedTableMetrics();
821     }
822 
823     //------------------------------------------------------------------------------------------------------------------
824     namespace
825     {
826         //..............................................................................................................
827         /// determines whether a scrollbar is needed for the given values
828         bool lcl_determineScrollbarNeed( long const i_position, ScrollbarVisibility const i_visibility,
829             long const i_availableSpace, long const i_neededSpace )
830         {
831             if ( i_visibility == ScrollbarShowNever )
832                 return false;
833             if ( i_visibility == ScrollbarShowAlways )
834                 return true;
835             if ( i_position > 0 )
836                 return true;
837             if ( i_availableSpace >= i_neededSpace )
838                 return false;
839             return true;
840         }
841 
842         //..............................................................................................................
843         void lcl_setButtonRepeat( Window& _rWindow, sal_uLong _nDelay )
844         {
845             AllSettings aSettings = _rWindow.GetSettings();
846             MouseSettings aMouseSettings = aSettings.GetMouseSettings();
847 
848             aMouseSettings.SetButtonRepeat( _nDelay );
849             aSettings.SetMouseSettings( aMouseSettings );
850 
851             _rWindow.SetSettings( aSettings, sal_True );
852         }
853 
854         //..............................................................................................................
855         bool lcl_updateScrollbar( Window& _rParent, ScrollBar*& _rpBar,
856             bool const i_needBar, long _nVisibleUnits,
857             long _nPosition, long _nLineSize, long _nRange,
858             bool _bHorizontal, const Link& _rScrollHandler )
859         {
860             // do we currently have the scrollbar?
861             bool bHaveBar = _rpBar != NULL;
862 
863             // do we need to correct the scrollbar visibility?
864             if ( bHaveBar && !i_needBar )
865             {
866                 if ( _rpBar->IsTracking() )
867                     _rpBar->EndTracking();
868                 DELETEZ( _rpBar );
869             }
870             else if ( !bHaveBar && i_needBar )
871             {
872                 _rpBar = new ScrollBar(
873                     &_rParent,
874                     WB_DRAG | ( _bHorizontal ? WB_HSCROLL : WB_VSCROLL )
875                 );
876                 _rpBar->SetScrollHdl( _rScrollHandler );
877                 // get some speed into the scrolling ....
878                 lcl_setButtonRepeat( *_rpBar, 0 );
879             }
880 
881             if ( _rpBar )
882             {
883                 _rpBar->SetRange( Range( 0, _nRange ) );
884                 _rpBar->SetVisibleSize( _nVisibleUnits );
885                 _rpBar->SetPageSize( _nVisibleUnits );
886                 _rpBar->SetLineSize( _nLineSize );
887                 _rpBar->SetThumbPos( _nPosition );
888                 _rpBar->Show();
889             }
890 
891             return ( bHaveBar != i_needBar );
892         }
893 
894         //..............................................................................................................
895         /** returns the number of rows fitting into the given range,
896             for the given row height. Partially fitting rows are counted, too, if the
897             respective parameter says so.
898         */
899         TableSize lcl_getRowsFittingInto( long _nOverallHeight, long _nRowHeightPixel, bool _bAcceptPartialRow = false )
900         {
901             return  _bAcceptPartialRow
902                 ?   ( _nOverallHeight + ( _nRowHeightPixel - 1 ) ) / _nRowHeightPixel
903                 :   _nOverallHeight / _nRowHeightPixel;
904         }
905 
906         //..............................................................................................................
907         /** returns the number of columns fitting into the given area,
908             with the first visible column as given. Partially fitting columns are counted, too,
909             if the respective parameter says so.
910         */
911         TableSize lcl_getColumnsVisibleWithin( const Rectangle& _rArea, ColPos _nFirstVisibleColumn,
912             const TableControl_Impl& _rControl, bool _bAcceptPartialRow )
913         {
914             TableSize visibleColumns = 0;
915             TableColumnGeometry aColumn( _rControl, _rArea, _nFirstVisibleColumn );
916             while ( aColumn.isValid() )
917             {
918                 if ( !_bAcceptPartialRow )
919                     if ( aColumn.getRect().Right() > _rArea.Right() )
920                         // this column is only partially visible, and this is not allowed
921                         break;
922 
923                 aColumn.moveRight();
924                 ++visibleColumns;
925             }
926             return visibleColumns;
927         }
928 
929     }
930 
931     //------------------------------------------------------------------------------------------------------------------
932     long TableControl_Impl::impl_ni_calculateColumnWidths( ColPos const i_assumeInflexibleColumnsUpToIncluding,
933         bool const i_assumeVerticalScrollbar, ::std::vector< long >& o_newColWidthsPixel ) const
934     {
935         // the available horizontal space
936         long gridWidthPixel = m_rAntiImpl.GetOutputSizePixel().Width();
937         ENSURE_OR_RETURN( !!m_pModel, "TableControl_Impl::impl_ni_calculateColumnWidths: not allowed without a model!", gridWidthPixel );
938         if ( m_pModel->hasRowHeaders() && ( gridWidthPixel != 0 ) )
939         {
940             gridWidthPixel -= m_nRowHeaderWidthPixel;
941         }
942 
943         if ( i_assumeVerticalScrollbar && ( m_pModel->getVerticalScrollbarVisibility() != ScrollbarShowNever ) )
944         {
945             long nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
946             gridWidthPixel -= nScrollbarMetrics;
947         }
948 
949         // no need to do anything without columns
950         TableSize const colCount = m_pModel->getColumnCount();
951         if ( colCount == 0 )
952             return gridWidthPixel;
953 
954         // collect some meta data for our columns:
955         // - their current (pixel) metrics
956         long accumulatedCurrentWidth = 0;
957         ::std::vector< long > currentColWidths;
958         currentColWidths.reserve( colCount );
959         typedef ::std::vector< ::std::pair< long, long > >   ColumnLimits;
960         ColumnLimits effectiveColumnLimits;
961         effectiveColumnLimits.reserve( colCount );
962         long accumulatedMinWidth = 0;
963         long accumulatedMaxWidth = 0;
964         // - their relative flexibility
965         ::std::vector< ::sal_Int32 > columnFlexibilities;
966         columnFlexibilities.reserve( colCount );
967         long flexibilityDenominator = 0;
968         size_t flexibleColumnCount = 0;
969         for ( ColPos col = 0; col < colCount; ++col )
970         {
971             PColumnModel const pColumn = m_pModel->getColumnModel( col );
972             ENSURE_OR_THROW( !!pColumn, "invalid column returned by the model!" );
973 
974             // current width
975             long const currentWidth = appFontWidthToPixel( pColumn->getWidth() );
976             currentColWidths.push_back( currentWidth );
977 
978             // accumulated width
979             accumulatedCurrentWidth += currentWidth;
980 
981             // flexibility
982             ::sal_Int32 flexibility = pColumn->getFlexibility();
983             OSL_ENSURE( flexibility >= 0, "TableControl_Impl::impl_ni_calculateColumnWidths: a column's flexibility should be non-negative." );
984             if  (   ( flexibility < 0 )                                 // normalization
985                 ||  ( !pColumn->isResizable() )                         // column not resizeable => no auto-resize
986                 ||  ( col <= i_assumeInflexibleColumnsUpToIncluding )   // column shall be treated as inflexible => respec this
987                 )
988                 flexibility = 0;
989 
990             // min/max width
991             long effectiveMin = currentWidth, effectiveMax = currentWidth;
992             // if the column is not flexible, it will not be asked for min/max, but we assume the current width as limit then
993             if ( flexibility > 0 )
994             {
995                 long const minWidth = appFontWidthToPixel( pColumn->getMinWidth() );
996                 if ( minWidth > 0 )
997                     effectiveMin = minWidth;
998                 else
999                     effectiveMin = MIN_COLUMN_WIDTH_PIXEL;
1000 
1001                 long const maxWidth = appFontWidthToPixel( pColumn->getMaxWidth() );
1002                 OSL_ENSURE( minWidth <= maxWidth, "TableControl_Impl::impl_ni_calculateColumnWidths: pretty undecided 'bout its width limits, this column!" );
1003                 if ( ( maxWidth > 0 ) && ( maxWidth >= minWidth ) )
1004                     effectiveMax = maxWidth;
1005                 else
1006                     effectiveMax = gridWidthPixel; // TODO: any better guess here?
1007 
1008                 if ( effectiveMin == effectiveMax )
1009                     // if the min and the max are identical, this implies no flexibility at all
1010                     flexibility = 0;
1011             }
1012 
1013             columnFlexibilities.push_back( flexibility );
1014             flexibilityDenominator += flexibility;
1015             if ( flexibility > 0 )
1016                 ++flexibleColumnCount;
1017 
1018             effectiveColumnLimits.push_back( ::std::pair< long, long >( effectiveMin, effectiveMax ) );
1019             accumulatedMinWidth += effectiveMin;
1020             accumulatedMaxWidth += effectiveMax;
1021         }
1022 
1023         o_newColWidthsPixel = currentColWidths;
1024         if ( flexibilityDenominator == 0 )
1025         {
1026             // no column is flexible => don't adjust anything
1027         }
1028         else if ( gridWidthPixel > accumulatedCurrentWidth )
1029         {   // we have space to give away ...
1030             long distributePixel = gridWidthPixel - accumulatedCurrentWidth;
1031             if ( gridWidthPixel > accumulatedMaxWidth )
1032             {
1033                 // ... but the column's maximal widths are still less than we have
1034                 // => set them all to max
1035                 for ( size_t i = 0; i < size_t( colCount ); ++i )
1036                 {
1037                     o_newColWidthsPixel[i] = effectiveColumnLimits[i].second;
1038                 }
1039             }
1040             else
1041             {
1042                 bool startOver = false;
1043                 do
1044                 {
1045                     startOver = false;
1046                     // distribute the remaining space amongst all columns with a positive flexibility
1047                     for ( size_t i=0; i<o_newColWidthsPixel.size() && !startOver; ++i )
1048                     {
1049                         long const columnFlexibility = columnFlexibilities[i];
1050                         if ( columnFlexibility == 0 )
1051                             continue;
1052 
1053                         long newColWidth = currentColWidths[i] + columnFlexibility * distributePixel / flexibilityDenominator;
1054 
1055                         if ( newColWidth > effectiveColumnLimits[i].second )
1056                         {   // that was too much, we hit the col's maximum
1057                             // set the new width to exactly this maximum
1058                             newColWidth = effectiveColumnLimits[i].second;
1059                             // adjust the flexibility denominator ...
1060                             flexibilityDenominator -= columnFlexibility;
1061                             columnFlexibilities[i] = 0;
1062                             --flexibleColumnCount;
1063                             // ... and the remaining width ...
1064                             long const difference = newColWidth - currentColWidths[i];
1065                             distributePixel -= difference;
1066                             // ... this way, we ensure that the width not taken up by this column is consumed by the other
1067                             // flexible ones (if there are some)
1068 
1069                             // and start over with the first column, since there might be earlier columns which need
1070                             // to be recalculated now
1071                             startOver = true;
1072                         }
1073 
1074                         o_newColWidthsPixel[i] = newColWidth;
1075                     }
1076                 }
1077                 while ( startOver );
1078 
1079                 // are there pixels left (might be caused by rounding errors)?
1080                 distributePixel = gridWidthPixel - ::std::accumulate( o_newColWidthsPixel.begin(), o_newColWidthsPixel.end(), 0 );
1081                 while ( ( distributePixel > 0 ) && ( flexibleColumnCount > 0 ) )
1082                 {
1083                     // yes => ignore relative flexibilities, and subsequently distribute single pixels to all flexible
1084                     // columns which did not yet reach their maximum.
1085                     for ( size_t i=0; ( i < o_newColWidthsPixel.size() ) && ( distributePixel > 0 ); ++i )
1086                     {
1087                         if ( columnFlexibilities[i] == 0 )
1088                             continue;
1089 
1090                         OSL_ENSURE( o_newColWidthsPixel[i] <= effectiveColumnLimits[i].second,
1091                             "TableControl_Impl::impl_ni_calculateColumnWidths: inconsitency!" );
1092                         if ( o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first )
1093                         {
1094                             columnFlexibilities[i] = 0;
1095                             --flexibleColumnCount;
1096                             continue;
1097                         }
1098 
1099                         ++o_newColWidthsPixel[i];
1100                         --distributePixel;
1101                     }
1102                 }
1103             }
1104         }
1105         else if ( gridWidthPixel < accumulatedCurrentWidth )
1106         {   // we need to take away some space from the columns which allow it ...
1107             long takeAwayPixel = accumulatedCurrentWidth - gridWidthPixel;
1108             if ( gridWidthPixel < accumulatedMinWidth )
1109             {
1110                 // ... but the column's minimal widths are still more than we have
1111                 // => set them all to min
1112                 for ( size_t i = 0; i < size_t( colCount ); ++i )
1113                 {
1114                     o_newColWidthsPixel[i] = effectiveColumnLimits[i].first;
1115                 }
1116             }
1117             else
1118             {
1119                 bool startOver = false;
1120                 do
1121                 {
1122                     startOver = false;
1123                     // take away the space we need from the columns with a positive flexibility
1124                     for ( size_t i=0; i<o_newColWidthsPixel.size() && !startOver; ++i )
1125                     {
1126                         long const columnFlexibility = columnFlexibilities[i];
1127                         if ( columnFlexibility == 0 )
1128                             continue;
1129 
1130                         long newColWidth = currentColWidths[i] - columnFlexibility * takeAwayPixel / flexibilityDenominator;
1131 
1132                         if ( newColWidth < effectiveColumnLimits[i].first )
1133                         {   // that was too much, we hit the col's minimum
1134                             // set the new width to exactly this minimum
1135                             newColWidth = effectiveColumnLimits[i].first;
1136                             // adjust the flexibility denominator ...
1137                             flexibilityDenominator -= columnFlexibility;
1138                             columnFlexibilities[i] = 0;
1139                             --flexibleColumnCount;
1140                             // ... and the remaining width ...
1141                             long const difference = currentColWidths[i] - newColWidth;
1142                             takeAwayPixel -= difference;
1143 
1144                             // and start over with the first column, since there might be earlier columns which need
1145                             // to be recalculated now
1146                             startOver = true;
1147                         }
1148 
1149                         o_newColWidthsPixel[i] = newColWidth;
1150                     }
1151                 }
1152                 while ( startOver );
1153 
1154                 // are there pixels left (might be caused by rounding errors)?
1155                 takeAwayPixel = ::std::accumulate( o_newColWidthsPixel.begin(), o_newColWidthsPixel.end(), 0 ) - gridWidthPixel;
1156                 while ( ( takeAwayPixel > 0 ) && ( flexibleColumnCount > 0 ) )
1157                 {
1158                     // yes => ignore relative flexibilities, and subsequently take away pixels from all flexible
1159                     // columns which did not yet reach their minimum.
1160                     for ( size_t i=0; ( i < o_newColWidthsPixel.size() ) && ( takeAwayPixel > 0 ); ++i )
1161                     {
1162                         if ( columnFlexibilities[i] == 0 )
1163                             continue;
1164 
1165                         OSL_ENSURE( o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first,
1166                             "TableControl_Impl::impl_ni_calculateColumnWidths: inconsitency!" );
1167                         if ( o_newColWidthsPixel[i] <= effectiveColumnLimits[i].first )
1168                         {
1169                             columnFlexibilities[i] = 0;
1170                             --flexibleColumnCount;
1171                             continue;
1172                         }
1173 
1174                         --o_newColWidthsPixel[i];
1175                         --takeAwayPixel;
1176                     }
1177                 }
1178             }
1179         }
1180 
1181         return gridWidthPixel;
1182     }
1183 
1184     //------------------------------------------------------------------------------------------------------------------
1185     void TableControl_Impl::impl_ni_relayout( ColPos const i_assumeInflexibleColumnsUpToIncluding )
1186     {
1187         ENSURE_OR_RETURN_VOID( !m_bUpdatingColWidths, "TableControl_Impl::impl_ni_relayout: recursive call detected!" );
1188 
1189         m_aColumnWidths.resize( 0 );
1190         if ( !m_pModel )
1191             return;
1192 
1193         ::comphelper::FlagRestorationGuard const aWidthUpdateFlag( m_bUpdatingColWidths, true );
1194         SuppressCursor aHideCursor( *this );
1195 
1196         // layouting steps:
1197         //
1198         // 1. adjust column widths, leaving space for a vertical scrollbar
1199         // 2. determine need for a vertical scrollbar
1200         //    - V-YES: all fine, result from 1. is still valid
1201         //    - V-NO: result from 1. is still under consideration
1202         //
1203         // 3. determine need for a horizontal scrollbar
1204         //   - H-NO: all fine, result from 2. is still valid
1205         //   - H-YES: reconsider need for a vertical scrollbar, if result of 2. was V-NO
1206         //     - V-YES: all fine, result from 1. is still valid
1207         //     - V-NO: redistribute the remaining space (if any) amongst all columns which allow it
1208 
1209         ::std::vector< long > newWidthsPixel;
1210         long gridWidthPixel = impl_ni_calculateColumnWidths( i_assumeInflexibleColumnsUpToIncluding, true, newWidthsPixel );
1211 
1212         // the width/height of a scrollbar, needed several times below
1213         long const nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
1214 
1215         // determine the playground for the data cells (excluding headers)
1216         // TODO: what if the control is smaller than needed for the headers/scrollbars?
1217         Rectangle aDataCellPlayground( Point( 0, 0 ), m_rAntiImpl.GetOutputSizePixel() );
1218         aDataCellPlayground.Left() = m_nRowHeaderWidthPixel;
1219         aDataCellPlayground.Top() = m_nColHeaderHeightPixel;
1220 
1221         OSL_ENSURE( ( m_nRowCount == m_pModel->getRowCount() ) && ( m_nColumnCount == m_pModel->getColumnCount() ),
1222             "TableControl_Impl::impl_ni_relayout: how is this expected to work with invalid data?" );
1223         long const nAllColumnsWidth = ::std::accumulate( newWidthsPixel.begin(), newWidthsPixel.end(), 0 );
1224 
1225         ScrollbarVisibility const eVertScrollbar = m_pModel->getVerticalScrollbarVisibility();
1226         ScrollbarVisibility const eHorzScrollbar = m_pModel->getHorizontalScrollbarVisibility();
1227 
1228         // do we need a vertical scrollbar?
1229         bool bNeedVerticalScrollbar = lcl_determineScrollbarNeed(
1230             m_nTopRow, eVertScrollbar, aDataCellPlayground.GetHeight(), m_nRowHeightPixel * m_nRowCount );
1231         bool bFirstRoundVScrollNeed = false;
1232         if ( bNeedVerticalScrollbar )
1233         {
1234             aDataCellPlayground.Right() -= nScrollbarMetrics;
1235             bFirstRoundVScrollNeed = true;
1236         }
1237 
1238         // do we need a horizontal scrollbar?
1239         bool const bNeedHorizontalScrollbar = lcl_determineScrollbarNeed(
1240             m_nLeftColumn, eHorzScrollbar, aDataCellPlayground.GetWidth(), nAllColumnsWidth );
1241         if ( bNeedHorizontalScrollbar )
1242         {
1243             aDataCellPlayground.Bottom() -= nScrollbarMetrics;
1244 
1245             // now that we just found that we need a horizontal scrollbar,
1246             // the need for a vertical one may have changed, since the horizontal
1247             // SB might just occupy enough space so that not all rows do fit
1248             // anymore
1249             if  ( !bFirstRoundVScrollNeed )
1250             {
1251                 bNeedVerticalScrollbar = lcl_determineScrollbarNeed(
1252                     m_nTopRow, eVertScrollbar, aDataCellPlayground.GetHeight(), m_nRowHeightPixel * m_nRowCount );
1253                 if ( bNeedVerticalScrollbar )
1254                 {
1255                     aDataCellPlayground.Right() -= nScrollbarMetrics;
1256                 }
1257             }
1258         }
1259 
1260         // the initial call to impl_ni_calculateColumnWidths assumed that we need a vertical scrollbar. If, by now,
1261         // we know that this is not the case, re-calculate the column widths.
1262         if ( !bNeedVerticalScrollbar )
1263             gridWidthPixel = impl_ni_calculateColumnWidths( i_assumeInflexibleColumnsUpToIncluding, false, newWidthsPixel );
1264 
1265         // update the column objects with the new widths we finally calculated
1266         TableSize const colCount = m_pModel->getColumnCount();
1267         m_aColumnWidths.reserve( colCount );
1268         long accumulatedWidthPixel = m_nRowHeaderWidthPixel;
1269         bool anyColumnWidthChanged = false;
1270         for ( ColPos col = 0; col < colCount; ++col )
1271         {
1272             const long columnStart = accumulatedWidthPixel;
1273             const long columnEnd = columnStart + newWidthsPixel[col];
1274             m_aColumnWidths.push_back( MutableColumnMetrics( columnStart, columnEnd ) );
1275             accumulatedWidthPixel = columnEnd;
1276 
1277             // and don't forget to forward this to the column models
1278             PColumnModel const pColumn = m_pModel->getColumnModel( col );
1279             ENSURE_OR_THROW( !!pColumn, "invalid column returned by the model!" );
1280 
1281             long const oldColumnWidthAppFont = pColumn->getWidth();
1282             long const newColumnWidthAppFont = pixelWidthToAppFont( newWidthsPixel[col] );
1283             pColumn->setWidth( newColumnWidthAppFont );
1284 
1285             anyColumnWidthChanged |= ( oldColumnWidthAppFont != newColumnWidthAppFont );
1286         }
1287 
1288         // if the column widths changed, ensure everything is repainted
1289         if ( anyColumnWidthChanged )
1290             invalidate( TableAreaAll );
1291 
1292         // if the column resizing happened to leave some space at the right, but there are columns
1293         // scrolled out to the left, scroll them in
1294         while   (   ( m_nLeftColumn > 0 )
1295                 &&  ( accumulatedWidthPixel - m_aColumnWidths[ m_nLeftColumn - 1 ].getStart() <= gridWidthPixel )
1296                 )
1297         {
1298             --m_nLeftColumn;
1299         }
1300 
1301         // now adjust the column metrics, since they currently ignore the horizontal scroll position
1302         if ( m_nLeftColumn > 0 )
1303         {
1304             const long offsetPixel = m_aColumnWidths[ 0 ].getStart() - m_aColumnWidths[ m_nLeftColumn ].getStart();
1305             for (   ColumnPositions::iterator colPos = m_aColumnWidths.begin();
1306                     colPos != m_aColumnWidths.end();
1307                     ++colPos
1308                  )
1309             {
1310                 colPos->move( offsetPixel );
1311             }
1312         }
1313 
1314         // show or hide the scrollbars as needed, and position the data window
1315         impl_ni_positionChildWindows( aDataCellPlayground, bNeedVerticalScrollbar, bNeedHorizontalScrollbar );
1316     }
1317 
1318     //------------------------------------------------------------------------------------------------------------------
1319     void TableControl_Impl::impl_ni_positionChildWindows( Rectangle const & i_dataCellPlayground,
1320         bool const i_verticalScrollbar, bool const i_horizontalScrollbar )
1321     {
1322         long const nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize();
1323 
1324         // create or destroy the vertical scrollbar, as needed
1325         lcl_updateScrollbar(
1326             m_rAntiImpl,
1327             m_pVScroll,
1328             i_verticalScrollbar,
1329             lcl_getRowsFittingInto( i_dataCellPlayground.GetHeight(), m_nRowHeightPixel ),
1330                                                                     // visible units
1331             m_nTopRow,                                              // current position
1332             1,                                                      // line size
1333             m_nRowCount,                                            // range
1334             false,                                                  // vertical
1335             LINK( this, TableControl_Impl, OnScroll )               // scroll handler
1336         );
1337 
1338         // position it
1339         if ( m_pVScroll )
1340         {
1341             Rectangle aScrollbarArea(
1342                 Point( i_dataCellPlayground.Right() + 1, 0 ),
1343                 Size( nScrollbarMetrics, i_dataCellPlayground.Bottom() + 1 )
1344             );
1345             m_pVScroll->SetPosSizePixel(
1346                 aScrollbarArea.TopLeft(), aScrollbarArea.GetSize() );
1347         }
1348 
1349         // create or destroy the horizontal scrollbar, as needed
1350         lcl_updateScrollbar(
1351             m_rAntiImpl,
1352             m_pHScroll,
1353             i_horizontalScrollbar,
1354             lcl_getColumnsVisibleWithin( i_dataCellPlayground, m_nLeftColumn, *this, false ),
1355                                                                     // visible units
1356             m_nLeftColumn,                                          // current position
1357             1,                                                      // line size
1358             m_nColumnCount,                                         // range
1359             true,                                                   // horizontal
1360             LINK( this, TableControl_Impl, OnScroll )               // scroll handler
1361         );
1362 
1363         // position it
1364         if ( m_pHScroll )
1365         {
1366             TableSize const nVisibleUnits = lcl_getColumnsVisibleWithin( i_dataCellPlayground, m_nLeftColumn, *this, false );
1367             TableMetrics const nRange = m_nColumnCount;
1368             if( m_nLeftColumn + nVisibleUnits == nRange - 1 )
1369             {
1370                 if ( m_aColumnWidths[ nRange - 1 ].getStart() - m_aColumnWidths[ m_nLeftColumn ].getEnd() + m_aColumnWidths[ nRange-1 ].getWidth() > i_dataCellPlayground.GetWidth() )
1371                 {
1372                     m_pHScroll->SetVisibleSize( nVisibleUnits -1 );
1373                     m_pHScroll->SetPageSize( nVisibleUnits - 1 );
1374                 }
1375             }
1376             Rectangle aScrollbarArea(
1377                 Point( 0, i_dataCellPlayground.Bottom() + 1 ),
1378                 Size( i_dataCellPlayground.Right() + 1, nScrollbarMetrics )
1379             );
1380             m_pHScroll->SetPosSizePixel(
1381                 aScrollbarArea.TopLeft(), aScrollbarArea.GetSize() );
1382         }
1383 
1384         // the corner window connecting the two scrollbars in the lower right corner
1385         bool bHaveScrollCorner = NULL != m_pScrollCorner;
1386         bool bNeedScrollCorner = ( NULL != m_pHScroll ) && ( NULL != m_pVScroll );
1387         if ( bHaveScrollCorner && !bNeedScrollCorner )
1388         {
1389             DELETEZ( m_pScrollCorner );
1390         }
1391         else if ( !bHaveScrollCorner && bNeedScrollCorner )
1392         {
1393             m_pScrollCorner = new ScrollBarBox( &m_rAntiImpl );
1394             m_pScrollCorner->SetSizePixel( Size( nScrollbarMetrics, nScrollbarMetrics ) );
1395             m_pScrollCorner->SetPosPixel( Point( i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1 ) );
1396             m_pScrollCorner->Show();
1397         }
1398         else if(bHaveScrollCorner && bNeedScrollCorner)
1399         {
1400             m_pScrollCorner->SetPosPixel( Point( i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1 ) );
1401             m_pScrollCorner->Show();
1402         }
1403 
1404         // resize the data window
1405         m_pDataWindow->SetSizePixel( Size(
1406             i_dataCellPlayground.GetWidth() + m_nRowHeaderWidthPixel,
1407             i_dataCellPlayground.GetHeight() + m_nColHeaderHeightPixel
1408         ) );
1409     }
1410 
1411     //------------------------------------------------------------------------------------------------------------------
1412     void TableControl_Impl::onResize()
1413     {
1414         DBG_CHECK_ME();
1415 
1416         impl_ni_relayout();
1417         checkCursorPosition();
1418     }
1419 
1420     //------------------------------------------------------------------------------------------------------------------
1421     void TableControl_Impl::doPaintContent( const Rectangle& _rUpdateRect )
1422     {
1423         DBG_CHECK_ME();
1424 
1425         if ( !getModel() )
1426             return;
1427         PTableRenderer pRenderer = getModel()->getRenderer();
1428         DBG_ASSERT( !!pRenderer, "TableDataWindow::doPaintContent: invalid renderer!" );
1429         if ( !pRenderer )
1430             return;
1431 
1432         // our current style settings, to be passed to the renderer
1433         const StyleSettings& rStyle = m_rAntiImpl.GetSettings().GetStyleSettings();
1434         m_nRowCount = m_pModel->getRowCount();
1435         // the area occupied by all (at least partially) visible cells, including
1436         // headers
1437         Rectangle const aAllCellsWithHeaders( impl_getAllVisibleCellsArea() );
1438 
1439         // ............................
1440         // draw the header column area
1441         if ( m_pModel->hasColumnHeaders() )
1442         {
1443             TableRowGeometry const aHeaderRow( *this, Rectangle( Point( 0, 0 ),
1444                 aAllCellsWithHeaders.BottomRight() ), ROW_COL_HEADERS );
1445             Rectangle const aColRect(aHeaderRow.getRect());
1446             pRenderer->PaintHeaderArea(
1447                 *m_pDataWindow, aColRect, true, false, rStyle
1448             );
1449             // Note that strictly, aHeaderRow.getRect() also contains the intersection between column
1450             // and row header area. However, below we go to paint this intersection, again,
1451             // so this hopefully doesn't hurt if we already paint it here.
1452 
1453             for ( TableCellGeometry aCell( aHeaderRow, m_nLeftColumn );
1454                   aCell.isValid();
1455                   aCell.moveRight()
1456                 )
1457             {
1458                 if ( _rUpdateRect.GetIntersection( aCell.getRect() ).IsEmpty() )
1459                     continue;
1460 
1461                 bool isActiveColumn = ( aCell.getColumn() == getCurrentColumn() );
1462                 bool isSelectedColumn = false;
1463                 pRenderer->PaintColumnHeader( aCell.getColumn(), isActiveColumn, isSelectedColumn,
1464                     *m_pDataWindow, aCell.getRect(), rStyle );
1465             }
1466         }
1467         // the area occupied by the row header, if any
1468         Rectangle aRowHeaderArea;
1469         if ( m_pModel->hasRowHeaders() )
1470         {
1471             aRowHeaderArea = aAllCellsWithHeaders;
1472             aRowHeaderArea.Right() = m_nRowHeaderWidthPixel - 1;
1473 
1474             TableSize const nVisibleRows = impl_getVisibleRows( true );
1475             TableSize nActualRows = nVisibleRows;
1476             if ( m_nTopRow + nActualRows > m_nRowCount )
1477                 nActualRows = m_nRowCount - m_nTopRow;
1478             aRowHeaderArea.Bottom() = m_nColHeaderHeightPixel + m_nRowHeightPixel * nActualRows - 1;
1479 
1480             pRenderer->PaintHeaderArea( *m_pDataWindow, aRowHeaderArea, false, true, rStyle );
1481             // Note that strictly, aRowHeaderArea also contains the intersection between column
1482             // and row header area. However, below we go to paint this intersection, again,
1483             // so this hopefully doesn't hurt if we already paint it here.
1484 
1485             if ( m_pModel->hasColumnHeaders() )
1486             {
1487                 TableCellGeometry const aIntersection( *this, Rectangle( Point( 0, 0 ),
1488                     aAllCellsWithHeaders.BottomRight() ), COL_ROW_HEADERS, ROW_COL_HEADERS );
1489                 Rectangle const aInters( aIntersection.getRect() );
1490                 pRenderer->PaintHeaderArea(
1491                     *m_pDataWindow, aInters, true, true, rStyle
1492                 );
1493             }
1494         }
1495 
1496         // ............................
1497         // draw the table content row by row
1498 
1499         TableSize colCount = getModel()->getColumnCount();
1500 
1501         // paint all rows
1502         Rectangle const aAllDataCellsArea( impl_getAllVisibleDataCellArea() );
1503         for ( TableRowGeometry aRowIterator( *this, aAllCellsWithHeaders, getTopRow() );
1504               aRowIterator.isValid();
1505               aRowIterator.moveDown() )
1506         {
1507             if ( _rUpdateRect.GetIntersection( aRowIterator.getRect() ).IsEmpty() )
1508                 continue;
1509 
1510             bool const isControlFocused = m_rAntiImpl.HasControlFocus();
1511             bool const isSelectedRow = isRowSelected( aRowIterator.getRow() );
1512 
1513             Rectangle const aRect = aRowIterator.getRect().GetIntersection( aAllDataCellsArea );
1514 
1515             // give the redenderer a chance to prepare the row
1516             pRenderer->PrepareRow(
1517                 aRowIterator.getRow(), isControlFocused, isSelectedRow,
1518                 *m_pDataWindow, aRect, rStyle
1519             );
1520 
1521             // paint the row header
1522             if ( m_pModel->hasRowHeaders() )
1523             {
1524                 const Rectangle aCurrentRowHeader( aRowHeaderArea.GetIntersection( aRowIterator.getRect() ) );
1525                 pRenderer->PaintRowHeader( isControlFocused, isSelectedRow, *m_pDataWindow, aCurrentRowHeader,
1526                     rStyle );
1527             }
1528 
1529             if ( !colCount )
1530                 continue;
1531 
1532             // paint all cells in this row
1533             for ( TableCellGeometry aCell( aRowIterator, m_nLeftColumn );
1534                   aCell.isValid();
1535                   aCell.moveRight()
1536                 )
1537             {
1538                 bool isSelectedColumn = false;
1539                 pRenderer->PaintCell( aCell.getColumn(), isSelectedRow || isSelectedColumn, isControlFocused,
1540                                 *m_pDataWindow, aCell.getRect(), rStyle );
1541             }
1542         }
1543     }
1544     //------------------------------------------------------------------------------------------------------------------
1545     void TableControl_Impl::hideCursor()
1546     {
1547         DBG_CHECK_ME();
1548 
1549         if ( ++m_nCursorHidden == 1 )
1550             impl_ni_doSwitchCursor( false );
1551     }
1552 
1553     //------------------------------------------------------------------------------------------------------------------
1554     void TableControl_Impl::showCursor()
1555     {
1556         DBG_CHECK_ME();
1557 
1558         DBG_ASSERT( m_nCursorHidden > 0, "TableControl_Impl::showCursor: cursor not hidden!" );
1559         if ( --m_nCursorHidden == 0 )
1560             impl_ni_doSwitchCursor( true );
1561     }
1562 
1563     //------------------------------------------------------------------------------------------------------------------
1564     bool TableControl_Impl::dispatchAction( TableControlAction _eAction )
1565     {
1566         DBG_CHECK_ME();
1567 
1568         bool bSuccess = false;
1569         bool selectionChanged = false;
1570 
1571         switch ( _eAction )
1572         {
1573         case cursorDown:
1574         if ( m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION )
1575         {
1576             //if other rows already selected, deselect them
1577             if ( m_aSelectedRows.size()>0 )
1578             {
1579                 invalidateSelectedRows();
1580                 m_aSelectedRows.clear();
1581             }
1582             if ( m_nCurRow < m_nRowCount-1 )
1583             {
1584                 ++m_nCurRow;
1585                 m_aSelectedRows.push_back(m_nCurRow);
1586             }
1587             else
1588                 m_aSelectedRows.push_back(m_nCurRow);
1589             invalidateRow( m_nCurRow );
1590             ensureVisible(m_nCurColumn,m_nCurRow,false);
1591             selectionChanged = true;
1592             bSuccess = true;
1593         }
1594         else
1595         {
1596             if ( m_nCurRow < m_nRowCount - 1 )
1597                 bSuccess = goTo( m_nCurColumn, m_nCurRow + 1 );
1598         }
1599             break;
1600 
1601         case cursorUp:
1602         if(m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION)
1603         {
1604             if(m_aSelectedRows.size()>0)
1605             {
1606                 invalidateSelectedRows();
1607                 m_aSelectedRows.clear();
1608             }
1609             if(m_nCurRow>0)
1610             {
1611                 --m_nCurRow;
1612                 m_aSelectedRows.push_back(m_nCurRow);
1613                 invalidateRow( m_nCurRow );
1614             }
1615             else
1616             {
1617                 m_aSelectedRows.push_back(m_nCurRow);
1618                 invalidateRow( m_nCurRow );
1619             }
1620             ensureVisible(m_nCurColumn,m_nCurRow,false);
1621             selectionChanged = true;
1622             bSuccess = true;
1623         }
1624         else
1625         {
1626             if ( m_nCurRow > 0 )
1627                 bSuccess = goTo( m_nCurColumn, m_nCurRow - 1 );
1628         }
1629         break;
1630         case cursorLeft:
1631             if ( m_nCurColumn > 0 )
1632                 bSuccess = goTo( m_nCurColumn - 1, m_nCurRow );
1633             else
1634                 if ( ( m_nCurColumn == 0) && ( m_nCurRow > 0 ) )
1635                     bSuccess = goTo( m_nColumnCount - 1, m_nCurRow - 1 );
1636             break;
1637 
1638         case cursorRight:
1639             if ( m_nCurColumn < m_nColumnCount - 1 )
1640                 bSuccess = goTo( m_nCurColumn + 1, m_nCurRow );
1641             else
1642                 if ( ( m_nCurColumn == m_nColumnCount - 1 ) && ( m_nCurRow < m_nRowCount - 1 ) )
1643                     bSuccess = goTo( 0, m_nCurRow + 1 );
1644             break;
1645 
1646         case cursorToLineStart:
1647             bSuccess = goTo( 0, m_nCurRow );
1648             break;
1649 
1650         case cursorToLineEnd:
1651             bSuccess = goTo( m_nColumnCount - 1, m_nCurRow );
1652             break;
1653 
1654         case cursorToFirstLine:
1655             bSuccess = goTo( m_nCurColumn, 0 );
1656             break;
1657 
1658         case cursorToLastLine:
1659             bSuccess = goTo( m_nCurColumn, m_nRowCount - 1 );
1660             break;
1661 
1662         case cursorPageUp:
1663         {
1664             RowPos nNewRow = ::std::max( (RowPos)0, m_nCurRow - impl_getVisibleRows( false ) );
1665             bSuccess = goTo( m_nCurColumn, nNewRow );
1666         }
1667         break;
1668 
1669         case cursorPageDown:
1670         {
1671             RowPos nNewRow = ::std::min( m_nRowCount - 1, m_nCurRow + impl_getVisibleRows( false ) );
1672             bSuccess = goTo( m_nCurColumn, nNewRow );
1673         }
1674         break;
1675 
1676         case cursorTopLeft:
1677             bSuccess = goTo( 0, 0 );
1678             break;
1679 
1680         case cursorBottomRight:
1681             bSuccess = goTo( m_nColumnCount - 1, m_nRowCount - 1 );
1682             break;
1683 
1684         case cursorSelectRow:
1685         {
1686             if(m_pSelEngine->GetSelectionMode() == NO_SELECTION)
1687                 return bSuccess = false;
1688             //pos is the position of the current row in the vector of selected rows, if current row is selected
1689             int pos = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
1690             //if current row is selected, it should be deselected, when ALT+SPACE are pressed
1691             if(pos>-1)
1692             {
1693                 m_aSelectedRows.erase(m_aSelectedRows.begin()+pos);
1694                 if(m_aSelectedRows.empty() && m_nAnchor != -1)
1695                     m_nAnchor = -1;
1696             }
1697             //else select the row->put it in the vector
1698             else
1699                 m_aSelectedRows.push_back(m_nCurRow);
1700             invalidateRow( m_nCurRow );
1701             selectionChanged = true;
1702             bSuccess = true;
1703         }
1704             break;
1705         case cursorSelectRowUp:
1706         {
1707             if(m_pSelEngine->GetSelectionMode() == NO_SELECTION)
1708                 return bSuccess = false;
1709             else if(m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION)
1710             {
1711                 //if there are other selected rows, deselect them
1712                 return false;
1713             }
1714             else
1715             {
1716                 //there are other selected rows
1717                 if(m_aSelectedRows.size()>0)
1718                 {
1719                     //the anchor wasn't set -> a region is not selected, that's why clear all selection
1720                     //and select the current row
1721                     if(m_nAnchor==-1)
1722                     {
1723                         invalidateSelectedRows();
1724                         m_aSelectedRows.clear();
1725                         m_aSelectedRows.push_back(m_nCurRow);
1726                         invalidateRow( m_nCurRow );
1727                     }
1728                     else
1729                     {
1730                         //a region is already selected, prevRow is last selected row and the row above - nextRow - should be selected
1731                         int prevRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
1732                         int nextRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow-1);
1733                         if(prevRow>-1)
1734                         {
1735                             //if m_nCurRow isn't the upper one, can move up, otherwise not
1736                             if(m_nCurRow>0)
1737                                 m_nCurRow--;
1738                             else
1739                                 return bSuccess = true;
1740                             //if nextRow already selected, deselect it, otherwise select it
1741                             if(nextRow>-1 && m_aSelectedRows[nextRow] == m_nCurRow)
1742                             {
1743                                 m_aSelectedRows.erase(m_aSelectedRows.begin()+prevRow);
1744                                 invalidateRow( m_nCurRow + 1 );
1745                             }
1746                             else
1747                             {
1748                                 m_aSelectedRows.push_back(m_nCurRow);
1749                                 invalidateRow( m_nCurRow );
1750                             }
1751                         }
1752                         else
1753                         {
1754                             if(m_nCurRow>0)
1755                             {
1756                                 m_aSelectedRows.push_back(m_nCurRow);
1757                                 m_nCurRow--;
1758                                 m_aSelectedRows.push_back(m_nCurRow);
1759                                 invalidateSelectedRegion( m_nCurRow+1, m_nCurRow );
1760                             }
1761                         }
1762                     }
1763                 }
1764                 else
1765                 {
1766                     //if nothing is selected and the current row isn't the upper one
1767                     //select the current and one row above
1768                     //otherwise select only the upper row
1769                     if(m_nCurRow>0)
1770                     {
1771                         m_aSelectedRows.push_back(m_nCurRow);
1772                         m_nCurRow--;
1773                         m_aSelectedRows.push_back(m_nCurRow);
1774                         invalidateSelectedRegion( m_nCurRow+1, m_nCurRow );
1775                     }
1776                     else
1777                     {
1778                         m_aSelectedRows.push_back(m_nCurRow);
1779                         invalidateRow( m_nCurRow );
1780                     }
1781                 }
1782                 m_pSelEngine->SetAnchor(sal_True);
1783                 m_nAnchor = m_nCurRow;
1784                 ensureVisible(m_nCurColumn, m_nCurRow, false);
1785                 selectionChanged = true;
1786                 bSuccess = true;
1787             }
1788         }
1789         break;
1790         case cursorSelectRowDown:
1791         {
1792             if(m_pSelEngine->GetSelectionMode() == NO_SELECTION)
1793                 bSuccess = false;
1794             else if(m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION)
1795             {
1796                 bSuccess = false;
1797             }
1798             else
1799             {
1800                 if(m_aSelectedRows.size()>0)
1801                 {
1802                     //the anchor wasn't set -> a region is not selected, that's why clear all selection
1803                     //and select the current row
1804                     if(m_nAnchor==-1)
1805                     {
1806                         invalidateSelectedRows();
1807                         m_aSelectedRows.clear();
1808                         m_aSelectedRows.push_back(m_nCurRow);
1809                         invalidateRow( m_nCurRow );
1810                         }
1811                     else
1812                     {
1813                         //a region is already selected, prevRow is last selected row and the row beneath - nextRow - should be selected
1814                         int prevRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow);
1815                         int nextRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow+1);
1816                         if(prevRow>-1)
1817                         {
1818                             //if m_nCurRow isn't the last one, can move down, otherwise not
1819                             if(m_nCurRow<m_nRowCount-1)
1820                                 m_nCurRow++;
1821                             else
1822                                 return bSuccess = true;
1823                             //if next row already selected, deselect it, otherwise select it
1824                             if(nextRow>-1 && m_aSelectedRows[nextRow] == m_nCurRow)
1825                             {
1826                                 m_aSelectedRows.erase(m_aSelectedRows.begin()+prevRow);
1827                                 invalidateRow( m_nCurRow - 1 );
1828                             }
1829                             else
1830                             {
1831                                 m_aSelectedRows.push_back(m_nCurRow);
1832                                 invalidateRow( m_nCurRow );
1833                             }
1834                         }
1835                         else
1836                         {
1837                             if(m_nCurRow<m_nRowCount-1)
1838                             {
1839                                 m_aSelectedRows.push_back(m_nCurRow);
1840                                 m_nCurRow++;
1841                                 m_aSelectedRows.push_back(m_nCurRow);
1842                                 invalidateSelectedRegion( m_nCurRow-1, m_nCurRow );
1843                             }
1844                         }
1845                     }
1846                 }
1847                 else
1848                 {
1849                     //there wasn't any selection, select current and row beneath, otherwise only row beneath
1850                     if(m_nCurRow<m_nRowCount-1)
1851                     {
1852                         m_aSelectedRows.push_back(m_nCurRow);
1853                         m_nCurRow++;
1854                         m_aSelectedRows.push_back(m_nCurRow);
1855                         invalidateSelectedRegion( m_nCurRow-1, m_nCurRow );
1856                     }
1857                     else
1858                     {
1859                         m_aSelectedRows.push_back(m_nCurRow);
1860                         invalidateRow( m_nCurRow );
1861                     }
1862                 }
1863                 m_pSelEngine->SetAnchor(sal_True);
1864                 m_nAnchor = m_nCurRow;
1865                 ensureVisible(m_nCurColumn, m_nCurRow, false);
1866                 selectionChanged = true;
1867                 bSuccess = true;
1868             }
1869         }
1870         break;
1871 
1872         case cursorSelectRowAreaTop:
1873         {
1874             if(m_pSelEngine->GetSelectionMode() == NO_SELECTION)
1875                 bSuccess = false;
1876             else if(m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION)
1877                 bSuccess = false;
1878             else
1879             {
1880                 //select the region between the current and the upper row
1881                 RowPos iter = m_nCurRow;
1882                 invalidateSelectedRegion( m_nCurRow, 0 );
1883                 //put the rows in vector
1884                 while(iter>=0)
1885                 {
1886                     if ( !isRowSelected( iter ) )
1887                         m_aSelectedRows.push_back(iter);
1888                     --iter;
1889                 }
1890                 m_nCurRow = 0;
1891                 m_nAnchor = m_nCurRow;
1892                 m_pSelEngine->SetAnchor(sal_True);
1893                 ensureVisible(m_nCurColumn, 0, false);
1894                 selectionChanged = true;
1895                 bSuccess = true;
1896             }
1897         }
1898         break;
1899 
1900         case cursorSelectRowAreaBottom:
1901         {
1902             if(m_pSelEngine->GetSelectionMode() == NO_SELECTION)
1903                 return bSuccess = false;
1904             else if(m_pSelEngine->GetSelectionMode() == SINGLE_SELECTION)
1905                 return bSuccess = false;
1906             //select the region between the current and the last row
1907             RowPos iter = m_nCurRow;
1908             invalidateSelectedRegion( m_nCurRow, m_nRowCount-1 );
1909             //put the rows in the vector
1910             while(iter<=m_nRowCount)
1911             {
1912                 if ( !isRowSelected( iter ) )
1913                     m_aSelectedRows.push_back(iter);
1914                 ++iter;
1915             }
1916             m_nCurRow = m_nRowCount-1;
1917             m_nAnchor = m_nCurRow;
1918             m_pSelEngine->SetAnchor(sal_True);
1919             ensureVisible(m_nCurColumn, m_nRowCount-1, false);
1920             selectionChanged = true;
1921             bSuccess = true;
1922         }
1923         break;
1924         default:
1925             DBG_ERROR( "TableControl_Impl::dispatchAction: unsupported action!" );
1926             break;
1927         }
1928 
1929         if ( bSuccess && selectionChanged )
1930         {
1931             m_rAntiImpl.Select();
1932         }
1933 
1934         return bSuccess;
1935     }
1936 
1937     //------------------------------------------------------------------------------------------------------------------
1938     void TableControl_Impl::impl_ni_doSwitchCursor( bool _bShow )
1939     {
1940         PTableRenderer pRenderer = !!m_pModel ? m_pModel->getRenderer() : PTableRenderer();
1941         if ( !!pRenderer )
1942         {
1943             Rectangle aCellRect;
1944             impl_getCellRect( m_nCurColumn, m_nCurRow, aCellRect );
1945             if ( _bShow )
1946                 pRenderer->ShowCellCursor( *m_pDataWindow, aCellRect );
1947             else
1948                 pRenderer->HideCellCursor( *m_pDataWindow, aCellRect );
1949         }
1950     }
1951 
1952     //------------------------------------------------------------------------------------------------------------------
1953     void TableControl_Impl::impl_getCellRect( ColPos _nColumn, RowPos _nRow, Rectangle& _rCellRect ) const
1954     {
1955         DBG_CHECK_ME();
1956 
1957         if  (   !m_pModel
1958             ||  ( COL_INVALID == _nColumn )
1959             ||  ( ROW_INVALID == _nRow )
1960             )
1961         {
1962             _rCellRect.SetEmpty();
1963             return;
1964         }
1965 
1966         TableCellGeometry aCell( *this, impl_getAllVisibleCellsArea(), _nColumn, _nRow );
1967         _rCellRect = aCell.getRect();
1968     }
1969 
1970     //------------------------------------------------------------------------------------------------------------------
1971     RowPos TableControl_Impl::getRowAtPoint( const Point& rPoint ) const
1972     {
1973         DBG_CHECK_ME();
1974         return impl_getRowForAbscissa( rPoint.Y() );
1975     }
1976 
1977     //------------------------------------------------------------------------------------------------------------------
1978     ColPos TableControl_Impl::getColAtPoint( const Point& rPoint ) const
1979     {
1980         DBG_CHECK_ME();
1981         return impl_getColumnForOrdinate( rPoint.X() );
1982     }
1983 
1984     //------------------------------------------------------------------------------------------------------------------
1985     TableCell TableControl_Impl::hitTest( Point const & i_point ) const
1986     {
1987         TableCell aCell( getColAtPoint( i_point ), getRowAtPoint( i_point ) );
1988         if ( aCell.nColumn > COL_ROW_HEADERS )
1989         {
1990             PColumnModel const pColumn = m_pModel->getColumnModel( aCell.nColumn );
1991             MutableColumnMetrics const & rColInfo( m_aColumnWidths[ aCell.nColumn ] );
1992             if  (   ( rColInfo.getEnd() - 3 <= i_point.X() )
1993                 &&  ( rColInfo.getEnd() >= i_point.X() )
1994                 &&  pColumn->isResizable()
1995                 )
1996             {
1997                 aCell.eArea = ColumnDivider;
1998             }
1999         }
2000         return aCell;
2001     }
2002 
2003     //------------------------------------------------------------------------------------------------------------------
2004     ColumnMetrics TableControl_Impl::getColumnMetrics( ColPos const i_column ) const
2005     {
2006         DBG_CHECK_ME();
2007 
2008         ENSURE_OR_RETURN( ( i_column >= 0 ) && ( i_column < m_pModel->getColumnCount() ),
2009             "TableControl_Impl::getColumnMetrics: illegal column index!", ColumnMetrics() );
2010         return (ColumnMetrics const &)m_aColumnWidths[ i_column ];
2011     }
2012 
2013     //------------------------------------------------------------------------------------------------------------------
2014     PTableModel TableControl_Impl::getModel() const
2015     {
2016         return m_pModel;
2017     }
2018 
2019     //------------------------------------------------------------------------------------------------------------------
2020     RowPos TableControl_Impl::getCurrentColumn() const
2021     {
2022         return m_nCurColumn;
2023     }
2024 
2025     //------------------------------------------------------------------------------------------------------------------
2026     RowPos TableControl_Impl::getCurrentRow() const
2027     {
2028         return m_nCurRow;
2029     }
2030 
2031     //------------------------------------------------------------------------------------------------------------------
2032     ::Size TableControl_Impl::getTableSizePixel() const
2033     {
2034         return m_pDataWindow->GetOutputSizePixel();
2035     }
2036 
2037     //------------------------------------------------------------------------------------------------------------------
2038     void TableControl_Impl::setPointer( Pointer const & i_pointer )
2039     {
2040         DBG_CHECK_ME();
2041         m_pDataWindow->SetPointer( i_pointer );
2042     }
2043 
2044     //------------------------------------------------------------------------------------------------------------------
2045     void TableControl_Impl::captureMouse()
2046     {
2047         m_pDataWindow->CaptureMouse();
2048     }
2049 
2050     //------------------------------------------------------------------------------------------------------------------
2051     void TableControl_Impl::releaseMouse()
2052     {
2053         m_pDataWindow->ReleaseMouse();
2054     }
2055 
2056     //------------------------------------------------------------------------------------------------------------------
2057     void TableControl_Impl::invalidate( TableArea const i_what )
2058     {
2059         switch ( i_what )
2060         {
2061         case TableAreaColumnHeaders:
2062             m_pDataWindow->Invalidate( calcHeaderRect( true ) );
2063             break;
2064 
2065         case TableAreaRowHeaders:
2066             m_pDataWindow->Invalidate( calcHeaderRect( false ) );
2067             break;
2068 
2069         case TableAreaDataArea:
2070             m_pDataWindow->Invalidate( impl_getAllVisibleDataCellArea() );
2071             break;
2072 
2073         case TableAreaAll:
2074             m_pDataWindow->Invalidate();
2075             m_pDataWindow->GetParent()->Invalidate( INVALIDATE_TRANSPARENT );
2076             break;
2077         }
2078     }
2079 
2080     //------------------------------------------------------------------------------------------------------------------
2081     long TableControl_Impl::pixelWidthToAppFont( long const i_pixels ) const
2082     {
2083         return m_pDataWindow->PixelToLogic( Size( i_pixels, 0 ), MAP_APPFONT ).Width();
2084     }
2085 
2086     //------------------------------------------------------------------------------------------------------------------
2087     long TableControl_Impl::appFontWidthToPixel( long const i_appFontUnits ) const
2088     {
2089         return m_pDataWindow->LogicToPixel( Size( i_appFontUnits, 0 ), MAP_APPFONT ).Width();
2090     }
2091 
2092     //------------------------------------------------------------------------------------------------------------------
2093     void TableControl_Impl::hideTracking()
2094     {
2095         m_pDataWindow->HideTracking();
2096     }
2097 
2098     //------------------------------------------------------------------------------------------------------------------
2099     void TableControl_Impl::showTracking( Rectangle const & i_location, sal_uInt16 const i_flags )
2100     {
2101         m_pDataWindow->ShowTracking( i_location, i_flags );
2102     }
2103 
2104     //------------------------------------------------------------------------------------------------------------------
2105     bool TableControl_Impl::activateCell( ColPos const i_col, RowPos const i_row )
2106     {
2107         DBG_CHECK_ME();
2108         return goTo( i_col, i_row );
2109     }
2110 
2111     //------------------------------------------------------------------------------------------------------------------
2112     void TableControl_Impl::invalidateSelectedRegion( RowPos _nPrevRow, RowPos _nCurRow )
2113     {
2114         DBG_CHECK_ME();
2115         // get the visible area of the table control and set the Left and right border of the region to be repainted
2116         Rectangle const aAllCells( impl_getAllVisibleCellsArea() );
2117 
2118         Rectangle aInvalidateRect;
2119         aInvalidateRect.Left() = aAllCells.Left();
2120         aInvalidateRect.Right() = aAllCells.Right();
2121         // if only one row is selected
2122         if ( _nPrevRow == _nCurRow )
2123         {
2124             Rectangle aCellRect;
2125             impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
2126             aInvalidateRect.Top() = aCellRect.Top();
2127             aInvalidateRect.Bottom() = aCellRect.Bottom();
2128         }
2129         //if the region is above the current row
2130         else if(_nPrevRow < _nCurRow )
2131         {
2132             Rectangle aCellRect;
2133             impl_getCellRect( m_nCurColumn, _nPrevRow, aCellRect );
2134             aInvalidateRect.Top() = aCellRect.Top();
2135             impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
2136             aInvalidateRect.Bottom() = aCellRect.Bottom();
2137         }
2138         //if the region is beneath the current row
2139         else
2140         {
2141             Rectangle aCellRect;
2142             impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect );
2143             aInvalidateRect.Top() = aCellRect.Top();
2144             impl_getCellRect( m_nCurColumn, _nPrevRow, aCellRect );
2145             aInvalidateRect.Bottom() = aCellRect.Bottom();
2146         }
2147         m_pDataWindow->Invalidate( aInvalidateRect );
2148     }
2149 
2150     //------------------------------------------------------------------------------------------------------------------
2151     void TableControl_Impl::invalidateSelectedRows()
2152     {
2153         for (   ::std::vector< RowPos >::iterator selRow = m_aSelectedRows.begin();
2154                 selRow != m_aSelectedRows.end();
2155                 ++selRow
2156             )
2157         {
2158             invalidateRow( *selRow );
2159         }
2160     }
2161 
2162     //------------------------------------------------------------------------------------------------------------------
2163     void TableControl_Impl::invalidateRowRange( RowPos const i_firstRow, RowPos const i_lastRow )
2164     {
2165         RowPos const firstRow = i_firstRow < m_nTopRow ? m_nTopRow : i_firstRow;
2166         RowPos const lastVisibleRow = m_nTopRow + impl_getVisibleRows( true ) - 1;
2167         RowPos const lastRow = ( ( i_lastRow == ROW_INVALID ) || ( i_lastRow > lastVisibleRow ) ) ? lastVisibleRow : i_lastRow;
2168 
2169         Rectangle aInvalidateRect;
2170 
2171         Rectangle const aVisibleCellsArea( impl_getAllVisibleCellsArea() );
2172         TableRowGeometry aRow( *this, aVisibleCellsArea, firstRow, true );
2173         while ( aRow.isValid() && ( aRow.getRow() <= lastRow ) )
2174         {
2175             aInvalidateRect.Union( aRow.getRect() );
2176             aRow.moveDown();
2177         }
2178 
2179         if ( i_lastRow == ROW_INVALID )
2180             aInvalidateRect.Bottom() = m_pDataWindow->GetOutputSizePixel().Height();
2181 
2182         m_pDataWindow->Invalidate( aInvalidateRect,
2183             m_pDataWindow->GetControlBackground().GetTransparency() ? INVALIDATE_TRANSPARENT : 0 );
2184     }
2185 
2186     //------------------------------------------------------------------------------
2187     void TableControl_Impl::checkCursorPosition()
2188     {
2189         DBG_CHECK_ME();
2190 
2191         TableSize nVisibleRows = impl_getVisibleRows(true);
2192         TableSize nVisibleCols = impl_getVisibleColumns(true);
2193         if  (   ( m_nTopRow + nVisibleRows > m_nRowCount )
2194             &&  ( m_nRowCount >= nVisibleRows )
2195             )
2196         {
2197             --m_nTopRow;
2198         }
2199         else
2200         {
2201             m_nTopRow = 0;
2202         }
2203 
2204         if  (   ( m_nLeftColumn + nVisibleCols > m_nColumnCount )
2205             &&  ( m_nColumnCount >= nVisibleCols )
2206             )
2207         {
2208             --m_nLeftColumn;
2209         }
2210         else
2211         {
2212             m_nLeftColumn = 0;
2213         }
2214 
2215         m_pDataWindow->Invalidate();
2216     }
2217 
2218     //--------------------------------------------------------------------
2219     TableSize TableControl_Impl::impl_getVisibleRows( bool _bAcceptPartialRow ) const
2220     {
2221         DBG_CHECK_ME();
2222 
2223         DBG_ASSERT( m_pDataWindow, "TableControl_Impl::impl_getVisibleRows: no data window!" );
2224 
2225         return lcl_getRowsFittingInto(
2226             m_pDataWindow->GetOutputSizePixel().Height() - m_nColHeaderHeightPixel,
2227             m_nRowHeightPixel,
2228             _bAcceptPartialRow
2229         );
2230     }
2231 
2232     //--------------------------------------------------------------------
2233     TableSize TableControl_Impl::impl_getVisibleColumns( bool _bAcceptPartialCol ) const
2234     {
2235         DBG_CHECK_ME();
2236 
2237         DBG_ASSERT( m_pDataWindow, "TableControl_Impl::impl_getVisibleColumns: no data window!" );
2238 
2239         return lcl_getColumnsVisibleWithin(
2240             Rectangle( Point( 0, 0 ), m_pDataWindow->GetOutputSizePixel() ),
2241             m_nLeftColumn,
2242             *this,
2243             _bAcceptPartialCol
2244         );
2245     }
2246 
2247     //--------------------------------------------------------------------
2248     bool TableControl_Impl::goTo( ColPos _nColumn, RowPos _nRow )
2249     {
2250         DBG_CHECK_ME();
2251 
2252         // TODO: give veto listeners a chance
2253 
2254         if  (  ( _nColumn < 0 ) || ( _nColumn >= m_nColumnCount )
2255             || ( _nRow < 0 ) || ( _nRow >= m_nRowCount )
2256             )
2257         {
2258             OSL_ENSURE( false, "TableControl_Impl::goTo: invalid row or column index!" );
2259             return false;
2260         }
2261 
2262         SuppressCursor aHideCursor( *this );
2263         m_nCurColumn = _nColumn;
2264         m_nCurRow = _nRow;
2265 
2266         // ensure that the new cell is visible
2267         ensureVisible( m_nCurColumn, m_nCurRow, false );
2268         return true;
2269     }
2270 
2271     //--------------------------------------------------------------------
2272     void TableControl_Impl::ensureVisible( ColPos _nColumn, RowPos _nRow, bool _bAcceptPartialVisibility )
2273     {
2274         DBG_CHECK_ME();
2275         DBG_ASSERT( ( _nColumn >= 0 ) && ( _nColumn < m_nColumnCount )
2276                  && ( _nRow >= 0 ) && ( _nRow < m_nRowCount ),
2277                  "TableControl_Impl::ensureVisible: invalid coordinates!" );
2278 
2279         SuppressCursor aHideCursor( *this );
2280 
2281         if ( _nColumn < m_nLeftColumn )
2282             impl_scrollColumns( _nColumn - m_nLeftColumn );
2283         else
2284         {
2285             TableSize nVisibleColumns = impl_getVisibleColumns( _bAcceptPartialVisibility );
2286             if ( _nColumn > m_nLeftColumn + nVisibleColumns - 1 )
2287             {
2288                 impl_scrollColumns( _nColumn - ( m_nLeftColumn + nVisibleColumns - 1 ) );
2289                 // TODO: since not all columns have the same width, this might in theory result
2290                 // in the column still not being visible.
2291             }
2292         }
2293 
2294         if ( _nRow < m_nTopRow )
2295             impl_scrollRows( _nRow - m_nTopRow );
2296         else
2297         {
2298             TableSize nVisibleRows = impl_getVisibleRows( _bAcceptPartialVisibility );
2299             if ( _nRow > m_nTopRow + nVisibleRows - 1 )
2300                 impl_scrollRows( _nRow - ( m_nTopRow + nVisibleRows - 1 ) );
2301         }
2302     }
2303 
2304     //--------------------------------------------------------------------
2305     ::rtl::OUString TableControl_Impl::getCellContentAsString( RowPos const i_row, ColPos const i_col )
2306     {
2307         Any aCellValue;
2308         m_pModel->getCellContent( i_col, i_row, aCellValue );
2309 
2310         ::rtl::OUString sCellStringContent;
2311         m_pModel->getRenderer()->GetFormattedCellString( aCellValue, i_col, i_row, sCellStringContent );
2312 
2313         return sCellStringContent;
2314     }
2315 
2316     //--------------------------------------------------------------------
2317     TableSize TableControl_Impl::impl_ni_ScrollRows( TableSize _nRowDelta )
2318     {
2319         // compute new top row
2320         RowPos nNewTopRow =
2321             ::std::max(
2322                 ::std::min( (RowPos)( m_nTopRow + _nRowDelta ), (RowPos)( m_nRowCount - 1 ) ),
2323                 (RowPos)0
2324             );
2325 
2326         RowPos nOldTopRow = m_nTopRow;
2327         m_nTopRow = nNewTopRow;
2328 
2329         // if updates are enabled currently, scroll the viewport
2330         if ( m_nTopRow != nOldTopRow )
2331         {
2332             DBG_SUSPEND_INV( INV_SCROLL_POSITION );
2333             SuppressCursor aHideCursor( *this );
2334             // TODO: call a onStartScroll at our listener (or better an own onStartScroll,
2335             // which hides the cursor and then calls the listener)
2336             // Same for onEndScroll
2337 
2338             // scroll the view port, if possible
2339             long nPixelDelta = m_nRowHeightPixel * ( m_nTopRow - nOldTopRow );
2340 
2341             Rectangle aDataArea( Point( 0, m_nColHeaderHeightPixel ), m_pDataWindow->GetOutputSizePixel() );
2342 
2343             if  (   m_pDataWindow->GetBackground().IsScrollable()
2344                 &&  abs( nPixelDelta ) < aDataArea.GetHeight()
2345                 )
2346             {
2347                 m_pDataWindow->Scroll( 0, (long)-nPixelDelta, aDataArea, SCROLL_CLIP | SCROLL_UPDATE | SCROLL_CHILDREN);
2348             }
2349             else
2350             {
2351                 m_pDataWindow->Invalidate( INVALIDATE_UPDATE );
2352                 m_pDataWindow->GetParent()->Invalidate( INVALIDATE_TRANSPARENT );
2353             }
2354 
2355             // update the position at the vertical scrollbar
2356             if ( m_pVScroll != NULL )
2357                 m_pVScroll->SetThumbPos( m_nTopRow );
2358         }
2359 
2360         // The scroll bar availaility might change when we scrolled.
2361         // For instance, imagine a view with 10 rows, if which 5 fit into the window, numbered 1 to 10.
2362         // Now let
2363         // - the user scroll to row number 6, so the last 5 rows are visible
2364         // - somebody remove the last 4 rows
2365         // - the user scroll to row number 5 being the top row, so the last two rows are visible
2366         // - somebody remove row number 6
2367         // - the user scroll to row number 1
2368         // => in this case, the need for the scrollbar vanishes immediately.
2369         if ( m_nTopRow == 0 )
2370             m_rAntiImpl.PostUserEvent( LINK( this, TableControl_Impl, OnUpdateScrollbars ) );
2371 
2372         return (TableSize)( m_nTopRow - nOldTopRow );
2373     }
2374 
2375     //--------------------------------------------------------------------
2376     TableSize TableControl_Impl::impl_scrollRows( TableSize const i_rowDelta )
2377     {
2378         DBG_CHECK_ME();
2379         return impl_ni_ScrollRows( i_rowDelta );
2380     }
2381 
2382     //--------------------------------------------------------------------
2383     TableSize TableControl_Impl::impl_ni_ScrollColumns( TableSize _nColumnDelta )
2384     {
2385         // compute new left column
2386         const ColPos nNewLeftColumn =
2387             ::std::max(
2388                 ::std::min( (ColPos)( m_nLeftColumn + _nColumnDelta ), (ColPos)( m_nColumnCount - 1 ) ),
2389                 (ColPos)0
2390             );
2391 
2392         const ColPos nOldLeftColumn = m_nLeftColumn;
2393         m_nLeftColumn = nNewLeftColumn;
2394 
2395         // if updates are enabled currently, scroll the viewport
2396         if ( m_nLeftColumn != nOldLeftColumn )
2397         {
2398             DBG_SUSPEND_INV( INV_SCROLL_POSITION );
2399             SuppressCursor aHideCursor( *this );
2400             // TODO: call a onStartScroll at our listener (or better an own onStartScroll,
2401             // which hides the cursor and then calls the listener)
2402             // Same for onEndScroll
2403 
2404             // scroll the view port, if possible
2405             const Rectangle aDataArea( Point( m_nRowHeaderWidthPixel, 0 ), m_pDataWindow->GetOutputSizePixel() );
2406 
2407             long nPixelDelta =
2408                     m_aColumnWidths[ nOldLeftColumn ].getStart()
2409                 -   m_aColumnWidths[ m_nLeftColumn ].getStart();
2410 
2411             // update our column positions
2412             // Do this *before* scrolling, as SCROLL_UPDATE will trigger a paint, which already needs the correct
2413             // information in m_aColumnWidths
2414             for (   ColumnPositions::iterator colPos = m_aColumnWidths.begin();
2415                     colPos != m_aColumnWidths.end();
2416                     ++colPos
2417                  )
2418             {
2419                 colPos->move( nPixelDelta );
2420             }
2421 
2422             // scroll the window content (if supported and possible), or invalidate the complete window
2423             if  (   m_pDataWindow->GetBackground().IsScrollable()
2424                 &&  abs( nPixelDelta ) < aDataArea.GetWidth()
2425                 )
2426             {
2427                 m_pDataWindow->Scroll( nPixelDelta, 0, aDataArea, SCROLL_CLIP | SCROLL_UPDATE );
2428             }
2429             else
2430             {
2431                 m_pDataWindow->Invalidate( INVALIDATE_UPDATE );
2432                 m_pDataWindow->GetParent()->Invalidate( INVALIDATE_TRANSPARENT );
2433             }
2434 
2435             // update the position at the horizontal scrollbar
2436             if ( m_pHScroll != NULL )
2437                 m_pHScroll->SetThumbPos( m_nLeftColumn );
2438         }
2439 
2440         // The scroll bar availaility might change when we scrolled. This is because we do not hide
2441         // the scrollbar when it is, in theory, unnecessary, but currently at a position > 0. In this case, it will
2442         // be auto-hidden when it's scrolled back to pos 0.
2443         if ( m_nLeftColumn == 0 )
2444             m_rAntiImpl.PostUserEvent( LINK( this, TableControl_Impl, OnUpdateScrollbars ) );
2445 
2446         return (TableSize)( m_nLeftColumn - nOldLeftColumn );
2447     }
2448 
2449     //------------------------------------------------------------------------------------------------------------------
2450     TableSize TableControl_Impl::impl_scrollColumns( TableSize const i_columnDelta )
2451     {
2452         DBG_CHECK_ME();
2453         return impl_ni_ScrollColumns( i_columnDelta );
2454     }
2455 
2456     //------------------------------------------------------------------------------------------------------------------
2457     SelectionEngine* TableControl_Impl::getSelEngine()
2458     {
2459         return m_pSelEngine;
2460     }
2461 
2462     //------------------------------------------------------------------------------------------------------------------
2463     ScrollBar* TableControl_Impl::getHorzScrollbar()
2464     {
2465         return m_pHScroll;
2466     }
2467 
2468     //------------------------------------------------------------------------------------------------------------------
2469     ScrollBar* TableControl_Impl::getVertScrollbar()
2470     {
2471         return m_pVScroll;
2472     }
2473 
2474     //------------------------------------------------------------------------------------------------------------------
2475     bool TableControl_Impl::isRowSelected( RowPos i_row ) const
2476     {
2477         return ::std::find( m_aSelectedRows.begin(), m_aSelectedRows.end(), i_row ) != m_aSelectedRows.end();
2478     }
2479 
2480     //------------------------------------------------------------------------------------------------------------------
2481     RowPos TableControl_Impl::getSelectedRowIndex( size_t const i_selectionIndex ) const
2482     {
2483         if ( i_selectionIndex < m_aSelectedRows.size() )
2484             return m_aSelectedRows[ i_selectionIndex ];
2485         return ROW_INVALID;
2486     }
2487 
2488     //------------------------------------------------------------------------------------------------------------------
2489     int TableControl_Impl::getRowSelectedNumber(const ::std::vector<RowPos>& selectedRows, RowPos current)
2490     {
2491         std::vector<RowPos>::const_iterator it = ::std::find(selectedRows.begin(),selectedRows.end(),current);
2492         if ( it != selectedRows.end() )
2493         {
2494             return it - selectedRows.begin();
2495         }
2496         return -1;
2497     }
2498 
2499     //--------------------------------------------------------------------
2500     ColPos TableControl_Impl::impl_getColumnForOrdinate( long const i_ordinate ) const
2501     {
2502         DBG_CHECK_ME();
2503 
2504         if ( ( m_aColumnWidths.empty() ) || ( i_ordinate < 0 ) )
2505             return COL_INVALID;
2506 
2507         if ( i_ordinate < m_nRowHeaderWidthPixel )
2508             return COL_ROW_HEADERS;
2509 
2510         ColumnPositions::const_iterator lowerBound = ::std::lower_bound(
2511             m_aColumnWidths.begin(),
2512             m_aColumnWidths.end(),
2513             i_ordinate + 1,
2514             ColumnInfoPositionLess()
2515         );
2516         if ( lowerBound == m_aColumnWidths.end() )
2517         {
2518             // point is *behind* the start of the last column ...
2519             if ( i_ordinate < m_aColumnWidths.rbegin()->getEnd() )
2520                 // ... but still before its end
2521                 return m_nColumnCount - 1;
2522             return COL_INVALID;
2523         }
2524         return lowerBound - m_aColumnWidths.begin();
2525     }
2526 
2527     //--------------------------------------------------------------------
2528     RowPos TableControl_Impl::impl_getRowForAbscissa( long const i_abscissa ) const
2529     {
2530         DBG_CHECK_ME();
2531 
2532         if ( i_abscissa < 0 )
2533             return ROW_INVALID;
2534 
2535         if ( i_abscissa < m_nColHeaderHeightPixel )
2536             return ROW_COL_HEADERS;
2537 
2538         long const abscissa = i_abscissa - m_nColHeaderHeightPixel;
2539         long const row = m_nTopRow + abscissa / m_nRowHeightPixel;
2540         return row < m_pModel->getRowCount() ? row : ROW_INVALID;
2541     }
2542 
2543     //--------------------------------------------------------------------
2544     bool TableControl_Impl::markRowAsDeselected( RowPos const i_rowIndex )
2545     {
2546         DBG_CHECK_ME();
2547 
2548         ::std::vector< RowPos >::iterator selPos = ::std::find( m_aSelectedRows.begin(), m_aSelectedRows.end(), i_rowIndex );
2549         if ( selPos == m_aSelectedRows.end() )
2550             return false;
2551 
2552         m_aSelectedRows.erase( selPos );
2553         return true;
2554     }
2555 
2556     //--------------------------------------------------------------------
2557     bool TableControl_Impl::markRowAsSelected( RowPos const i_rowIndex )
2558     {
2559         DBG_CHECK_ME();
2560 
2561         if ( isRowSelected( i_rowIndex ) )
2562             return false;
2563 
2564         SelectionMode const eSelMode = getSelEngine()->GetSelectionMode();
2565         switch ( eSelMode )
2566         {
2567         case SINGLE_SELECTION:
2568             if ( !m_aSelectedRows.empty() )
2569             {
2570                 OSL_ENSURE( m_aSelectedRows.size() == 1, "TableControl::markRowAsSelected: SingleSelection with more than one selected element?" );
2571                 m_aSelectedRows[0] = i_rowIndex;
2572                 break;
2573             }
2574             // fall through
2575 
2576         case MULTIPLE_SELECTION:
2577             m_aSelectedRows.push_back( i_rowIndex );
2578             break;
2579 
2580         default:
2581             OSL_ENSURE( false, "TableControl_Impl::markRowAsSelected: unsupported selection mode!" );
2582             return false;
2583         }
2584 
2585         return true;
2586     }
2587 
2588     //--------------------------------------------------------------------
2589     bool TableControl_Impl::markAllRowsAsDeselected()
2590     {
2591         if ( m_aSelectedRows.empty() )
2592             return false;
2593 
2594         m_aSelectedRows.clear();
2595         return true;
2596     }
2597 
2598     //--------------------------------------------------------------------
2599     bool TableControl_Impl::markAllRowsAsSelected()
2600     {
2601         DBG_CHECK_ME();
2602 
2603         SelectionMode const eSelMode = getSelEngine()->GetSelectionMode();
2604         ENSURE_OR_RETURN_FALSE( eSelMode == MULTIPLE_SELECTION, "TableControl_Impl::markAllRowsAsSelected: unsupported selection mode!" );
2605 
2606         if ( m_aSelectedRows.size() == size_t( m_pModel->getRowCount() ) )
2607         {
2608         #if OSL_DEBUG_LEVEL > 0
2609             for ( TableSize row = 0; row < m_pModel->getRowCount(); ++row )
2610             {
2611                 OSL_ENSURE( isRowSelected( row ), "TableControl_Impl::markAllRowsAsSelected: inconsistency in the selected rows!" );
2612             }
2613         #endif
2614             // already all rows marked as selected
2615             return false;
2616         }
2617 
2618         m_aSelectedRows.clear();
2619         for ( RowPos i=0; i < m_pModel->getRowCount(); ++i )
2620             m_aSelectedRows.push_back(i);
2621 
2622         return true;
2623     }
2624 
2625     //--------------------------------------------------------------------
2626     void TableControl_Impl::commitAccessibleEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue )
2627     {
2628         impl_commitAccessibleEvent( i_eventID, i_newValue, i_oldValue );
2629     }
2630 
2631     //--------------------------------------------------------------------
2632     void TableControl_Impl::commitCellEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue )
2633     {
2634         DBG_CHECK_ME();
2635         if ( impl_isAccessibleAlive() )
2636             m_pAccessibleTable->commitCellEvent( i_eventID, i_newValue, i_oldValue );
2637     }
2638 
2639     //--------------------------------------------------------------------
2640     void TableControl_Impl::commitTableEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue )
2641     {
2642         DBG_CHECK_ME();
2643         if ( impl_isAccessibleAlive() )
2644             m_pAccessibleTable->commitTableEvent( i_eventID, i_newValue, i_oldValue );
2645     }
2646 
2647     //--------------------------------------------------------------------
2648     Rectangle TableControl_Impl::calcHeaderRect(bool bColHeader)
2649     {
2650         Rectangle const aRectTableWithHeaders( impl_getAllVisibleCellsArea() );
2651         Size const aSizeTableWithHeaders( aRectTableWithHeaders.GetSize() );
2652         if ( bColHeader )
2653             return Rectangle( aRectTableWithHeaders.TopLeft(), Size( aSizeTableWithHeaders.Width(), m_nColHeaderHeightPixel ) );
2654         else
2655             return Rectangle( aRectTableWithHeaders.TopLeft(), Size( m_nRowHeaderWidthPixel, aSizeTableWithHeaders.Height() ) );
2656     }
2657 
2658     //--------------------------------------------------------------------
2659     Rectangle TableControl_Impl::calcHeaderCellRect( bool bColHeader, sal_Int32 nPos )
2660     {
2661         Rectangle const aHeaderRect = calcHeaderRect( bColHeader );
2662         TableCellGeometry const aGeometry(
2663             *this, aHeaderRect,
2664             bColHeader ? nPos : COL_ROW_HEADERS,
2665             bColHeader ? ROW_COL_HEADERS : nPos
2666         );
2667         return aGeometry.getRect();
2668     }
2669 
2670     //--------------------------------------------------------------------
2671     Rectangle TableControl_Impl::calcTableRect()
2672     {
2673         return impl_getAllVisibleDataCellArea();
2674     }
2675 
2676     //--------------------------------------------------------------------
2677     Rectangle TableControl_Impl::calcCellRect( sal_Int32 nRow, sal_Int32 nCol )
2678     {
2679         Rectangle aCellRect;
2680         impl_getCellRect( nRow, nCol, aCellRect );
2681         return aCellRect;
2682     }
2683 
2684     //--------------------------------------------------------------------
2685     IMPL_LINK( TableControl_Impl, OnUpdateScrollbars, void*, /**/ )
2686     {
2687         DBG_CHECK_ME();
2688         // TODO: can't we simply use lcl_updateScrollbar here, so the scrollbars ranges are updated, instead of
2689         // doing a complete re-layout?
2690         impl_ni_relayout();
2691         return 1L;
2692     }
2693 
2694     //--------------------------------------------------------------------
2695     IMPL_LINK( TableControl_Impl, OnScroll, ScrollBar*, _pScrollbar )
2696     {
2697         DBG_ASSERT( ( _pScrollbar == m_pVScroll ) || ( _pScrollbar == m_pHScroll ),
2698             "TableControl_Impl::OnScroll: where did this come from?" );
2699 
2700         if ( _pScrollbar == m_pVScroll )
2701             impl_ni_ScrollRows( _pScrollbar->GetDelta() );
2702         else
2703             impl_ni_ScrollColumns( _pScrollbar->GetDelta() );
2704 
2705         return 0L;
2706     }
2707 
2708     //------------------------------------------------------------------------------------------------------------------
2709     Reference< XAccessible > TableControl_Impl::getAccessible( Window& i_parentWindow )
2710     {
2711         DBG_TESTSOLARMUTEX();
2712         if ( m_pAccessibleTable == NULL )
2713         {
2714             Reference< XAccessible > const xAccParent = i_parentWindow.GetAccessible();
2715             if ( xAccParent.is() )
2716             {
2717                 m_pAccessibleTable = m_aFactoryAccess.getFactory().createAccessibleTableControl(
2718                     xAccParent, m_rAntiImpl
2719                 );
2720             }
2721         }
2722 
2723         Reference< XAccessible > xAccessible;
2724         if ( m_pAccessibleTable )
2725             xAccessible = m_pAccessibleTable->getMyself();
2726         return xAccessible;
2727     }
2728 
2729     //------------------------------------------------------------------------------------------------------------------
2730     void TableControl_Impl::disposeAccessible()
2731     {
2732         if ( m_pAccessibleTable )
2733             m_pAccessibleTable->dispose();
2734         m_pAccessibleTable = NULL;
2735     }
2736 
2737     //------------------------------------------------------------------------------------------------------------------
2738     bool TableControl_Impl::impl_isAccessibleAlive() const
2739     {
2740         DBG_CHECK_ME();
2741         return ( NULL != m_pAccessibleTable ) && m_pAccessibleTable->isAlive();
2742     }
2743 
2744     //------------------------------------------------------------------------------------------------------------------
2745     void TableControl_Impl::impl_commitAccessibleEvent( sal_Int16 const i_eventID, Any const & i_newValue, Any const & i_oldValue )
2746     {
2747         DBG_CHECK_ME();
2748         if ( impl_isAccessibleAlive() )
2749             m_pAccessibleTable->commitEvent( i_eventID, i_newValue, i_oldValue );
2750     }
2751 
2752     //==================================================================================================================
2753     //= TableFunctionSet
2754     //==================================================================================================================
2755     //------------------------------------------------------------------------------------------------------------------
2756     TableFunctionSet::TableFunctionSet(TableControl_Impl* _pTableControl)
2757         :m_pTableControl( _pTableControl)
2758         ,m_nCurrentRow( ROW_INVALID )
2759     {
2760     }
2761     //------------------------------------------------------------------------------------------------------------------
2762     TableFunctionSet::~TableFunctionSet()
2763     {
2764     }
2765     //------------------------------------------------------------------------------------------------------------------
2766     void TableFunctionSet::BeginDrag()
2767     {
2768     }
2769     //------------------------------------------------------------------------------------------------------------------
2770     void TableFunctionSet::CreateAnchor()
2771     {
2772         m_pTableControl->setAnchor( m_pTableControl->getCurRow() );
2773     }
2774 
2775     //------------------------------------------------------------------------------------------------------------------
2776     void TableFunctionSet::DestroyAnchor()
2777     {
2778         m_pTableControl->setAnchor( ROW_INVALID );
2779     }
2780 
2781     //------------------------------------------------------------------------------------------------------------------
2782     sal_Bool TableFunctionSet::SetCursorAtPoint(const Point& rPoint, sal_Bool bDontSelectAtCursor)
2783     {
2784         sal_Bool bHandled = sal_False;
2785         // newRow is the row which includes the point, getCurRow() is the last selected row, before the mouse click
2786         RowPos newRow = m_pTableControl->getRowAtPoint( rPoint );
2787         if ( newRow == ROW_COL_HEADERS )
2788             newRow = m_pTableControl->getTopRow();
2789 
2790         ColPos newCol = m_pTableControl->getColAtPoint( rPoint );
2791         if ( newCol == COL_ROW_HEADERS )
2792             newCol = m_pTableControl->getLeftColumn();
2793 
2794         if ( ( newRow == ROW_INVALID ) || ( newCol == COL_INVALID ) )
2795             return sal_False;
2796 
2797         if ( bDontSelectAtCursor )
2798         {
2799             if ( m_pTableControl->getSelectedRowCount() > 1 )
2800                 m_pTableControl->getSelEngine()->AddAlways(sal_True);
2801             bHandled = sal_True;
2802         }
2803         else if ( m_pTableControl->getAnchor() == m_pTableControl->getCurRow() )
2804         {
2805             //selecting region,
2806             int diff = m_pTableControl->getCurRow() - newRow;
2807             //selected region lies above the last selection
2808             if( diff >= 0)
2809             {
2810                 //put selected rows in vector
2811                 while ( m_pTableControl->getAnchor() >= newRow )
2812                 {
2813                     m_pTableControl->markRowAsSelected( m_pTableControl->getAnchor() );
2814                     m_pTableControl->setAnchor( m_pTableControl->getAnchor() - 1 );
2815                     diff--;
2816                 }
2817                 m_pTableControl->setAnchor( m_pTableControl->getAnchor() + 1 );
2818             }
2819             //selected region lies beneath the last selected row
2820             else
2821             {
2822                 while ( m_pTableControl->getAnchor() <= newRow )
2823                 {
2824                     m_pTableControl->markRowAsSelected( m_pTableControl->getAnchor() );
2825                     m_pTableControl->setAnchor( m_pTableControl->getAnchor() + 1 );
2826                     diff++;
2827                 }
2828                 m_pTableControl->setAnchor( m_pTableControl->getAnchor() - 1 );
2829             }
2830             m_pTableControl->invalidateSelectedRegion( m_pTableControl->getCurRow(), newRow );
2831             bHandled = sal_True;
2832         }
2833         //no region selected
2834         else
2835         {
2836             if ( !m_pTableControl->hasRowSelection() )
2837                 m_pTableControl->markRowAsSelected( newRow );
2838             else
2839             {
2840                 if ( m_pTableControl->getSelEngine()->GetSelectionMode() == SINGLE_SELECTION )
2841                 {
2842                     DeselectAll();
2843                     m_pTableControl->markRowAsSelected( newRow );
2844                 }
2845                 else
2846                 {
2847                     m_pTableControl->markRowAsSelected( newRow );
2848                 }
2849             }
2850             if ( m_pTableControl->getSelectedRowCount() > 1 && m_pTableControl->getSelEngine()->GetSelectionMode() != SINGLE_SELECTION )
2851                 m_pTableControl->getSelEngine()->AddAlways(sal_True);
2852 
2853             m_pTableControl->invalidateRow( newRow );
2854             bHandled = sal_True;
2855         }
2856         m_pTableControl->goTo( newCol, newRow );
2857         return bHandled;
2858     }
2859     //------------------------------------------------------------------------------------------------------------------
2860     sal_Bool TableFunctionSet::IsSelectionAtPoint( const Point& rPoint )
2861     {
2862         m_pTableControl->getSelEngine()->AddAlways(sal_False);
2863         if ( !m_pTableControl->hasRowSelection() )
2864             return sal_False;
2865         else
2866         {
2867             RowPos curRow = m_pTableControl->getRowAtPoint( rPoint );
2868             m_pTableControl->setAnchor( ROW_INVALID );
2869             bool selected = m_pTableControl->isRowSelected( curRow );
2870             m_nCurrentRow = curRow;
2871             return selected;
2872         }
2873     }
2874     //------------------------------------------------------------------------------------------------------------------
2875     void TableFunctionSet::DeselectAtPoint( const Point& rPoint )
2876     {
2877         (void)rPoint;
2878         m_pTableControl->invalidateRow( m_nCurrentRow );
2879         m_pTableControl->markRowAsDeselected( m_nCurrentRow );
2880     }
2881 
2882     //------------------------------------------------------------------------------------------------------------------
2883     void TableFunctionSet::DeselectAll()
2884     {
2885         if ( m_pTableControl->hasRowSelection() )
2886         {
2887             for ( size_t i=0; i<m_pTableControl->getSelectedRowCount(); ++i )
2888             {
2889                 RowPos const rowIndex = m_pTableControl->getSelectedRowIndex(i);
2890                 m_pTableControl->invalidateRow( rowIndex );
2891             }
2892 
2893             m_pTableControl->markAllRowsAsDeselected();
2894         }
2895     }
2896 
2897 //......................................................................................................................
2898 } } // namespace svt::table
2899 //......................................................................................................................
2900