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